diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f170a1fd1..88458a696 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -57,8 +57,9 @@ body: id: homeserver attributes: label: Homeserver - description: Which server is your account registered on? - placeholder: e.g. matrix.org + description: | + Which server is your account registered on? If it is a local or non-public homeserver, please tell us what is the homeserver implementation (ex: Synapse/Dendrite/etc.) and the version. + placeholder: e.g. matrix.org or Synapse 1.50.0rc1 validations: required: false - type: dropdown diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index dfbd7ac6a..c952f6a4a 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -13,7 +13,21 @@ env: MX_GIT_BRANCH: ${{ github.event.pull_request.head.ref }} jobs: + check-secret: + runs-on: macos-11 + outputs: + out-key: ${{ steps.out-key.outputs.defined }} + steps: + - id: out-key + env: + P12_KEY: ${{ secrets.ALPHA_CERTIFICATES_P12 }} + P12_PASSWORD_KEY: ${{ secrets.ALPHA_CERTIFICATES_P12 }} + if: "${{ env.P12_KEY != '' || env.P12_PASSWORD_KEY != '' }}" + run: echo "::set-output name=defined::true" build: + # Run job if secrets are avilable (not avaiable for forks). + needs: [check-secret] + if: needs.check-secret.outputs.out-key == 'true' name: Release runs-on: macos-11 diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 44704833b..4645e9b02 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -5,6 +5,31 @@ on: types: [labeled] jobs: + apply_Z-Labs_label: + name: Add Z-Labs label for features behind labs flags + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'A-Maths') || + contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || + contains(github.event.issue.labels.*.name, 'A-Threads') || + contains(github.event.issue.labels.*.name, 'A-Polls') || + contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || + contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || + contains(github.event.issue.labels.*.name, 'Z-IA') || + contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || + contains(github.event.issue.labels.*.name, 'A-Tags') + steps: + - uses: actions/github-script@v5 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['Z-Labs'] + }) + move_needs_info_issues: name: X-Needs-Info issues to Need info column on triage board runs-on: ubuntu-latest @@ -34,7 +59,7 @@ jobs: with: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | - mutation add_to_project($projectid:String!,$contentid:String!) { + mutation add_to_project($projectid:ID!,$contentid:ID!) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { projectNextItem { id @@ -47,19 +72,44 @@ jobs: PROJECT_ID: "PN_kwDOAM0swc0sUA" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + add_product_issues_to_project: + name: X-Needs-Product to Design project board + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'X-Needs-Product') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc4AAg6N" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + Delight_issues_to_board: name: Spaces issues to Delight project board runs-on: ubuntu-latest if: > contains(github.event.issue.labels.*.name, 'A-Spaces') || contains(github.event.issue.labels.*.name, 'A-Space-Settings') || - contains(github.event.issue.labels.*.name, 'A-Subspaces') + contains(github.event.issue.labels.*.name, 'A-Subspaces') || + contains(github.event.issue.labels.*.name, 'Z-IA') steps: - uses: octokit/graphql-action@v2.x with: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | - mutation add_to_project($projectid:String!,$contentid:String!) { + mutation add_to_project($projectid:ID!,$contentid:ID!) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { projectNextItem { id @@ -82,7 +132,7 @@ jobs: with: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | - mutation add_to_project($projectid:String!,$contentid:String!) { + mutation add_to_project($projectid:ID!,$contentid:ID!) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { projectNextItem { id @@ -105,7 +155,7 @@ jobs: with: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | - mutation add_to_project($projectid:String!,$contentid:String!) { + mutation add_to_project($projectid:ID!,$contentid:ID!) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { projectNextItem { id @@ -128,7 +178,7 @@ jobs: with: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | - mutation add_to_project($projectid:String!,$contentid:String!) { + mutation add_to_project($projectid:ID!,$contentid:ID!) { addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { projectNextItem { id @@ -140,3 +190,49 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc3m-g" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + +move_FTUE_issues: + name: Z-FTUE to FTUE board + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'Z-FTUE') + steps: + - uses: octokit/graphql-action@v2.x + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc4AAqVx" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + +move_WTF_issues: + name: Z-WTF to WTF board + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'Z-WTF') + steps: + - uses: octokit/graphql-action@v2.x + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc4AArk0" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-move-unlabelled.yml b/.github/workflows/triage-move-unlabelled.yml index 94bd049b9..453fafe54 100644 --- a/.github/workflows/triage-move-unlabelled.yml +++ b/.github/workflows/triage-move-unlabelled.yml @@ -33,3 +33,29 @@ jobs: project: Issue triage column: Triaged repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }} + + remove_Z-Labs_label: + name: Remove Z-Labs label when features behind labs flags are removed + runs-on: ubuntu-latest + if: > + !(contains(github.event.issue.labels.*.name, 'A-Maths') || + contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || + contains(github.event.issue.labels.*.name, 'A-Threads') || + contains(github.event.issue.labels.*.name, 'A-Polls') || + contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || + contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || + contains(github.event.issue.labels.*.name, 'Z-IA') || + contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || + contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || + contains(github.event.issue.labels.*.name, 'A-Tags')) && + contains(github.event.issue.labels.*.name, 'Z-Labs') + steps: + - uses: actions/github-script@v5 + with: + script: | + github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: ['Z-Labs'] + }) diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 0a4d1c49f..843c6234c 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -34,6 +34,7 @@ jobs: P1_issues_to_crypto_team_workboard: runs-on: ubuntu-latest if: > + contains(github.event.issue.labels.*.name, 'Z-UISI') || (contains(github.event.issue.labels.*.name, 'A-E2EE') || contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') || contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || diff --git a/.github/workflows/triage-review-requests.yml b/.github/workflows/triage-review-requests.yml new file mode 100644 index 000000000..8306cffef --- /dev/null +++ b/.github/workflows/triage-review-requests.yml @@ -0,0 +1,139 @@ +name: Move pull requests asking for review to the relevant project +on: + pull_request_target: + types: [review_requested] + +jobs: + add_design_pr_to_project: + name: Move PRs asking for design review to the design board + runs-on: ubuntu-latest + steps: + - uses: octokit/graphql-action@v2.x + id: find_team_members + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + query find_team_members($team: String!) { + organization(login: "vector-im") { + team(slug: $team) { + members { + nodes { + login + } + } + } + } + } + team: ${{ env.TEAM }} + env: + TEAM: "design" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + - id: any_matching_reviewers + run: | + # Fetch requested reviewers, and people who are on the team + echo '${{ tojson(fromjson(steps.find_team_members.outputs.data).organization.team.members.nodes[*].login) }}' | tee /tmp/team_members.json + echo '${{ tojson(github.event.pull_request.requested_reviewers[*].login) }}' | tee /tmp/reviewers.json + jq --raw-output .[] < /tmp/team_members.json | sort | tee /tmp/team_members.txt + jq --raw-output .[] < /tmp/reviewers.json | sort | tee /tmp/reviewers.txt + + # Fetch requested team reviewers, and the name of the team + echo '${{ tojson(github.event.pull_request.requested_teams[*].slug) }}' | tee /tmp/team_reviewers.json + jq --raw-output .[] < /tmp/team_reviewers.json | sort | tee /tmp/team_reviewers.txt + echo '${{ env.TEAM }}' | tee /tmp/team.txt + + # If either a reviewer matches a team member, or a team matches our team, say "true" + if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then + echo "::set-output name=match::true" + elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then + echo "::set-output name=match::true" + else + echo "::set-output name=match::false" + fi + env: + TEAM: "design" + - uses: octokit/graphql-action@v2.x + id: add_to_project + if: steps.any_matching_reviewers.outputs.match == 'true' + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!, $contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.pull_request.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc0sUA" + TEAM: "design" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + add_product_pr_to_project: + name: Move PRs asking for design review to the design board + runs-on: ubuntu-latest + steps: + - uses: octokit/graphql-action@v2.x + id: find_team_members + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + query find_team_members($team: String!) { + organization(login: "vector-im") { + team(slug: $team) { + members { + nodes { + login + } + } + } + } + } + team: ${{ env.TEAM }} + env: + TEAM: "product" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + - id: any_matching_reviewers + run: | + # Fetch requested reviewers, and people who are on the team + echo '${{ tojson(fromjson(steps.find_team_members.outputs.data).organization.team.members.nodes[*].login) }}' | tee /tmp/team_members.json + echo '${{ tojson(github.event.pull_request.requested_reviewers[*].login) }}' | tee /tmp/reviewers.json + jq --raw-output .[] < /tmp/team_members.json | sort | tee /tmp/team_members.txt + jq --raw-output .[] < /tmp/reviewers.json | sort | tee /tmp/reviewers.txt + + # Fetch requested team reviewers, and the name of the team + echo '${{ tojson(github.event.pull_request.requested_teams[*].slug) }}' | tee /tmp/team_reviewers.json + jq --raw-output .[] < /tmp/team_reviewers.json | sort | tee /tmp/team_reviewers.txt + echo '${{ env.TEAM }}' | tee /tmp/team.txt + + # If either a reviewer matches a team member, or a team matches our team, say "true" + if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then + echo "::set-output name=match::true" + elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then + echo "::set-output name=match::true" + else + echo "::set-output name=match::false" + fi + env: + TEAM: "product" + - uses: octokit/graphql-action@v2.x + id: add_to_project + if: steps.any_matching_reviewers.outputs.match == 'true' + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!, $contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.pull_request.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc4AAg6N" + TEAM: "product" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/CHANGES.md b/CHANGES.md index d2b81bdf1..c5c23b33f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,72 @@ +## Changes in 1.7.0 (2022-01-25) + +✨ Features + +- Message bubbles: Text message layout. ([#5208](https://github.com/vector-im/element-ios/issues/5208)) +- Message Bubbles: Layout for Media. ([#5209](https://github.com/vector-im/element-ios/issues/5209)) +- Message Bubbles: Support URL Previews. ([#5212](https://github.com/vector-im/element-ios/issues/5212)) +- Message Bubbles: Support reactions. ([#5214](https://github.com/vector-im/element-ios/issues/5214)) +- Added static location sharing sending and rendering support. ([#5298](https://github.com/vector-im/element-ios/issues/5298)) +- Message bubbles: Add settings and build flag. ([#5321](https://github.com/vector-im/element-ios/issues/5321)) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.21.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.21.0)). +- Using mutable room list fetch sort options after chaning them to be a structure. Adaptation to MXStore api changes. ([#4384](https://github.com/vector-im/element-ios/issues/4384)) +- Reduce grace period to report decryption failure ([#5345](https://github.com/vector-im/element-ios/issues/5345)) + +🐛 Bugfixes + +- Fixed home screen not updating properly on theme changes. ([#4208](https://github.com/vector-im/element-ios/issues/4208)) +- Fixes DTMF(dial tones) during voice calls. ([#5375](https://github.com/vector-im/element-ios/issues/5375)) +- Fix crash when uploading a video on iPad when "Confirm size when sending" is enabled in settings. ([#5399](https://github.com/vector-im/element-ios/issues/5399)) +- Fix BuildSetting to show/hide the "Invite Friends" button in the side SideMenu. ([#5402](https://github.com/vector-im/element-ios/issues/5402)) +- Add BuildSetting to hide social login in favour of the simple SSO button. ([#5404](https://github.com/vector-im/element-ios/issues/5404)) +- Fix grey spinner showing indefinitely over the home view after launch. ([#5407](https://github.com/vector-im/element-ios/issues/5407)) +- RecentsViewController: Update tab bar badges on section-only updates. ([#5421](https://github.com/vector-im/element-ios/issues/5421)) + +Others + +- Fix graphql warnings in issue workflow automation ([#5294](https://github.com/vector-im/element-ios/issues/5294)) + + +## Changes in 1.6.12 (2022-01-11) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.20.16](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.20.16)). +- Analytics: Replace Matomo with PostHog. ([#5035](https://github.com/vector-im/element-ios/issues/5035)) + +🐛 Bugfixes + +- RoomVC: Fix left room reason label memory management. ([#5311](https://github.com/vector-im/element-ios/issues/5311)) + + +## Changes in 1.6.11 (2021-12-14) + +✨ Features + +- Added support for creating, displaying and interacting with polls in the timeline. ([#5114](https://github.com/vector-im/element-ios/issues/5114)) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.20.15](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.20.15)). +- Room member details: Display user Matrix ID and make it copyable. ([#4568](https://github.com/vector-im/element-ios/issues/4568)) + +🐛 Bugfixes + +- Fix crash when trying to scroll the people's tab to the top. ([#5190](https://github.com/vector-im/element-ios/issues/5190)) + +🧱 Build + +- Fix SwiftGen only generating strings for MatrixKit. ([#5280](https://github.com/vector-im/element-ios/issues/5280)) + +Others + +- Update issue workflow automation for the Delight team ([#5285](https://github.com/vector-im/element-ios/issues/5285)) +- Update workflow to add automation for the new Message Bubbles board ([#5289](https://github.com/vector-im/element-ios/issues/5289)) + + ## Changes in 1.6.10 (2021-12-09) 🙌 Improvements diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index febe520e8..cc05f87f8 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.6.11 -CURRENT_PROJECT_VERSION = 1.6.11 +MARKETING_VERSION = 1.7.1 +CURRENT_PROJECT_VERSION = 1.7.1 diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 525870b51..07645cff8 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -22,13 +22,6 @@ import Foundation final class BuildSettings: NSObject { // MARK: - Bundle Settings - static var bundleDisplayName: String { - guard let bundleDisplayName = Bundle.app.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String else { - fatalError("CFBundleDisplayName should be defined") - } - return bundleDisplayName - } - static var applicationGroupIdentifier: String { guard let applicationGroupIdentifier = Bundle.app.object(forInfoDictionaryKey: "applicationGroupIdentifier") as? String else { fatalError("applicationGroupIdentifier should be defined") @@ -165,8 +158,20 @@ final class BuildSettings: NSObject { static let roomsAllowToJoinPublicRooms: Bool = true // MARK: - Analytics - static let analyticsServerUrl = URL(string: "https://piwik.riot.im/piwik.php") - static let analyticsAppId = "14" + #if DEBUG + /// Host to use for PostHog analytics during development. Set to nil to disable analytics in debug builds. + static let analyticsHost: String? = "https://posthog-poc.lab.element.dev" + /// Public key for submitting analytics during development. Set to nil to disable analytics in debug builds. + static let analyticsKey: String? = "rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8" + #else + /// Host to use for PostHog analytics. Set to nil to disable analytics. + static let analyticsHost: String? = "https://posthog.hss.element.io" + /// Public key for submitting analytics. Set to nil to disable analytics. + static let analyticsKey: String? = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO" + #endif + + /// The URL to open with more information about analytics terms. + static let analyticsTermsURL = URL(string: "https://element.io/cookie-policy")! // MARK: - Bug report @@ -207,8 +212,10 @@ final class BuildSettings: NSObject { static let allowInviteExernalUsers: Bool = true + // MARK: - Side Menu static let enableSideMenu: Bool = true - + static let sideMenuShowInviteFriends: Bool = true + /// Whether to read the `io.element.functional_members` state event and exclude any service members when computing a room's name and avatar. static let supportFunctionalMembers: Bool = true @@ -255,7 +262,6 @@ final class BuildSettings: NSObject { static let settingsScreenAllowBugReportingManually: Bool = true static let settingsScreenAllowDeactivatingAccount: Bool = true static let settingsScreenShowChangePassword:Bool = true - static let settingsScreenShowInviteFriends:Bool = true static let settingsScreenShowEnableStunServerFallback: Bool = true static let settingsScreenShowNotificationDecodedContentOption: Bool = true static let settingsScreenShowNsfwRoomsOption: Bool = true @@ -285,6 +291,13 @@ final class BuildSettings: NSObject { static let roomScreenAllowMediaLibraryAction: Bool = true static let roomScreenAllowStickerAction: Bool = true static let roomScreenAllowFilesAction: Bool = true + + // Timeline style + static let roomScreenAllowTimelineStyleConfiguration: Bool = false + static let roomScreenTimelineDefaultStyleIdentifier: RoomTimelineStyleIdentifier = .plain + static var isRoomScreenEnableMessageBubblesByDefault: Bool { + return self.roomScreenTimelineDefaultStyleIdentifier == .bubble + } /// Allow split view detail view stacking static let allowSplitViewDetailsScreenStacking: Bool = true @@ -336,6 +349,10 @@ final class BuildSettings: NSObject { static let authScreenShowPhoneNumber = true static let authScreenShowForgotPassword = true static let authScreenShowCustomServerOptions = true + static let authScreenShowSocialLoginSection = true + + // MARK: - Authentication Options + static let authEnableRefreshTokens = false // MARK: - Unified Search static let unifiedSearchScreenShowPublicDirectory = true @@ -352,4 +369,16 @@ final class BuildSettings: NSObject { return true } + + // MARK: - Location Sharing + + static let tileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx")! + + static var locationSharingEnabled: Bool { + guard #available(iOS 14, *) else { + return false + } + + return true + } } diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index 5edcacc37..9567a696d 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -74,8 +74,13 @@ class CommonConfiguration: NSObject, Configurable { // Disable key backup on common sdkOptions.enableKeyBackupWhenStartingMXCrypto = false + + // Pass threading option to the SDK + sdkOptions.enableThreads = RiotSettings.shared.enableThreads sdkOptions.clientPermalinkBaseUrl = BuildSettings.clientPermalinkBaseUrl + + sdkOptions.authEnableRefreshTokens = BuildSettings.authEnableRefreshTokens // Configure key provider delegate MXKeyProvider.sharedInstance().delegate = EncryptionKeyManager.shared } diff --git a/Gemfile.lock b/Gemfile.lock index a8674758c..610cbbadd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.4) + CFPropertyList (3.0.5) rexml - activesupport (6.1.4.1) + activesupport (6.1.4.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -17,17 +17,17 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.510.0) - aws-sdk-core (3.121.1) + aws-partitions (1.541.0) + aws-sdk-core (3.124.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) + aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.48.0) - aws-sdk-core (~> 3, >= 3.120.0) + aws-sdk-kms (1.52.0) + aws-sdk-core (~> 3, >= 3.122.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.103.0) - aws-sdk-core (~> 3, >= 3.120.0) + aws-sdk-s3 (1.109.0) + aws-sdk-core (~> 3, >= 3.122.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) @@ -84,9 +84,9 @@ GEM dotenv (2.7.6) emoji_regex (3.2.3) escape (0.0.4) - ethon (0.14.0) + ethon (0.15.0) ffi (>= 1.15.0) - excon (0.86.0) + excon (0.89.0) faraday (1.8.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -109,10 +109,10 @@ GEM faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) - faraday_middleware (1.1.0) + faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.5) - fastlane (2.195.0) + fastimage (2.2.6) + fastlane (2.199.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -161,7 +161,7 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.11.0) + google-apis-androidpublisher_v3 (0.14.0) google-apis-core (>= 0.4, < 2.a) google-apis-core (0.4.1) addressable (~> 2.5, >= 2.5.1) @@ -172,11 +172,11 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.7.0) + google-apis-iamcredentials_v1 (0.9.0) google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.5.0) + google-apis-playcustomapp_v1 (0.6.0) google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.8.0) + google-apis-storage_v1 (0.10.0) google-apis-core (>= 0.4, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) @@ -184,15 +184,15 @@ GEM google-cloud-env (1.5.0) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.34.1) - addressable (~> 2.5) + google-cloud-storage (1.35.0) + addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) google-apis-storage_v1 (~> 0.1) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.0.0) + googleauth (1.1.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -204,18 +204,18 @@ GEM http-cookie (1.0.4) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.8.10) + i18n (1.8.11) concurrent-ruby (~> 1.0) jmespath (1.4.0) - json (2.5.1) + json (2.6.1) jwt (2.3.0) memoist (0.16.2) - mime-types (3.3.1) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0901) + mime-types-data (3.2021.1115) mini_magick (4.11.0) - mini_mime (1.1.1) - minitest (5.14.4) + mini_mime (1.1.2) + minitest (5.15.0) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.0.0) @@ -224,7 +224,7 @@ GEM naturally (2.2.1) netrc (0.11.0) optparse (0.1.1) - os (1.1.1) + os (1.1.4) plist (3.6.0) public_suffix (4.0.6) rake (13.0.6) @@ -255,7 +255,7 @@ GEM terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) tty-spinner (0.9.3) @@ -285,7 +285,7 @@ GEM rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.4.2) + zeitwerk (2.5.1) PLATFORMS ruby @@ -299,4 +299,4 @@ DEPENDENCIES xcode-install BUNDLED WITH - 2.2.28 + 2.2.32 diff --git a/Podfile b/Podfile index f51a72727..286135afb 100644 --- a/Podfile +++ b/Podfile @@ -3,7 +3,7 @@ source 'https://cdn.cocoapods.org/' # Uncomment this line to define a global platform for your project platform :ios, '12.1' -# Use frameforks to allow usage of pod written in Swift (like PiwikTracker) +# Use frameworks to allow usage of pods written in Swift use_frameworks! # Different flavours of pods to MatrixSDK. Can be one of: @@ -13,7 +13,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.20.14' +$matrixSDKVersion = '= 0.21.0' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } @@ -67,8 +67,10 @@ abstract_target 'RiotPods' do pod 'KeychainAccess', '~> 4.2.2' pod 'WeakDictionary', '~> 2.0' - # Piwik for analytics - pod 'MatomoTracker', '~> 7.4.1' + # PostHog for analytics + pod 'PostHog', '~> 1.4.4' + pod 'AnalyticsEvents', :git => 'https://github.com/matrix-org/matrix-analytics-events.git', :branch => 'release/swift' + # pod 'AnalyticsEvents', :path => '../matrix-analytics-events/AnalyticsEvents.podspec' # Remove warnings from "bad" pods pod 'OLMKit', :inhibit_warnings => true @@ -85,6 +87,7 @@ abstract_target 'RiotPods' do import_SwiftUI_pods pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' + pod 'UICollectionViewRightAlignedLayout', '~> 0.0.3' pod 'KTCenterFlowLayout', '~> 1.3.1' pod 'ZXingObjC', '~> 3.6.5' pod 'FlowCommoniOS', '~> 1.12.0' @@ -93,7 +96,7 @@ abstract_target 'RiotPods' do pod 'SwiftJWT', '~> 3.6.200' pod 'SideMenu', '~> 6.5' pod 'DSWaveformImage', '~> 6.1.1' - pod 'ffmpeg-kit-ios-audio', '~> 4.5' + pod 'ffmpeg-kit-ios-audio', '4.5.1' pod 'FLEX', '~> 4.5.0', :configurations => ['Debug'] @@ -127,7 +130,6 @@ abstract_target 'RiotPods' do end - post_install do |installer| installer.pods_project.targets.each do |target| @@ -149,4 +151,4 @@ post_install do |installer| config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' end end -end +end \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index dcde25ebc..366d83a2e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -14,6 +14,7 @@ PODS: - AFNetworking/Serialization (4.0.1) - AFNetworking/UIKit (4.0.1): - AFNetworking/NSURLSession + - AnalyticsEvents (0.1.0) - BlueCryptor (1.0.32) - BlueECC (1.2.5) - BlueRSA (1.0.200) @@ -37,7 +38,7 @@ PODS: - DTFoundation/Core - DTFoundation/UIKit (1.7.18): - DTFoundation/Core - - ffmpeg-kit-ios-audio (4.5) + - ffmpeg-kit-ios-audio (4.5.1) - FLEX (4.5.0) - FlowCommoniOS (1.12.2) - GBDeviceInfo (6.6.0): @@ -56,19 +57,16 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatomoTracker (7.4.1): - - MatomoTracker/Core (= 7.4.1) - - MatomoTracker/Core (7.4.1) - - MatrixSDK (0.20.14): - - MatrixSDK/Core (= 0.20.14) - - MatrixSDK/Core (0.20.14): + - MatrixSDK (0.21.0): + - MatrixSDK/Core (= 0.21.0) + - MatrixSDK/Core (0.21.0): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - OLMKit (~> 3.2.5) - Realm (= 10.16.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.20.14): + - MatrixSDK/JingleCallStack (0.21.0): - JitsiMeetSDK (= 3.10.2) - MatrixSDK/Core - OLMKit (3.2.5): @@ -76,6 +74,7 @@ PODS: - OLMKit/olmcpp (= 3.2.5) - OLMKit/olmc (3.2.5) - OLMKit/olmcpp (3.2.5) + - PostHog (1.4.4) - ReadMoreTextView (3.0.1) - Realm (10.16.0): - Realm/Headers (= 10.16.0) @@ -103,11 +102,12 @@ PODS: - ZXingObjC/All (3.6.5) DEPENDENCIES: + - AnalyticsEvents (from `https://github.com/matrix-org/matrix-analytics-events.git`, branch `release/swift`) - DGCollectionViewLeftAlignFlowLayout (~> 1.0.4) - Down (~> 0.11.0) - DSWaveformImage (~> 6.1.1) - DTCoreText (~> 1.6.25) - - ffmpeg-kit-ios-audio (~> 4.5) + - ffmpeg-kit-ios-audio (= 4.5.1) - FLEX (~> 4.5.0) - FlowCommoniOS (~> 1.12.0) - GBDeviceInfo (~> 6.6.0) @@ -116,10 +116,10 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatomoTracker (~> 7.4.1) - - MatrixSDK (= 0.20.14) - - MatrixSDK/JingleCallStack (= 0.20.14) + - MatrixSDK (= 0.21.0) + - MatrixSDK/JingleCallStack (= 0.21.0) - OLMKit + - PostHog (~> 1.4.4) - ReadMoreTextView (~> 3.0.1) - Reusable (~> 4.1) - SideMenu (~> 6.5) @@ -157,9 +157,9 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging - - MatomoTracker - MatrixSDK - OLMKit + - PostHog - ReadMoreTextView - Realm - Reusable @@ -173,8 +173,19 @@ SPEC REPOS: - zxcvbn-ios - ZXingObjC +EXTERNAL SOURCES: + AnalyticsEvents: + :branch: release/swift + :git: https://github.com/matrix-org/matrix-analytics-events.git + +CHECKOUT OPTIONS: + AnalyticsEvents: + :commit: 8058dc6ec07ce0acfe5fdb19eb7e309b0c13845c + :git: https://github.com/matrix-org/matrix-analytics-events.git + SPEC CHECKSUMS: AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce + AnalyticsEvents: 333bf47d67dc628fadd29ce887b7ac93d8bd6e05 BlueCryptor: b0aee3d9b8f367b49b30de11cda90e1735571c24 BlueECC: 0d18e93347d3ec6d41416de21c1ffa4d4cd3c2cc BlueRSA: dfeef51db96bcc4edec654956c1581adbda4e6a3 @@ -183,7 +194,7 @@ SPEC CHECKSUMS: DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce DTFoundation: a53f8cda2489208cbc71c648be177f902ee17536 - ffmpeg-kit-ios-audio: 8c44d93054e1a9743a7014ec3dd26cd1ad8f2a59 + ffmpeg-kit-ios-audio: 662ce2064e56733ca7d8216705efbc38d9e1c3fe FLEX: e51461dd6f0bfb00643c262acdfea5d5d12c596b FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec @@ -198,9 +209,9 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb - MatrixSDK: 31a0d0f01c3be32c808adb78110292ed46e1b669 + MatrixSDK: cd98e3e4287b8a4f3a5bb47642beae80e8f62534 OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5 + PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: b6027801398f3743fc222f096faa85281b506e6c Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136 @@ -214,6 +225,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 12f459fece6a213a48e425c1839aad493f331c80 +PODFILE CHECKSUM: 39329dd448eb0ad10c396f84f854a18962ab1fc4 COCOAPODS: 1.11.2 diff --git a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme index 84ecb908a..a9bea1d96 100644 --- a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme +++ b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme @@ -4,7 +4,8 @@ version = "1.3"> + buildImplicitDependencies = "YES" + runPostActionsOnFailure = "NO"> - - - - - - - - - - - - - - - - - - - - - @@ -98,9 +77,6 @@ - - - @@ -543,10 +519,29 @@ + + + + + + + + + + + + + + + + + + + + - diff --git a/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsCheckmark.imageset/AnalyticsTick.pdf b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsCheckmark.imageset/AnalyticsTick.pdf new file mode 100644 index 000000000..9696208e3 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsCheckmark.imageset/AnalyticsTick.pdf @@ -0,0 +1,123 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.000000 -1.000000 cm +0.049479 0.742188 0.545395 scn +10.000000 23.000000 m +3.947715 23.000000 -1.000000 18.052284 -1.000000 12.000000 c +1.000000 12.000000 l +1.000000 16.947716 5.052285 21.000000 10.000000 21.000000 c +10.000000 23.000000 l +h +-1.000000 12.000000 m +-1.000000 5.947716 3.947715 1.000000 10.000000 1.000000 c +10.000000 3.000000 l +5.052285 3.000000 1.000000 7.052285 1.000000 12.000000 c +-1.000000 12.000000 l +h +10.000000 1.000000 m +16.052284 1.000000 21.000000 5.947716 21.000000 12.000000 c +19.000000 12.000000 l +19.000000 7.052285 14.947715 3.000000 10.000000 3.000000 c +10.000000 1.000000 l +h +21.000000 12.000000 m +21.000000 18.052284 16.052284 23.000000 10.000000 23.000000 c +10.000000 21.000000 l +14.947715 21.000000 19.000000 16.947716 19.000000 12.000000 c +21.000000 12.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 5.545532 4.655060 cm +0.049479 0.742188 0.545395 scn +0.717378 6.153159 m +0.332610 6.549356 -0.300487 6.558620 -0.696684 6.173852 c +-1.092881 5.789084 -1.102146 5.155987 -0.717378 4.759790 c +0.717378 6.153159 l +h +3.257576 2.102139 m +2.540198 1.405455 l +2.728505 1.211555 2.987285 1.102139 3.257576 1.102139 c +3.527867 1.102139 3.786646 1.211555 3.974954 1.405455 c +3.257576 2.102139 l +h +11.626469 9.284243 m +12.011237 9.680439 12.001972 10.313537 11.605776 10.698304 c +11.209579 11.083073 10.576482 11.073808 10.191713 10.677610 c +11.626469 9.284243 l +h +-0.717378 4.759790 m +2.540198 1.405455 l +3.974954 2.798823 l +0.717378 6.153159 l +-0.717378 4.759790 l +h +3.974954 1.405455 m +11.626469 9.284243 l +10.191713 10.677610 l +2.540198 2.798823 l +3.974954 1.405455 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1679 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 22.000000 22.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001769 00000 n +0000001792 00000 n +0000001965 00000 n +0000002039 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2098 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsCheckmark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsCheckmark.imageset/Contents.json new file mode 100644 index 000000000..146a290ce --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsCheckmark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "AnalyticsTick.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsLogo.imageset/AnalyticsLogo.pdf b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsLogo.imageset/AnalyticsLogo.pdf new file mode 100644 index 000000000..096c22d4b --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsLogo.imageset/AnalyticsLogo.pdf @@ -0,0 +1,641 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /ExtGState << /E2 << /ca 0.400000 >> + /E1 << /ca 0.400000 >> + >> >> + /BBox [ 0.000000 0.000000 119.000000 93.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 14.875000 -0.000015 cm +0.049479 0.742188 0.545395 scn +0.000000 44.521278 m +0.000000 69.109695 20.036579 89.042557 44.625000 89.042557 c +44.625000 89.042557 l +69.213417 89.042557 89.250000 69.109695 89.250000 44.521278 c +89.250000 44.521278 l +89.250000 19.932861 69.213417 0.000000 44.625000 0.000000 c +44.625000 0.000000 l +20.036579 0.000000 0.000000 19.932861 0.000000 44.521278 c +0.000000 44.521278 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 52.227402 46.594299 cm +1.000000 1.000000 1.000000 scn +0.000000 20.730219 m +0.000000 22.447567 1.395428 23.839752 3.116776 23.839752 c +14.592426 23.839752 23.895279 14.558517 23.895279 3.109533 c +23.895279 1.392185 22.499851 0.000000 20.778503 0.000000 c +19.057156 0.000000 17.661728 1.392185 17.661728 3.109533 c +17.661728 11.123821 11.149731 17.620686 3.116776 17.620686 c +1.395428 17.620686 0.000000 19.012871 0.000000 20.730219 c +h +f* +n +Q +q +-1.000000 0.000000 -0.000000 -1.000000 66.772202 42.448242 cm +1.000000 1.000000 1.000000 scn +0.000000 20.730206 m +0.000000 22.447552 1.395429 23.839737 3.116779 23.839737 c +14.592443 23.839737 23.895306 14.558502 23.895306 3.109520 c +23.895306 1.392172 22.499878 -0.000011 20.778526 -0.000011 c +19.057177 -0.000011 17.661749 1.392172 17.661749 3.109520 c +17.661749 11.123808 11.149744 17.620672 3.116779 17.620672 c +1.395429 17.620672 0.000000 19.012857 0.000000 20.730206 c +h +f* +n +Q +q +-0.000000 1.000000 -1.000000 -0.000000 57.366512 37.265598 cm +1.000000 1.000000 1.000000 scn +0.000000 20.722975 m +0.000000 22.444323 1.392186 23.839752 3.109534 23.839752 c +14.558520 23.839752 23.839758 14.536892 23.839758 3.061234 c +23.839758 1.339886 22.447573 -0.055544 20.730225 -0.055544 c +19.012875 -0.055544 17.620689 1.339886 17.620689 3.061234 c +17.620689 11.094195 11.123824 17.606197 3.109534 17.606197 c +1.392186 17.606197 0.000000 19.001625 0.000000 20.722975 c +h +f* +n +Q +q +-0.000000 -1.000000 1.000000 -0.000000 61.633194 51.777008 cm +1.000000 1.000000 1.000000 scn +0.000000 20.722975 m +0.000000 22.444324 1.392186 23.839752 3.109534 23.839752 c +14.558520 23.839752 23.839758 14.536893 23.839758 3.061237 c +23.839758 1.339888 22.447573 -0.055540 20.730225 -0.055540 c +19.012875 -0.055540 17.620689 1.339888 17.620689 3.061237 c +17.620689 11.094196 11.123824 17.606197 3.109534 17.606197 c +1.392186 17.606197 0.000000 19.001627 0.000000 20.722975 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.123089 16.695465 cm +1.000000 1.000000 1.000000 scn +10.448288 0.000008 m +11.057861 0.000008 11.560491 0.448122 11.646045 1.077618 c +12.576446 7.617959 13.464069 8.514189 19.795069 9.229038 c +20.436726 9.303724 20.917965 9.826525 20.917965 10.434681 c +20.917965 11.053506 20.447418 11.554968 19.805763 11.640323 c +13.506846 12.461866 12.694082 13.262072 11.646045 19.802414 c +11.539103 20.431910 11.057861 20.869354 10.448288 20.869354 c +9.849410 20.869354 9.346780 20.431910 9.250531 19.791744 c +8.330826 13.251402 7.443202 12.355172 1.112203 11.640323 c +0.470547 11.565637 0.000000 11.053506 0.000000 10.434681 c +0.000000 9.826525 0.459853 9.314394 1.112203 9.229038 c +7.411120 8.354148 8.191800 7.607290 9.250531 1.066948 c +9.368169 0.437452 9.860105 0.000008 10.448288 0.000008 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.123089 15.195465 cm +0.049479 0.742188 0.545395 scn +11.646045 2.577618 m +10.903506 2.683247 l +10.902877 2.678619 l +11.646045 2.577618 l +h +19.795069 10.729038 m +19.879219 9.983770 l +19.881781 9.984068 l +19.795069 10.729038 l +h +19.805763 13.140323 m +19.904661 13.883777 l +19.902761 13.884024 l +19.805763 13.140323 l +h +11.646045 21.302414 m +12.386630 21.421087 l +12.385450 21.428030 l +11.646045 21.302414 l +h +9.250531 21.291744 m +8.508834 21.403259 l +8.507838 21.396183 l +9.250531 21.291744 l +h +1.112203 13.140323 m +1.028052 13.885592 l +1.025491 13.885293 l +1.112203 13.140323 l +h +1.112203 10.729038 m +1.215387 11.471931 l +1.209505 11.472700 l +1.112203 10.729038 l +h +9.250531 2.566948 m +8.510169 2.447100 l +8.511623 2.438120 l +8.513294 2.429176 l +9.250531 2.566948 l +h +10.448288 0.750008 m +11.450765 0.750008 12.255557 1.493191 12.389213 2.476614 c +10.902877 2.678619 l +10.865425 2.403053 10.664957 2.250008 10.448288 2.250008 c +10.448288 0.750008 l +h +12.388570 2.471989 m +12.620602 4.103085 12.844038 5.334888 13.138243 6.288948 c +13.429995 7.235054 13.776924 7.858273 14.225985 8.307880 c +15.140809 9.223818 16.666159 9.620979 19.879219 9.983774 c +19.710920 11.474303 l +16.592981 11.122249 14.509018 10.713870 13.164680 9.367895 c +12.484159 8.686545 12.039045 7.814714 11.704848 6.730967 c +11.373105 5.655174 11.136688 4.322321 10.903521 2.683245 c +12.388570 2.471989 l +h +19.881781 9.984068 m +20.873766 10.099530 21.667965 10.918526 21.667965 11.934681 c +20.167965 11.934681 l +20.167965 11.734525 19.999683 11.507918 19.708359 11.474010 c +19.881781 9.984068 l +h +21.667965 11.934681 m +21.667965 12.966555 20.881077 13.753887 19.904659 13.883774 c +19.706867 12.396872 l +20.013762 12.356048 20.167965 12.140457 20.167965 11.934681 c +21.667965 11.934681 l +h +19.902761 13.884024 m +18.330524 14.089085 17.151228 14.287123 16.235826 14.561028 c +15.331247 14.831694 14.731587 15.163220 14.286853 15.607450 c +13.840208 16.053589 13.492528 16.670565 13.191763 17.611713 c +12.888441 18.560867 12.648228 19.788363 12.386598 21.421082 c +10.905493 21.183746 l +11.167881 19.546295 11.422276 18.221136 11.762950 17.155106 c +12.106180 16.081072 12.551881 15.220337 13.226795 14.546188 c +13.903622 13.870131 14.753702 13.438795 15.805837 13.123979 c +16.847151 12.812399 18.131544 12.602333 19.708765 12.396622 c +19.902761 13.884024 l +h +12.385450 21.428030 m +12.223795 22.379583 11.461336 23.119354 10.448288 23.119354 c +10.448288 21.619354 l +10.654386 21.619354 10.854410 21.484234 10.906639 21.176800 c +12.385450 21.428030 l +h +10.448288 23.119354 m +9.455997 23.119354 8.656921 22.387987 8.508867 21.403254 c +9.992196 21.180237 l +10.036638 21.475830 10.242823 21.619354 10.448288 21.619354 c +10.448288 23.119354 l +h +8.507838 21.396183 m +8.278477 19.765110 8.056973 18.533388 7.764218 17.579443 c +7.473907 16.633463 7.127907 16.010515 6.679636 15.561167 c +5.766263 14.645599 4.241331 14.248406 1.028053 13.885587 c +1.196352 12.395059 l +4.314074 12.747088 6.398453 13.155437 7.741569 14.501781 c +8.421542 15.183390 8.865580 16.055490 9.198210 17.139366 c +9.528394 18.215273 9.762733 19.548208 9.993224 21.187307 c +8.507838 21.396183 l +h +1.025491 13.885293 m +0.028613 13.769261 -0.750000 12.956783 -0.750000 11.934681 c +0.750000 11.934681 l +0.750000 12.150229 0.912482 12.362013 1.198914 12.395352 c +1.025491 13.885293 l +h +-0.750000 11.934681 m +-0.750000 10.922595 0.016880 10.115961 1.014900 9.985377 c +1.209505 11.472700 l +0.902826 11.512827 0.750000 11.730454 0.750000 11.934681 c +-0.750000 11.934681 l +h +1.009022 9.986170 m +2.582546 9.767614 3.760892 9.563175 4.675031 9.286510 c +5.578484 9.013078 6.174878 8.682938 6.616406 8.241908 c +7.059544 7.799271 7.404263 7.187467 7.703608 6.250257 c +8.005574 5.304842 8.245770 4.080437 8.510169 2.447100 c +9.990894 2.686794 l +9.725928 4.323629 9.471515 5.645210 9.132493 6.706642 c +8.790851 7.776280 8.348207 8.632186 7.676467 9.303166 c +7.003119 9.975756 6.157125 10.405144 5.109545 10.722197 c +4.072651 11.036015 2.791318 11.253017 1.215384 11.471908 c +1.009022 9.986170 l +h +8.513294 2.429176 m +8.688872 1.489628 9.455215 0.750008 10.448288 0.750008 c +10.448288 2.250008 l +10.264993 2.250008 10.047464 2.385277 9.987769 2.704720 c +8.513294 2.429176 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 72.573807 58.608093 cm +1.000000 1.000000 1.000000 scn +8.619835 -0.000011 m +9.122732 -0.000011 9.537402 0.373419 9.607985 0.897997 c +10.375565 6.348283 11.107853 7.095141 16.330927 7.690849 c +16.860292 7.753087 17.257317 8.188755 17.257317 8.695551 c +17.257317 9.211239 16.869114 9.629124 16.339748 9.700253 c +11.143145 10.384872 10.472614 11.051710 9.607985 16.501997 c +9.519756 17.026575 9.122732 17.391113 8.619835 17.391113 c +8.125761 17.391113 7.711091 17.026575 7.631686 16.493105 c +6.872929 11.042820 6.140640 10.295961 0.917567 9.700253 c +0.388201 9.638015 0.000000 9.211239 0.000000 8.695551 c +0.000000 8.188755 0.379379 7.761978 0.917567 7.690849 c +6.114172 6.961774 6.758233 6.339392 7.631686 0.889107 c +7.728737 0.364527 8.134583 -0.000011 8.619835 -0.000011 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 72.573807 57.608093 cm +0.049479 0.742188 0.545395 scn +9.607985 1.897997 m +9.112861 1.967726 l +9.112450 1.964672 l +9.607985 1.897997 l +h +16.330927 8.690849 m +16.387587 8.194067 l +16.389311 8.194269 l +16.330927 8.690849 l +h +16.339748 10.700253 m +16.406334 11.195801 l +16.405056 11.195970 l +16.339748 10.700253 l +h +9.607985 17.501997 m +10.101830 17.580339 l +10.101059 17.584925 l +9.607985 17.501997 l +h +7.631686 17.493105 m +7.137113 17.566721 l +7.136462 17.562048 l +7.631686 17.493105 l +h +0.917567 10.700253 m +0.860907 11.197035 l +0.859183 11.196833 l +0.917567 10.700253 l +h +0.917567 8.690849 m +0.987038 9.186015 l +0.983079 9.186539 l +0.917567 8.690849 l +h +7.631686 1.889107 m +7.137843 1.809963 l +7.140029 1.798147 l +7.631686 1.889107 l +h +8.619835 0.499989 m +9.388696 0.499989 10.001672 1.074373 10.103518 1.831324 c +9.112450 1.964672 l +9.073133 1.672462 8.856770 1.499989 8.619835 1.499989 c +8.619835 0.499989 l +h +10.103099 1.828268 m +10.294624 3.188222 10.480069 4.223436 10.725984 5.028956 c +10.970345 5.829387 11.264832 6.369568 11.654185 6.763333 c +12.442908 7.560994 13.744701 7.892640 16.387587 8.194070 c +16.274267 9.187629 l +13.694078 8.893350 12.018190 8.553713 10.943106 7.466446 c +10.400556 6.917747 10.041607 6.212054 9.769561 5.320940 c +9.499068 4.434915 9.305134 3.332915 9.112870 1.967726 c +10.103099 1.828268 l +h +16.389311 8.194269 m +17.155991 8.284410 17.757317 8.920855 17.757317 9.695551 c +16.757317 9.695551 l +16.757317 9.456655 16.564592 9.221766 16.272543 9.187428 c +16.389311 8.194269 l +h +17.757317 9.695551 m +17.757317 10.482604 17.162529 11.094193 16.406334 11.195800 c +16.273165 10.204706 l +16.575699 10.164056 16.757317 9.939874 16.757317 9.695551 c +17.757317 9.695551 l +h +16.405056 11.195970 m +15.107601 11.366901 14.126670 11.532858 13.361839 11.764020 c +12.604449 11.992933 12.090017 12.277006 11.704502 12.665974 c +11.317406 13.056538 11.022324 13.591100 10.770527 14.386979 c +10.517109 15.187984 10.317721 16.219311 10.101810 17.580338 c +9.114160 17.423656 l +9.330563 16.059540 9.539227 14.963654 9.817105 14.085340 c +10.096603 13.201900 10.456060 12.505035 10.994250 11.962027 c +11.534022 11.417421 12.215625 11.065775 13.072525 10.806786 c +13.921983 10.550046 14.973595 10.375915 16.274443 10.204536 c +16.405056 11.195970 l +h +10.101059 17.584925 m +9.977288 18.320837 9.395552 18.891113 8.619835 18.891113 c +8.619835 17.891113 l +8.849913 17.891113 9.062225 17.732313 9.114909 17.419067 c +10.101059 17.584925 l +h +8.619835 18.891113 m +7.859531 18.891113 7.250215 18.326427 7.137135 17.566717 c +8.126238 17.419493 l +8.171968 17.726725 8.391990 17.891113 8.619835 17.891113 c +8.619835 18.891113 l +h +7.136462 17.562048 m +6.947139 16.202108 6.763302 15.166948 6.518592 14.361504 c +6.275430 13.561155 5.981721 13.021152 5.592998 12.627558 c +4.805452 11.830145 3.503936 11.498478 0.860908 11.197033 c +0.974226 10.203474 l +3.554271 10.497736 5.230436 10.837353 6.304493 11.924868 c +6.846570 12.473737 7.204643 13.179608 7.475406 14.070805 c +7.744622 14.956905 7.936855 16.058958 8.126910 17.424164 c +7.136462 17.562048 l +h +0.859183 11.196833 m +0.089259 11.106312 -0.500000 10.475979 -0.500000 9.695551 c +0.500000 9.695551 l +0.500000 9.946500 0.687143 10.169718 0.975950 10.203673 c +0.859183 11.196833 l +h +-0.500000 9.695551 m +-0.500000 8.923503 0.079786 8.297226 0.852054 8.195160 c +0.983079 9.186539 l +0.678971 9.226731 0.500000 9.454006 0.500000 9.695551 c +-0.500000 9.695551 l +h +0.848098 8.195699 m +2.146421 8.013546 3.126409 7.842214 3.889960 7.608789 c +4.646159 7.377612 5.157835 7.094736 5.540681 6.708459 c +5.924912 6.320785 6.217544 5.790504 6.468155 4.997945 c +6.720429 4.200129 6.919805 3.171420 7.137986 1.809986 c +8.125387 1.968225 l +7.906841 3.331934 7.698164 4.424881 7.421624 5.299438 c +7.143422 6.179253 6.786478 6.872063 6.250936 7.412404 c +5.714010 7.954142 5.035716 8.304206 4.182313 8.565100 c +3.336262 8.823746 2.287016 9.003614 0.987036 9.186000 c +0.848098 8.195699 l +h +7.140029 1.798147 m +7.274719 1.070122 7.860904 0.499989 8.619835 0.499989 c +8.619835 1.499989 l +8.408263 1.499989 8.182755 1.658932 8.123343 1.980066 c +7.140029 1.798147 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 107.227104 75.129669 cm +0.049479 0.742188 0.545395 scn +5.619252 -0.000010 m +5.947090 -0.000010 6.217412 0.238985 6.263425 0.574716 c +6.763808 4.062898 7.241186 4.540888 10.646096 4.922141 c +10.991189 4.961974 11.250008 5.240800 11.250008 5.565150 c +11.250008 5.895190 10.996941 6.162637 10.651848 6.208159 c +7.264192 6.646316 6.827075 7.073092 6.263425 10.561275 c +6.205909 10.897006 5.947090 11.130310 5.619252 11.130310 c +5.297166 11.130310 5.026845 10.897006 4.975080 10.555585 c +4.480448 7.067402 4.003070 6.589413 0.598160 6.208159 c +0.253068 6.168327 0.000000 5.895190 0.000000 5.565150 c +0.000000 5.240800 0.247316 4.967664 0.598160 4.922141 c +3.985816 4.455533 4.405678 4.057208 4.975080 0.569025 c +5.038347 0.233294 5.302918 -0.000010 5.619252 -0.000010 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 103.008408 84.868683 cm +0.049479 0.742188 0.545395 scn +3.160831 -0.000001 m +3.345240 -0.000001 3.497297 0.134433 3.523178 0.323282 c +3.804644 2.285384 4.073170 2.554254 5.988433 2.768708 c +6.182547 2.791114 6.328133 2.947954 6.328133 3.130401 c +6.328133 3.316049 6.185782 3.466487 5.991668 3.492094 c +4.086111 3.738557 3.840232 3.978619 3.523178 5.940721 c +3.490826 6.129570 3.345240 6.260803 3.160831 6.260803 c +2.979658 6.260803 2.827601 6.129570 2.798484 5.937521 c +2.520254 3.975418 2.251728 3.706549 0.336465 3.492094 c +0.142351 3.469688 0.000000 3.316049 0.000000 3.130401 c +0.000000 2.947954 0.139115 2.794315 0.336465 2.768708 c +2.242023 2.506241 2.478195 2.282184 2.798484 0.320081 c +2.834072 0.131233 2.982893 -0.000001 3.160831 -0.000001 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 103.711479 69.564484 cm +0.049479 0.742188 0.545395 scn +3.863233 0.000004 m +4.088622 0.000004 4.274468 0.164312 4.306101 0.395127 c +4.650115 2.793253 4.978312 3.121871 7.319186 3.383983 c +7.556437 3.411368 7.734375 3.603061 7.734375 3.826052 c +7.734375 4.052955 7.560391 4.236824 7.323141 4.268121 c +4.994129 4.569354 4.693611 4.862762 4.306101 7.260888 c +4.266560 7.491703 4.088622 7.652100 3.863233 7.652100 c +3.641799 7.652100 3.455953 7.491703 3.420365 7.256976 c +3.080306 4.858850 2.752109 4.530232 0.411235 4.268121 c +0.173984 4.240736 0.000000 4.052955 0.000000 3.826052 c +0.000000 3.603061 0.170030 3.415280 0.411235 3.383983 c +2.740246 3.063190 3.028902 2.789341 3.420365 0.391215 c +3.463861 0.160400 3.645753 0.000004 3.863233 0.000004 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 -0.070938 78.607880 cm +0.049479 0.742188 0.545395 scn +5.268045 -0.000012 m +5.575393 -0.000012 5.828820 0.224045 5.871957 0.538792 c +6.341066 3.808963 6.788608 4.257079 9.980709 4.614503 c +10.304233 4.651846 10.546875 4.913247 10.546875 5.217325 c +10.546875 5.526737 10.309625 5.777468 9.986101 5.820146 c +6.810176 6.230918 6.400379 6.631021 5.871957 9.901192 c +5.818036 10.215940 5.575393 10.434662 5.268045 10.434662 c +4.966090 10.434662 4.712663 10.215940 4.664135 9.895857 c +4.200418 6.625686 3.752876 6.177571 0.560775 5.820146 c +0.237251 5.782803 0.000000 5.526737 0.000000 5.217325 c +0.000000 4.913247 0.231859 4.657181 0.560775 4.614503 c +3.736700 4.177058 4.130321 3.803629 4.664135 0.533457 c +4.723447 0.218710 4.971482 -0.000012 5.268045 -0.000012 c +h +f +n +Q +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 9.772858 88.346924 cm +0.049479 0.742188 0.545395 scn +2.458421 -0.000008 m +2.601850 -0.000008 2.720115 0.104552 2.740246 0.251434 c +2.959164 1.777514 3.168016 1.986634 4.657663 2.153433 c +4.808641 2.170859 4.921874 2.292846 4.921874 2.434749 c +4.921874 2.579142 4.811157 2.696150 4.660179 2.716066 c +3.178081 2.907759 2.986843 3.094474 2.740246 4.620554 c +2.715083 4.767436 2.601850 4.869507 2.458421 4.869507 c +2.317508 4.869507 2.199242 4.767436 2.176596 4.618064 c +1.960194 3.091985 1.751342 2.882864 0.261695 2.716066 c +0.110717 2.698639 0.000000 2.579142 0.000000 2.434749 c +0.000000 2.292846 0.108201 2.173349 0.261695 2.153433 c +1.743793 1.949291 1.927482 1.775024 2.176596 0.248944 c +2.204275 0.102062 2.320024 -0.000008 2.458421 -0.000008 c +h +f +n +Q +q +/E2 gs +1.000000 0.000000 -0.000000 1.000000 13.288479 82.086121 cm +0.049479 0.742188 0.545395 scn +2.458421 -0.000008 m +2.601850 -0.000008 2.720116 0.104552 2.740247 0.251434 c +2.959164 1.777514 3.168017 1.986634 4.657664 2.153433 c +4.808642 2.170859 4.921875 2.292846 4.921875 2.434749 c +4.921875 2.579142 4.811158 2.696150 4.660181 2.716066 c +3.178082 2.907759 2.986844 3.094474 2.740247 4.620554 c +2.715084 4.767436 2.601850 4.869507 2.458421 4.869507 c +2.317509 4.869507 2.199243 4.767436 2.176596 4.618064 c +1.960195 3.091985 1.751342 2.882864 0.261695 2.716066 c +0.110717 2.698639 0.000000 2.579142 0.000000 2.434749 c +0.000000 2.292846 0.108201 2.173349 0.261695 2.153433 c +1.743793 1.949291 1.927483 1.775024 2.176596 0.248944 c +2.204276 0.102062 2.320025 -0.000008 2.458421 -0.000008 c +h +f +n +Q + +endstream +endobj + +2 0 obj + 17245 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 119.000000 93.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 93.000000 m +119.000000 93.000000 l +119.000000 0.000000 l +0.000000 0.000000 l +0.000000 93.000000 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 234 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 119.000000 93.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Pages 9 0 R + /Type /Catalog + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000017630 00000 n +0000017654 00000 n +0000018137 00000 n +0000018159 00000 n +0000018457 00000 n +0000018559 00000 n +0000018580 00000 n +0000018754 00000 n +0000018828 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +18888 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsLogo.imageset/Contents.json b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsLogo.imageset/Contents.json new file mode 100644 index 000000000..7d49fc335 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/Analytics/AnalyticsLogo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "AnalyticsLogo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Authentication/Analytics/Contents.json b/Riot/Assets/Images.xcassets/Authentication/Analytics/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/Analytics/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1.imageset/Contents.json new file mode 100644 index 000000000..1c9083665 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1.imageset/OnboardingSplashScreenPage1.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1.imageset/OnboardingSplashScreenPage1.pdf new file mode 100644 index 000000000..2afeae294 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1.imageset/OnboardingSplashScreenPage1.pdf @@ -0,0 +1,1067 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 116.000000 142.000000 cm +0.857271 0.866762 0.880049 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +87.500000 13.000000 l +91.089851 13.000000 94.000000 10.089850 94.000000 6.500000 c +94.000000 6.500000 l +94.000000 2.910150 91.089851 0.000000 87.500000 0.000000 c +6.500002 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 142.000000 cm +1.000000 0.505882 0.176471 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +87.500000 13.000000 l +91.089851 13.000000 94.000000 10.089850 94.000000 6.500000 c +94.000000 6.500000 l +94.000000 2.910150 91.089851 0.000000 87.500000 0.000000 c +6.500002 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q + +endstream +endobj + +2 0 obj + 882 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 127.119141 85.000000 cm +0.857271 0.866762 0.880049 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 127.119141 85.000000 cm +0.454902 0.819608 0.172549 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 896 +endobj + +5 0 obj + << /Type /XObject + /Length 6 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 102.000000 207.000000 cm +0.857271 0.866762 0.880049 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 102.000000 207.000000 cm +0.360784 0.337255 0.960784 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q + +endstream +endobj + +6 0 obj + 898 +endobj + +7 0 obj + << /Type /XObject + /Length 8 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /XObject << /X2 1 0 R + /X3 3 0 R + /X1 5 0 R + >> + /ExtGState << /E6 << /ca 0.125000 >> + /E4 << /ca 0.250000 >> + /E2 << /ca 0.250000 >> + /E5 << /ca 0.500000 >> + /E3 << /ca 0.400000 >> + /E1 << /ca 0.300000 >> + >> + >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 51.000000 183.000000 cm +0.890196 0.909804 0.941176 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 51.000000 183.000000 cm +0.835294 0.847059 0.968627 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E2 gs +/X1 Do +Q +q +1.000000 0.000000 -0.000000 1.000000 102.000000 184.000000 cm +0.890196 0.909804 0.941176 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +141.500000 13.000000 l +145.089859 13.000000 148.000000 10.089850 148.000000 6.500000 c +148.000000 6.500000 l +148.000000 2.910150 145.089844 0.000000 141.500000 0.000000 c +6.499999 0.000000 l +2.910148 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 102.000000 184.000000 cm +0.890196 0.909804 0.941176 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +141.500000 13.000000 l +145.089859 13.000000 148.000000 10.089850 148.000000 6.500000 c +148.000000 6.500000 l +148.000000 2.910150 145.089844 0.000000 141.500000 0.000000 c +6.499999 0.000000 l +2.910148 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 60.000000 119.000000 cm +0.890196 0.909804 0.941176 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E3 gs +1.000000 0.000000 -0.000000 1.000000 60.000000 119.000000 cm +0.964706 0.878431 0.811765 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E4 gs +/X2 Do +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 121.000000 cm +0.890196 0.909804 0.941176 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +56.500000 13.000000 l +60.089848 13.000000 63.000000 10.089850 63.000000 6.500000 c +63.000000 6.500000 l +63.000000 2.910150 60.089851 0.000000 56.500000 0.000000 c +6.500001 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 121.000000 cm +0.890196 0.909804 0.941176 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +56.500000 13.000000 l +60.089848 13.000000 63.000000 10.089850 63.000000 6.500000 c +63.000000 6.500000 l +63.000000 2.910150 60.089851 0.000000 56.500000 0.000000 c +6.500001 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 189.000000 121.000000 cm +0.890196 0.909804 0.941176 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +57.500000 13.000000 l +61.089851 13.000000 64.000000 10.089850 64.000000 6.500000 c +64.000000 6.500000 l +64.000000 2.910150 61.089851 0.000000 57.500000 0.000000 c +6.500000 0.000000 l +2.910149 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 189.000000 121.000000 cm +0.890196 0.909804 0.941176 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +57.500000 13.000000 l +61.089851 13.000000 64.000000 10.089850 64.000000 6.500000 c +64.000000 6.500000 l +64.000000 2.910150 61.089851 0.000000 57.500000 0.000000 c +6.500000 0.000000 l +2.910149 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 70.000000 60.000000 cm +1.000000 1.000000 1.000000 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 70.000000 60.000000 cm +0.890196 0.909804 0.941176 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E5 gs +1.000000 0.000000 -0.000000 1.000000 70.000000 60.000000 cm +0.894118 0.964706 0.894118 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E6 gs +/X3 Do +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 72.000000 cm +1.000000 1.000000 1.000000 scn +0.000000 9.000000 m +0.000000 13.970562 4.029438 18.000000 9.000000 18.000000 c +23.000000 18.000000 l +27.970562 18.000000 32.000000 13.970562 32.000000 9.000000 c +32.000000 9.000000 l +32.000000 4.029437 27.970562 0.000000 23.000000 0.000000 c +9.000000 0.000000 l +4.029438 0.000000 0.000000 4.029437 0.000000 9.000000 c +0.000000 9.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 123.366211 77.722656 cm +0.552000 0.600000 0.648000 scn +4.910892 3.274046 m +4.910892 1.917940 3.811552 0.818600 2.455446 0.818600 c +1.099341 0.818600 0.000000 1.917940 0.000000 3.274046 c +0.000000 4.630152 1.099341 5.729492 2.455446 5.729492 c +3.811552 5.729492 4.910892 4.630152 4.910892 3.274046 c +h +11.458750 2.455564 m +11.458750 1.099458 10.359408 0.000118 9.003304 0.000118 c +7.647198 0.000118 6.547857 1.099458 6.547857 2.455564 c +6.547857 3.811669 7.647198 4.911010 9.003304 4.911010 c +10.359408 4.911010 11.458750 3.811669 11.458750 2.455564 c +h +15.551160 0.000118 m +16.907265 0.000118 18.006607 1.099458 18.006607 2.455564 c +18.006607 3.811669 16.907265 4.911010 15.551160 4.911010 c +14.195054 4.911010 13.095714 3.811669 13.095714 2.455564 c +13.095714 1.099458 14.195054 0.000118 15.551160 0.000118 c +h +f* +n +Q + +endstream +endobj + +8 0 obj + 6594 +endobj + +9 0 obj + << /Length 10 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch 1.000000 exch 1.000000 exch dup 0.885417 gt { exch pop exch pop exch pop dup 0.885417 sub 0.000000 mul 1.000000 add exch dup 0.885417 sub 0.000000 mul 1.000000 add exch dup 0.885417 sub 0.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } +endstream +endobj + +10 0 obj + 336 +endobj + +11 0 obj + << /Length 12 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch 1.000000 exch 1.000000 exch dup 0.703125 gt { exch pop exch pop exch pop dup 0.703125 sub 0.000000 mul 1.000000 add exch dup 0.703125 sub 0.000000 mul 1.000000 add exch dup 0.703125 sub 0.000000 mul 1.000000 add exch } if dup 0.927083 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } +endstream +endobj + +12 0 obj + 336 +endobj + +13 0 obj + << /Length 14 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.211765 exch 0.545098 exch 0.839216 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 4.324034 mul 0.211765 add exch dup 0.000000 sub -0.215126 mul 0.545098 add exch dup 0.000000 sub -3.635630 mul 0.839216 add exch } if dup 0.182292 gt { exch pop exch pop exch pop dup 0.182292 sub -1.689030 mul 1.000000 add exch dup 0.182292 sub -1.424484 mul 0.505882 add exch dup 0.182292 sub 2.503021 mul 0.176471 add exch } if dup 0.375000 gt { exch pop exch pop exch pop dup 0.375000 sub -3.348607 mul 0.674510 add exch dup 0.375000 sub 2.377709 mul 0.231373 add exch dup 0.375000 sub -0.772755 mul 0.658824 add exch } if dup 0.572917 gt { exch pop exch pop exch pop dup 0.572917 sub 0.810860 mul 0.011765 add exch dup 0.572917 sub 0.289593 mul 0.701961 add exch dup 0.572917 sub 1.312820 mul 0.505882 add exch } if dup 0.776042 gt { exch pop exch pop exch pop dup 0.776042 sub 3.239398 mul 0.176471 add exch dup 0.776042 sub -2.013680 mul 0.760784 add exch dup 0.776042 sub -1.313270 mul 0.772549 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.901961 exch 0.309804 exch 0.478431 exch } if pop } +endstream +endobj + +14 0 obj + 1120 +endobj + +15 0 obj + << /Length 16 0 R + /Range [ 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.000000 exch dup 0.885417 gt { exch pop dup 0.885417 sub 8.727274 mul 0.000000 add exch } if dup 1.000000 gt { exch pop 1.000000 exch } if pop } +endstream +endobj + +16 0 obj + 148 +endobj + +17 0 obj + << /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources << /Pattern << /P1 << /Matrix [ -127.403831 248.798080 -248.798080 -127.403831 347.956787 70.673119 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceGray + /Function 15 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /Subtype /Form + /Length 18 0 R + /Group << /Type /Group + /S /Transparency + /CS /DeviceGray + >> + /Type /XObject + >> +stream +/DeviceGray CS +/DeviceGray cs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +/Pattern cs +/P1 scn +f +n + +endstream +endobj + +18 0 obj + 396 +endobj + +19 0 obj + << /Length 20 0 R + /Range [ 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch dup 0.703125 gt { exch pop dup 0.703125 sub -4.465117 mul 1.000000 add exch } if dup 0.927083 gt { exch pop 0.000000 exch } if pop } +endstream +endobj + +20 0 obj + 149 +endobj + +21 0 obj + << /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources << /Pattern << /P1 << /Matrix [ 15.144229 -277.163422 277.163422 15.144229 -3.725922 294.591370 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceGray + /Function 19 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /Subtype /Form + /Length 22 0 R + /Group << /Type /Group + /S /Transparency + /CS /DeviceGray + >> + /Type /XObject + >> +stream +/DeviceGray CS +/DeviceGray cs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +/Pattern cs +/P1 scn +f +n + +endstream +endobj + +22 0 obj + 396 +endobj + +23 0 obj + << /Type /XObject + /Length 24 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /Pattern << /P3 << /Matrix [ -127.403831 248.798080 -248.798080 -127.403831 347.956787 70.673119 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 9 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + /P2 << /Matrix [ 15.144229 -277.163422 277.163422 15.144229 -3.725922 294.591370 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 11 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + /P1 << /Matrix [ 190.384613 -179.807693 179.807693 190.384613 -122.380447 53.989655 ] + /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] + /ColorSpace /DeviceRGB + /Function 13 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 2 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + >> + /ExtGState << /E4 << /SMask << /Type /Mask + /G 17 0 R + /S /Luminosity + >> + /Type /ExtGState + >> + /E2 << /ca 0.800000 >> + /E3 << /SMask << /Type /Mask + /G 21 0 R + /S /Luminosity + >> + /Type /ExtGState + >> + /E1 << /ca 0.100000 >> + >> + >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P1 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E2 gs +/E3 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P2 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E4 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P3 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q + +endstream +endobj + +24 0 obj + 1166 +endobj + +25 0 obj + << /Length 26 0 R + /Range [ 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.000000 exch dup 0.885417 gt { exch pop dup 0.885417 sub 8.727274 mul 0.000000 add exch } if dup 1.000000 gt { exch pop 1.000000 exch } if pop } +endstream +endobj + +26 0 obj + 148 +endobj + +27 0 obj + << /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources << /Pattern << /P1 << /Matrix [ -127.403831 248.798080 -248.798080 -127.403831 347.956787 70.673119 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceGray + /Function 25 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /Subtype /Form + /Length 28 0 R + /Group << /Type /Group + /S /Transparency + /CS /DeviceGray + >> + /Type /XObject + >> +stream +/DeviceGray CS +/DeviceGray cs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +/Pattern cs +/P1 scn +f +n + +endstream +endobj + +28 0 obj + 396 +endobj + +29 0 obj + << /Length 30 0 R + /Range [ 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch dup 0.703125 gt { exch pop dup 0.703125 sub -4.465117 mul 1.000000 add exch } if dup 0.927083 gt { exch pop 0.000000 exch } if pop } +endstream +endobj + +30 0 obj + 149 +endobj + +31 0 obj + << /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources << /Pattern << /P1 << /Matrix [ 15.144229 -277.163422 277.163422 15.144229 -3.725922 294.591370 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceGray + /Function 29 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /Subtype /Form + /Length 32 0 R + /Group << /Type /Group + /S /Transparency + /CS /DeviceGray + >> + /Type /XObject + >> +stream +/DeviceGray CS +/DeviceGray cs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +/Pattern cs +/P1 scn +f +n + +endstream +endobj + +32 0 obj + 396 +endobj + +33 0 obj + << /Length 34 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch 1.000000 exch 1.000000 exch dup 0.885417 gt { exch pop exch pop exch pop dup 0.885417 sub 0.000000 mul 1.000000 add exch dup 0.885417 sub 0.000000 mul 1.000000 add exch dup 0.885417 sub 0.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } +endstream +endobj + +34 0 obj + 336 +endobj + +35 0 obj + << /Length 36 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch 1.000000 exch 1.000000 exch dup 0.703125 gt { exch pop exch pop exch pop dup 0.703125 sub 0.000000 mul 1.000000 add exch dup 0.703125 sub 0.000000 mul 1.000000 add exch dup 0.703125 sub 0.000000 mul 1.000000 add exch } if dup 0.927083 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } +endstream +endobj + +36 0 obj + 336 +endobj + +37 0 obj + << /Length 38 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.211765 exch 0.545098 exch 0.839216 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 4.324034 mul 0.211765 add exch dup 0.000000 sub -0.215126 mul 0.545098 add exch dup 0.000000 sub -3.635630 mul 0.839216 add exch } if dup 0.182292 gt { exch pop exch pop exch pop dup 0.182292 sub -1.689030 mul 1.000000 add exch dup 0.182292 sub -1.424484 mul 0.505882 add exch dup 0.182292 sub 2.503021 mul 0.176471 add exch } if dup 0.375000 gt { exch pop exch pop exch pop dup 0.375000 sub -3.348607 mul 0.674510 add exch dup 0.375000 sub 2.377709 mul 0.231373 add exch dup 0.375000 sub -0.772755 mul 0.658824 add exch } if dup 0.572917 gt { exch pop exch pop exch pop dup 0.572917 sub 0.810860 mul 0.011765 add exch dup 0.572917 sub 0.289593 mul 0.701961 add exch dup 0.572917 sub 1.312820 mul 0.505882 add exch } if dup 0.776042 gt { exch pop exch pop exch pop dup 0.776042 sub 3.239398 mul 0.176471 add exch dup 0.776042 sub -2.013680 mul 0.760784 add exch dup 0.776042 sub -1.313270 mul 0.772549 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.901961 exch 0.309804 exch 0.478431 exch } if pop } +endstream +endobj + +38 0 obj + 1120 +endobj + +39 0 obj + << /XObject << /X1 7 0 R >> + /ExtGState << /E5 << /SMask << /Type /Mask + /G 23 0 R + /S /Alpha + >> + /Type /ExtGState + >> + /E4 << /SMask << /Type /Mask + /G 27 0 R + /S /Luminosity + >> + /Type /ExtGState + >> + /E2 << /ca 0.800000 >> + /E3 << /SMask << /Type /Mask + /G 31 0 R + /S /Luminosity + >> + /Type /ExtGState + >> + /E1 << /ca 0.100000 >> + >> + /Pattern << /P3 << /Matrix [ -127.403831 248.798080 -248.798080 -127.403831 347.956787 70.673119 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 33 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + /P2 << /Matrix [ 15.144229 -277.163422 277.163422 15.144229 -3.725922 294.591370 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 35 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + /P1 << /Matrix [ 190.384613 -179.807693 179.807693 190.384613 -122.380447 53.989655 ] + /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] + /ColorSpace /DeviceRGB + /Function 37 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 2 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + >> + >> +endobj + +40 0 obj + << /Length 41 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +0.956863 0.964706 0.980392 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P1 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E2 gs +/E3 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P2 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E4 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P3 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E5 gs +/X1 Do +Q + +endstream +endobj + +41 0 obj + 1565 +endobj + +42 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources 39 0 R + /Contents 40 0 R + /Parent 43 0 R + >> +endobj + +43 0 obj + << /Kids [ 42 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +44 0 obj + << /Pages 43 0 R + /Type /Catalog + >> +endobj + +xref +0 45 +0000000000 65535 f +0000000010 00000 n +0000001142 00000 n +0000001164 00000 n +0000002310 00000 n +0000002332 00000 n +0000003480 00000 n +0000003502 00000 n +0000010866 00000 n +0000010889 00000 n +0000011410 00000 n +0000011433 00000 n +0000011955 00000 n +0000011978 00000 n +0000013284 00000 n +0000013308 00000 n +0000013606 00000 n +0000013629 00000 n +0000015078 00000 n +0000015101 00000 n +0000015400 00000 n +0000015423 00000 n +0000016868 00000 n +0000016891 00000 n +0000021481 00000 n +0000021505 00000 n +0000021803 00000 n +0000021826 00000 n +0000023275 00000 n +0000023298 00000 n +0000023597 00000 n +0000023620 00000 n +0000025065 00000 n +0000025088 00000 n +0000025610 00000 n +0000025633 00000 n +0000026155 00000 n +0000026178 00000 n +0000027484 00000 n +0000027508 00000 n +0000030295 00000 n +0000031918 00000 n +0000031942 00000 n +0000032121 00000 n +0000032197 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 44 0 R + /Size 45 +>> +startxref +32258 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1Dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1Dark.imageset/Contents.json new file mode 100644 index 000000000..5bb2cdab2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1Dark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage1-Dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1Dark.imageset/OnboardingSplashScreenPage1-Dark.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1Dark.imageset/OnboardingSplashScreenPage1-Dark.pdf new file mode 100644 index 000000000..be0081499 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage1Dark.imageset/OnboardingSplashScreenPage1-Dark.pdf @@ -0,0 +1,872 @@ +%PDF-1.7 + +1 0 obj + << /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 116.000000 142.000000 cm +0.083333 0.083333 0.083333 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +87.500000 13.000000 l +91.089851 13.000000 94.000000 10.089850 94.000000 6.500000 c +94.000000 6.500000 l +94.000000 2.910150 91.089851 0.000000 87.500000 0.000000 c +6.500002 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 142.000000 cm +1.000000 0.505882 0.176471 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +87.500000 13.000000 l +91.089851 13.000000 94.000000 10.089850 94.000000 6.500000 c +94.000000 6.500000 l +94.000000 2.910150 91.089851 0.000000 87.500000 0.000000 c +6.500002 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q + +endstream +endobj + +2 0 obj + 882 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 127.119080 85.000000 cm +0.083333 0.083333 0.083333 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 127.119080 85.000000 cm +0.454902 0.819608 0.172549 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 896 +endobj + +5 0 obj + << /Type /XObject + /Length 6 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 102.000000 207.000000 cm +0.083333 0.083333 0.083333 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 102.000000 207.000000 cm +0.360784 0.337255 0.960784 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +119.500000 13.000000 l +123.089851 13.000000 126.000000 10.089850 126.000000 6.500000 c +126.000000 6.500000 l +126.000000 2.910150 123.089851 0.000000 119.500000 0.000000 c +6.499998 0.000000 l +2.910147 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q + +endstream +endobj + +6 0 obj + 898 +endobj + +7 0 obj + << /Type /XObject + /Length 8 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /XObject << /X2 1 0 R + /X3 3 0 R + /X1 5 0 R + >> + /ExtGState << /E6 << /ca 0.400000 >> + /E4 << /ca 0.500000 >> + /E2 << /ca 0.500000 >> + /E5 << /ca 0.700000 >> + /E3 << /ca 0.700000 >> + /E1 << /ca 0.700000 >> + >> + >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 51.000000 183.000000 cm +0.054167 0.054167 0.054167 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 51.000000 183.000000 cm +0.835294 0.847059 0.968627 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E2 gs +/X1 Do +Q +q +1.000000 0.000000 -0.000000 1.000000 102.000000 184.000000 cm +0.054167 0.054167 0.054167 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +141.500000 13.000000 l +145.089859 13.000000 148.000000 10.089850 148.000000 6.500000 c +148.000000 6.500000 l +148.000000 2.910150 145.089844 0.000000 141.500000 0.000000 c +6.499999 0.000000 l +2.910148 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 102.000000 184.000000 cm +0.556863 0.600000 0.643137 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +141.500000 13.000000 l +145.089859 13.000000 148.000000 10.089850 148.000000 6.500000 c +148.000000 6.500000 l +148.000000 2.910150 145.089844 0.000000 141.500000 0.000000 c +6.499999 0.000000 l +2.910148 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 60.000000 119.000000 cm +0.054167 0.054167 0.054167 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E3 gs +1.000000 0.000000 -0.000000 1.000000 60.000000 119.000000 cm +0.964706 0.878431 0.811765 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E4 gs +/X2 Do +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 121.000000 cm +0.054167 0.054167 0.054167 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +56.500000 13.000000 l +60.089848 13.000000 63.000000 10.089850 63.000000 6.500000 c +63.000000 6.500000 l +63.000000 2.910150 60.089851 0.000000 56.500000 0.000000 c +6.500001 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 116.000000 121.000000 cm +0.556863 0.600000 0.643137 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +56.500000 13.000000 l +60.089848 13.000000 63.000000 10.089850 63.000000 6.500000 c +63.000000 6.500000 l +63.000000 2.910150 60.089851 0.000000 56.500000 0.000000 c +6.500001 0.000000 l +2.910151 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 189.000000 121.000000 cm +0.054167 0.054167 0.054167 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +57.500000 13.000000 l +61.089851 13.000000 64.000000 10.089850 64.000000 6.500000 c +64.000000 6.500000 l +64.000000 2.910150 61.089851 0.000000 57.500000 0.000000 c +6.500000 0.000000 l +2.910149 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 189.000000 121.000000 cm +0.556863 0.600000 0.643137 scn +0.000000 6.500000 m +0.000000 10.089850 2.910149 13.000000 6.500000 13.000000 c +57.500000 13.000000 l +61.089851 13.000000 64.000000 10.089850 64.000000 6.500000 c +64.000000 6.500000 l +64.000000 2.910150 61.089851 0.000000 57.500000 0.000000 c +6.500000 0.000000 l +2.910149 0.000000 0.000000 2.910150 0.000000 6.500000 c +0.000000 6.500000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 69.999939 60.000000 cm +1.000000 1.000000 1.000000 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 69.999939 60.000000 cm +0.054167 0.054167 0.054167 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E5 gs +1.000000 0.000000 -0.000000 1.000000 69.999939 60.000000 cm +0.894118 0.964706 0.894118 scn +38.000000 19.000000 m +38.000000 8.506590 29.493410 0.000000 19.000000 0.000000 c +8.506590 0.000000 0.000000 8.506590 0.000000 19.000000 c +0.000000 29.493410 8.506590 38.000000 19.000000 38.000000 c +29.493410 38.000000 38.000000 29.493410 38.000000 19.000000 c +h +f +n +Q +q +/E6 gs +/X3 Do +Q +q +1.000000 0.000000 -0.000000 1.000000 115.999939 72.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 9.000000 m +0.000000 13.970562 4.029438 18.000000 9.000000 18.000000 c +23.000000 18.000000 l +27.970562 18.000000 32.000000 13.970562 32.000000 9.000000 c +32.000000 9.000000 l +32.000000 4.029437 27.970562 0.000000 23.000000 0.000000 c +9.000000 0.000000 l +4.029438 0.000000 0.000000 4.029437 0.000000 9.000000 c +0.000000 9.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 123.366089 77.722778 cm +0.557143 0.600000 0.642857 scn +4.910892 3.273924 m +4.910892 1.917818 3.811552 0.818478 2.455446 0.818478 c +1.099341 0.818478 0.000000 1.917818 0.000000 3.273924 c +0.000000 4.630030 1.099341 5.729370 2.455446 5.729370 c +3.811552 5.729370 4.910892 4.630030 4.910892 3.273924 c +h +11.458750 2.455442 m +11.458750 1.099336 10.359408 -0.000004 9.003304 -0.000004 c +7.647198 -0.000004 6.547857 1.099336 6.547857 2.455442 c +6.547857 3.811547 7.647198 4.910888 9.003304 4.910888 c +10.359408 4.910888 11.458750 3.811547 11.458750 2.455442 c +h +15.551160 -0.000004 m +16.907265 -0.000004 18.006607 1.099336 18.006607 2.455442 c +18.006607 3.811547 16.907265 4.910888 15.551160 4.910888 c +14.195054 4.910888 13.095714 3.811547 13.095714 2.455442 c +13.095714 1.099336 14.195054 -0.000004 15.551160 -0.000004 c +h +f* +n +Q + +endstream +endobj + +8 0 obj + 6601 +endobj + +9 0 obj + << /Length 10 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.890196 exch 0.909804 exch 0.941176 exch dup 0.885417 gt { exch pop exch pop exch pop dup 0.885417 sub -1.163636 mul 0.890196 add exch dup 0.885417 sub -1.163636 mul 0.909804 add exch dup 0.885417 sub -1.197861 mul 0.941176 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.756863 exch 0.776471 exch 0.803922 exch } if pop } +endstream +endobj + +10 0 obj + 339 +endobj + +11 0 obj + << /Length 12 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.560784 exch 0.592157 exch 0.639216 exch dup 0.436121 gt { exch pop exch pop exch pop dup 0.436121 sub -1.132947 mul 0.560784 add exch dup 0.436121 sub -1.040270 mul 0.592157 add exch dup 0.436121 sub -1.080743 mul 0.639216 add exch } if dup 0.797508 gt { exch pop exch pop exch pop 0.151351 exch 0.216216 exch 0.248649 exch } if pop } +endstream +endobj + +12 0 obj + 339 +endobj + +13 0 obj + << /Length 14 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.211765 exch 0.545098 exch 0.839216 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 4.324034 mul 0.211765 add exch dup 0.000000 sub -0.215126 mul 0.545098 add exch dup 0.000000 sub -3.635630 mul 0.839216 add exch } if dup 0.182292 gt { exch pop exch pop exch pop dup 0.182292 sub -0.472174 mul 1.000000 add exch dup 0.182292 sub -0.944349 mul 0.505882 add exch dup 0.182292 sub 1.454296 mul 0.176471 add exch } if dup 0.389925 gt { exch pop exch pop exch pop dup 0.389925 sub -4.864687 mul 0.901961 add exch dup 0.389925 sub 2.143034 mul 0.309804 add exch dup 0.389925 sub 0.150013 mul 0.478431 add exch } if dup 0.572917 gt { exch pop exch pop exch pop dup 0.572917 sub 0.810860 mul 0.011765 add exch dup 0.572917 sub 0.289593 mul 0.701961 add exch dup 0.572917 sub 1.312820 mul 0.505882 add exch } if dup 0.776042 gt { exch pop exch pop exch pop dup 0.776042 sub 2.223803 mul 0.176471 add exch dup 0.776042 sub -2.363885 mul 0.760784 add exch dup 0.776042 sub -0.507798 mul 0.772549 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.674510 exch 0.231373 exch 0.658824 exch } if pop } +endstream +endobj + +14 0 obj + 1119 +endobj + +15 0 obj + << /Length 16 0 R + /Range [ 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 0.000000 exch dup 0.885417 gt { exch pop dup 0.885417 sub 8.727274 mul 0.000000 add exch } if dup 1.000000 gt { exch pop 1.000000 exch } if pop } +endstream +endobj + +16 0 obj + 148 +endobj + +17 0 obj + << /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources << /Pattern << /P1 << /Matrix [ -127.403831 248.798080 -248.798080 -127.403831 347.956726 70.673119 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceGray + /Function 15 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /Subtype /Form + /Length 18 0 R + /Group << /Type /Group + /S /Transparency + /CS /DeviceGray + >> + /Type /XObject + >> +stream +/DeviceGray CS +/DeviceGray cs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +/Pattern cs +/P1 scn +f +n + +endstream +endobj + +18 0 obj + 396 +endobj + +19 0 obj + << /Type /XObject + /Length 20 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /Pattern << /P3 << /Matrix [ -127.403831 248.798080 -248.798080 -127.403831 347.956726 70.673119 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 9 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + /P2 << /Matrix [ 105.999977 -284.000061 361.623688 134.972168 -115.811859 256.513977 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 11 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + /P1 << /Matrix [ 256.865387 -254.788467 254.788467 256.865387 -268.954285 56.983734 ] + /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] + /ColorSpace /DeviceRGB + /Function 13 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 2 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> + >> + /ExtGState << /E4 << /SMask << /Type /Mask + /G 17 0 R + /S /Luminosity + >> + /Type /ExtGState + >> + /E2 << /ca 0.500000 >> + /E3 << /ca 0.400000 >> + /E1 << /ca 0.200000 >> + >> + >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +q +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +0.098039 0.113725 0.129412 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P1 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E2 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P2 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q +q +/E3 gs +/E4 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P3 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q + +endstream +endobj + +20 0 obj + 1519 +endobj + +21 0 obj + << /Length 22 0 R + /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch 1.000000 exch 1.000000 exch dup 0.258514 gt { exch pop exch pop exch pop dup 0.258514 sub 0.000000 mul 1.000000 add exch dup 0.258514 sub 0.000000 mul 1.000000 add exch dup 0.258514 sub 0.000000 mul 1.000000 add exch } if dup 0.942462 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } +endstream +endobj + +22 0 obj + 336 +endobj + +23 0 obj + << /Length 24 0 R + /Range [ 0.000000 1.000000 ] + /Domain [ 0.000000 1.000000 ] + /FunctionType 4 + >> +stream +{ 1.000000 exch dup 0.258514 gt { exch pop dup 0.258514 sub -1.462099 mul 1.000000 add exch } if dup 0.942462 gt { exch pop 0.000000 exch } if pop } +endstream +endobj + +24 0 obj + 149 +endobj + +25 0 obj + << /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources << /Pattern << /P1 << /Matrix [ 18.000000 -296.999969 296.999969 18.000000 -16.499969 313.000000 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceGray + /Function 23 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> >> + /Subtype /Form + /Length 26 0 R + /Group << /Type /Group + /S /Transparency + /CS /DeviceGray + >> + /Type /XObject + >> +stream +/DeviceGray CS +/DeviceGray cs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +/Pattern cs +/P1 scn +f +n + +endstream +endobj + +26 0 obj + 396 +endobj + +27 0 obj + << /Type /XObject + /Length 28 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << /Pattern << /P1 << /Matrix [ 18.000000 -296.999969 296.999969 18.000000 -16.499969 313.000000 ] + /Shading << /Coords [ 0.500000 0.500000 0.000000 0.500000 0.500000 0.500000 ] + /ColorSpace /DeviceRGB + /Function 21 0 R + /Domain [ 0.000000 1.000000 ] + /ShadingType 3 + /Extend [ true true ] + >> + /PatternType 2 + /Type /Pattern + >> >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 25 0 R + /S /Luminosity + >> + /Type /ExtGState + >> >> + >> + /BBox [ 0.000000 0.000000 300.000000 300.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +1.000000 0.000000 -0.000000 1.000000 25.000000 25.000000 cm +/Pattern cs +/P1 scn +250.000000 125.000000 m +250.000000 55.964401 194.035599 0.000000 125.000000 0.000000 c +55.964405 0.000000 0.000000 55.964401 0.000000 125.000000 c +0.000000 194.035599 55.964405 250.000000 125.000000 250.000000 c +194.035599 250.000000 250.000000 194.035599 250.000000 125.000000 c +h +f +n +Q + +endstream +endobj + +28 0 obj + 405 +endobj + +29 0 obj + << /XObject << /X2 7 0 R + /X1 19 0 R + >> + /ExtGState << /E2 << /SMask << /Type /Mask + /G 27 0 R + /S /Alpha + >> + /Type /ExtGState + >> + /E1 << /ca 0.600000 >> + >> + >> +endobj + +30 0 obj + << /Length 31 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q +q +/E2 gs +/X2 Do +Q + +endstream +endobj + +31 0 obj + 64 +endobj + +32 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources 29 0 R + /Contents 30 0 R + /Parent 33 0 R + >> +endobj + +33 0 obj + << /Kids [ 32 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +34 0 obj + << /Pages 33 0 R + /Type /Catalog + >> +endobj + +xref +0 35 +0000000000 65535 f +0000000010 00000 n +0000001142 00000 n +0000001164 00000 n +0000002310 00000 n +0000002332 00000 n +0000003480 00000 n +0000003502 00000 n +0000010873 00000 n +0000010896 00000 n +0000011420 00000 n +0000011443 00000 n +0000011968 00000 n +0000011991 00000 n +0000013296 00000 n +0000013320 00000 n +0000013618 00000 n +0000013641 00000 n +0000015090 00000 n +0000015113 00000 n +0000019782 00000 n +0000019806 00000 n +0000020328 00000 n +0000020351 00000 n +0000020650 00000 n +0000020673 00000 n +0000022119 00000 n +0000022142 00000 n +0000023918 00000 n +0000023941 00000 n +0000024341 00000 n +0000024463 00000 n +0000024485 00000 n +0000024664 00000 n +0000024740 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 34 0 R + /Size 35 +>> +startxref +24801 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2.imageset/Contents.json new file mode 100644 index 000000000..2d4d81978 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2.imageset/OnboardingSplashScreenPage2.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2.imageset/OnboardingSplashScreenPage2.pdf new file mode 100644 index 000000000..283dbcc94 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2.imageset/OnboardingSplashScreenPage2.pdf differ diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2Dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2Dark.imageset/Contents.json new file mode 100644 index 000000000..f48d39559 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2Dark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage2-Dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2Dark.imageset/OnboardingSplashScreenPage2-Dark.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2Dark.imageset/OnboardingSplashScreenPage2-Dark.pdf new file mode 100644 index 000000000..9c1d4b260 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage2Dark.imageset/OnboardingSplashScreenPage2-Dark.pdf differ diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3.imageset/Contents.json new file mode 100644 index 000000000..18c1506a2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage3.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3.imageset/OnboardingSplashScreenPage3.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3.imageset/OnboardingSplashScreenPage3.pdf new file mode 100644 index 000000000..7c1c6dc76 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3.imageset/OnboardingSplashScreenPage3.pdf @@ -0,0 +1,1451 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 84.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 94.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 66.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 178.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 210.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 182.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 154.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 234.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 98.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 98.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 262.000000 98.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 98.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 98.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 98.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 126.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 126.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 126.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 70.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 70.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 262.000000 70.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 42.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 42.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 42.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 122.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 234.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 234.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 122.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 238.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 238.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 122.000000 238.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 66.000000 42.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 178.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 206.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 206.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 66.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 70.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 38.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 122.000000 14.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 94.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 210.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 182.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 182.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 210.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 154.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 126.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 42.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 70.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 238.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 126.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 42.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 70.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 266.000000 cm +0.854902 0.850980 0.992157 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 154.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 37227 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 300.000000 300.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000037317 00000 n +0000037341 00000 n +0000037516 00000 n +0000037590 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +37649 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3Dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3Dark.imageset/Contents.json new file mode 100644 index 000000000..25ceecb9f --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3Dark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage3-Dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3Dark.imageset/OnboardingSplashScreenPage3-Dark.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3Dark.imageset/OnboardingSplashScreenPage3-Dark.pdf new file mode 100644 index 000000000..ef2dcdbc8 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage3Dark.imageset/OnboardingSplashScreenPage3-Dark.pdf @@ -0,0 +1,1451 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 84.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 94.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 55.999996 70.000122 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 70.000122 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 66.000000 70.000122 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 178.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 41.999878 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 41.999878 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 41.999878 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000015 210.000061 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000015 182.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000015 154.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 223.999985 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000015 41.999878 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 223.999985 41.999878 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 111.999992 70.000122 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 223.999985 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 251.999985 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 233.999985 210.000061 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 154.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 98.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 98.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 262.000000 98.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 98.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 98.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 98.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 126.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 126.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 126.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 70.000122 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 70.000122 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 262.000000 70.000122 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 41.999878 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 41.999878 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 41.999878 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 28.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +28.000000 20.000000 l +28.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 111.999992 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 121.999992 154.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 223.999985 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 233.999985 182.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 223.999985 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 233.999985 154.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 111.999992 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 121.999992 210.000061 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 111.999992 237.999939 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 237.999939 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 121.999992 237.999939 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 55.999996 41.999878 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 112.000000 41.999878 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 66.000000 41.999878 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 126.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 178.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000015 237.999939 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 237.999939 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 206.000015 237.999939 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 196.000015 14.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 14.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 206.000015 14.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 14.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 14.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 14.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 55.999996 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 66.000000 98.000000 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 70.000122 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 224.000000 70.000122 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 150.000000 70.000122 cm +0.049479 0.742188 0.545395 scn +0.000000 20.000000 m +84.000000 20.000000 l +84.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 27.999998 237.999939 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 237.999939 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 38.000000 237.999939 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 182.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 56.000000 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 210.000061 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 111.999992 14.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 168.000000 14.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 121.999992 14.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 140.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 94.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +0.000000 20.000000 m +56.000000 20.000000 l +56.000000 0.000000 l +0.000000 0.000000 l +0.000000 20.000000 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 210.000061 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 182.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 111.999992 182.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 210.000061 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 55.999996 154.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 126.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 252.000000 41.999878 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 70.000122 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.000000 237.999939 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 55.999996 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 126.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 41.999878 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 27.999998 70.000122 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 280.000000 266.000000 cm +0.223529 0.250980 0.286275 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 84.000000 154.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 55.999996 126.000000 cm +0.049479 0.742188 0.545395 scn +20.000000 10.000000 m +20.000000 4.477153 15.522847 0.000000 10.000000 0.000000 c +4.477152 0.000000 0.000000 4.477153 0.000000 10.000000 c +0.000000 15.522848 4.477152 20.000000 10.000000 20.000000 c +15.522847 20.000000 20.000000 15.522848 20.000000 10.000000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 37227 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 300.000031 300.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000037317 00000 n +0000037341 00000 n +0000037516 00000 n +0000037590 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +37649 +%%EOF \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4.imageset/Contents.json new file mode 100644 index 000000000..f5374ef56 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage4.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4.imageset/OnboardingSplashScreenPage4.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4.imageset/OnboardingSplashScreenPage4.pdf new file mode 100644 index 000000000..aac8e76c0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4.imageset/OnboardingSplashScreenPage4.pdf differ diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4Dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4Dark.imageset/Contents.json new file mode 100644 index 000000000..70a47dd70 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4Dark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "OnboardingSplashScreenPage4-Dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4Dark.imageset/OnboardingSplashScreenPage4-Dark.pdf b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4Dark.imageset/OnboardingSplashScreenPage4-Dark.pdf new file mode 100644 index 000000000..05a60bf8e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Onboarding/OnboardingSplashScreenPage4Dark.imageset/OnboardingSplashScreenPage4-Dark.pdf differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/Contents.json new file mode 100644 index 000000000..3007f6fc5 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_location.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_location@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_location@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location.png b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location.png new file mode 100644 index 000000000..be968b77d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location@2x.png new file mode 100644 index 000000000..b77c9e11b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location@3x.png new file mode 100644 index 000000000..8ce3cacae Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_location.imageset/action_location@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_poll.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_poll.imageset/Contents.json index 7c6421fc2..2839a73cc 100644 --- a/Riot/Assets/Images.xcassets/Room/Actions/action_poll.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_poll.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json index da4a164c9..73c00596a 100644 --- a/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Contents.json similarity index 67% rename from Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/Contents.json rename to Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Contents.json index 9b395447b..d2c033d2d 100644 --- a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "space_home_icon.png", + "filename" : "Thread.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "space_home_icon@2x.png", + "filename" : "Thread@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "space_home_icon@3x.png", + "filename" : "Thread@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread.png new file mode 100644 index 000000000..632a63307 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread@2x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread@2x.png new file mode 100644 index 000000000..dcd35cf62 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread@3x.png b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread@3x.png new file mode 100644 index 000000000..32d13c5db Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_thread.imageset/Thread@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/Contents.json new file mode 100644 index 000000000..0bf02ac7f --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "location_marker_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_marker_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_marker_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon.png new file mode 100644 index 000000000..be968b77d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon@2x.png new file mode 100644 index 000000000..b77c9e11b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon@3x.png new file mode 100644 index 000000000..8ce3cacae Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_marker_icon.imageset/location_marker_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/Contents.json new file mode 100644 index 000000000..c3b8dbc7a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_share_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_share_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_share_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon.png new file mode 100644 index 000000000..da7d7530b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon@2x.png new file mode 100644 index 000000000..41969eb70 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon@3x.png new file mode 100644 index 000000000..43bf9dcb3 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_share_icon.imageset/location_share_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json new file mode 100644 index 000000000..175219374 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_user_marker.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_user_marker@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_user_marker@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png new file mode 100644 index 000000000..eedadad5e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png new file mode 100644 index 000000000..fce4dcb04 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png new file mode 100644 index 000000000..ff8ce3aad Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_user_marker.imageset/location_user_marker@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/Contents.json new file mode 100644 index 000000000..945c5c337 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "poll_type_checkbox_default.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "poll_type_checkbox_default@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "poll_type_checkbox_default@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default.png new file mode 100644 index 000000000..84e419079 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default@2x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default@2x.png new file mode 100644 index 000000000..7e6083bc3 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default@3x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default@3x.png new file mode 100644 index 000000000..316a8eab7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_default.imageset/poll_type_checkbox_default@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/Contents.json new file mode 100644 index 000000000..b7fbce06b --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "poll_type_checkbox_selected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "poll_type_checkbox_selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "poll_type_checkbox_selected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected.png new file mode 100644 index 000000000..6a744d6be Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected@2x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected@2x.png new file mode 100644 index 000000000..67c3bbd64 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected@3x.png b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected@3x.png new file mode 100644 index 000000000..a4cd21452 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Polls/poll_type_checkbox_selected.imageset/poll_type_checkbox_selected@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Threads/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Threads/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/Contents.json new file mode 100644 index 000000000..7c7666862 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "filter_list_black_24dp 1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "filter_list_black_24dp 1@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "filter_list_black_24dp 1@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1.png b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1.png new file mode 100644 index 000000000..7710fa90e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1@2x.png b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1@2x.png new file mode 100644 index 000000000..b9cff9c1b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1@3x.png b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1@3x.png new file mode 100644 index 000000000..5fec0029d Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter.imageset/filter_list_black_24dp 1@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_filter_applied.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter_applied.imageset/Contents.json new file mode 100644 index 000000000..5d848cb50 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter_applied.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "filter-on.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_filter_applied.imageset/filter-on.svg b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter_applied.imageset/filter-on.svg new file mode 100644 index 000000000..26b34d0e2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_filter_applied.imageset/filter-on.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Contents.json new file mode 100644 index 000000000..92eec1362 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "Thread.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Thread@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Thread@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread.png b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread.png new file mode 100644 index 000000000..21add0227 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread@2x.png b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread@2x.png new file mode 100644 index 000000000..e5c85a57f Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread@3x.png b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread@3x.png new file mode 100644 index 000000000..2204e1ccf Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Threads/threads_icon.imageset/Thread@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/Contents.json new file mode 100644 index 000000000..1b8f71d8d --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "file_attachment.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "file_attachment@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "file_attachment@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment.png b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment.png new file mode 100644 index 000000000..cbefc09be Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment.png differ diff --git a/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment@2x.png b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment@2x.png new file mode 100644 index 000000000..0ded843f2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment@3x.png b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment@3x.png new file mode 100644 index 000000000..d2d626082 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/file_attachment.imageset/file_attachment@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Contents.json new file mode 100644 index 000000000..b5cd56ac8 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "Link.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Link@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Link@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link.png b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link.png new file mode 100644 index 000000000..546ec9f08 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link.png differ diff --git a/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link@2x.png b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link@2x.png new file mode 100644 index 000000000..85c2c1bce Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link@3x.png b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link@3x.png new file mode 100644 index 000000000..9e44e1899 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/link_icon.imageset/Link@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/Contents.json new file mode 100644 index 000000000..cf8bba813 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "space_add_room.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_add_room@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_add_room@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room.png b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room.png new file mode 100644 index 000000000..451a80229 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room@2x.png new file mode 100644 index 000000000..b385451f0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room@3x.png new file mode 100644 index 000000000..de967119a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_add_room.imageset/space_add_room@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/Contents.json new file mode 100644 index 000000000..eb54967ab --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "space_creation_camera.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_creation_camera@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_creation_camera@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera.png new file mode 100644 index 000000000..65264a038 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera@2x.png new file mode 100644 index 000000000..f87bc29e8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera@3x.png new file mode 100644 index 000000000..c593c5bf5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_camera.imageset/space_creation_camera@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/Contents.json new file mode 100644 index 000000000..bb2b6cc6f --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "space_creation_private.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_creation_private@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_creation_private@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private.png new file mode 100644 index 000000000..a77ac3c62 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private@2x.png new file mode 100644 index 000000000..581625eaf Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private@3x.png new file mode 100644 index 000000000..9441bbbbc Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_private.imageset/space_creation_private@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/Contents.json new file mode 100644 index 000000000..d5cfa2de7 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "space_creation_public.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_creation_public@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_creation_public@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public.png new file mode 100644 index 000000000..dce0fb56a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public@2x.png new file mode 100644 index 000000000..38caf7d4e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public@3x.png new file mode 100644 index 000000000..3ff76fca5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_creation_public.imageset/space_creation_public@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon.png deleted file mode 100644 index 75af27227..000000000 Binary files a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@2x.png deleted file mode 100644 index 7c4243805..000000000 Binary files a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@3x.png deleted file mode 100644 index 1076124e2..000000000 Binary files a/Riot/Assets/Images.xcassets/Spaces/space_home_icon.imageset/space_home_icon@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/Contents.json new file mode 100644 index 000000000..4513c28c2 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "space_home_icon_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_home_icon_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_home_icon_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark.png new file mode 100644 index 000000000..8f122d2c9 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark@2x.png new file mode 100644 index 000000000..cf8c23942 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark@3x.png new file mode 100644 index 000000000..5960d13a5 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_dark.imageset/space_home_icon_dark@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/Contents.json new file mode 100644 index 000000000..97d17d9bb --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "space_home_icon_light.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_home_icon_light@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_home_icon_light@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light.png new file mode 100644 index 000000000..fb671b141 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light@2x.png new file mode 100644 index 000000000..d6396bb1a Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light@3x.png new file mode 100644 index 000000000..2d527378c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_home_icon_light.imageset/space_home_icon_light@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/Contents.json new file mode 100644 index 000000000..d428b7ae6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "space_invite_user.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_invite_user@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_invite_user@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user.png b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user.png new file mode 100644 index 000000000..672632139 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user@2x.png new file mode 100644 index 000000000..cfda74c5b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user@3x.png new file mode 100644 index 000000000..fcd432e87 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_invite_user.imageset/space_invite_user@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/Contents.json new file mode 100644 index 000000000..943055f00 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "space_menu_plus_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "space_menu_plus_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "space_menu_plus_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon.png b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon.png new file mode 100644 index 000000000..cfc87199c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon@2x.png b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon@2x.png new file mode 100644 index 000000000..c3cc1ee60 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon@3x.png b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon@3x.png new file mode 100644 index 000000000..bf5489cc2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/space_menu_plus_icon.imageset/space_menu_plus_icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/Contents.json new file mode 100644 index 000000000..f5861fe3f --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "spaces_add_space_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "spaces_add_space_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "spaces_add_space_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark.png b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark.png new file mode 100644 index 000000000..2e8b66fd0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark@2x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark@2x.png new file mode 100644 index 000000000..dfc11fc8f Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark@3x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark@3x.png new file mode 100644 index 000000000..9711a05a4 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_dark.imageset/spaces_add_space_dark@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/Contents.json new file mode 100644 index 000000000..3003074fa --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "spaces_add_space_light.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "spaces_add_space_light@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "spaces_add_space_light@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light.png b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light.png new file mode 100644 index 000000000..948a2b3bd Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light@2x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light@2x.png new file mode 100644 index 000000000..55ce9ba21 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light@3x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light@3x.png new file mode 100644 index 000000000..1db00fbb1 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_add_space_light.imageset/spaces_add_space_light@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/Contents.json new file mode 100644 index 000000000..d6068239b --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "spaces_invite_users.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "spaces_invite_users@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "spaces_invite_users@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users.png b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users.png new file mode 100644 index 000000000..5c6b4401b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users@2x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users@2x.png new file mode 100644 index 000000000..4a36ffbe2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users@3x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users@3x.png new file mode 100644 index 000000000..2568a3280 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_invite_users.imageset/spaces_invite_users@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/Contents.json new file mode 100644 index 000000000..75f10b9f4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "spaces_modal_back.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "spaces_modal_back@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "spaces_modal_back@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back.png b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back.png new file mode 100644 index 000000000..b9c92417e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back@2x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back@2x.png new file mode 100644 index 000000000..d299195d2 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back@3x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back@3x.png new file mode 100644 index 000000000..3a869e88e Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_back.imageset/spaces_modal_back@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/Contents.json new file mode 100644 index 000000000..93d85da8a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "spaces_modal_close.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "spaces_modal_close@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "spaces_modal_close@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close.png b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close.png new file mode 100644 index 000000000..751a9bd59 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close@2x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close@2x.png new file mode 100644 index 000000000..365ac0fac Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close@3x.png b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close@3x.png new file mode 100644 index 000000000..33d3f90da Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/spaces_modal_close.imageset/spaces_modal_close@3x.png differ diff --git a/Riot/Assets/cs.lproj/InfoPlist.strings b/Riot/Assets/cs.lproj/InfoPlist.strings new file mode 100644 index 000000000..88c506408 --- /dev/null +++ b/Riot/Assets/cs.lproj/InfoPlist.strings @@ -0,0 +1,7 @@ + + +// Permissions usage explanations +"NSCameraUsageDescription" = "Kamera slouží k focení, zachycení videí a k videohovorům."; +"NSPhotoLibraryUsageDescription" = "Galerie se používá k posílání obrázků a videí."; +"NSCalendarsUsageDescription" = "Zobrazuje události v aplikaci."; +"NSFaceIDUsageDescription" = "Face ID se používá k přístupu do aplikace."; diff --git a/Riot/Assets/cs.lproj/Localizable.strings b/Riot/Assets/cs.lproj/Localizable.strings index 8b1378917..15e6ddbbf 100644 --- a/Riot/Assets/cs.lproj/Localizable.strings +++ b/Riot/Assets/cs.lproj/Localizable.strings @@ -1 +1,170 @@ + + +/** Reactions **/ + +/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */ +"REACTION_FROM_USER" = "%@ reagoval/a %@"; + +/* Look, stuff's happened, alright? Just open the app. */ +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ nových zpráv v %@, %@ a dalších"; + +/* Multiple messages in two rooms */ +"MSGS_IN_TWO_ROOMS" = "%@ nových zpráv v %@ a %@"; + +/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ +"MSGS_FROM_TWO_PLUS_USERS" = "%@ nových zpráv od %@, %@ a dalších"; + +/* Multiple unread messages from three people */ +"MSGS_FROM_THREE_USERS" = "%@ nových zpráv od %@, %@ a %@"; + +/* Multiple unread messages from two people */ +"MSGS_FROM_TWO_USERS" = "%@ nových zpráv od %@ a %@"; + +/* Multiple unread messages from a specific person, not referencing a room */ +"MSGS_FROM_USER" = "%@ nové zprávy od %@"; + +/** Coalesced messages **/ + +/* Multiple unread messages in a room */ +"UNREAD_IN_ROOM" = "%@ nové zprávy v %@"; + +/* New message with hidden content due to PIN enabled */ +"MESSAGE_PROTECTED" = "Nová zpráva"; + +/* New message indicator on a room */ +"MESSAGE_IN_X" = "Zpráva v %@"; + +/* New message indicator from a DM */ +"MESSAGE_FROM_X" = "Zpráva od %@"; + +/** Notification messages **/ + +/* New message indicator on unknown room */ +"MESSAGE" = "Zpráva"; + +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ poslal/a nálepku"; + +/* A single unread message */ +"SINGLE_UNREAD" = "Dostali jste zprávu"; + +/* A single unread message in a room */ +"SINGLE_UNREAD_IN_ROOM" = "V %@ vám přišla zpráva"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ sdílel/a svou polohu"; + +/** Single, unencrypted messages (where we can include the content */ + +/* New message from a specific person, not referencing a room. Content included. */ +"MSG_FROM_USER_WITH_CONTENT" = "%@: %@"; + +/** Key verification **/ + +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ žádá o ověření"; + +/* Group call from user, CallKit caller name */ +"GROUP_CALL_FROM_USER" = "%@ (Skupinový hovor)"; + +/* A user added a Jitsi call to a room */ +"GROUP_CALL_STARTED" = "Začal skupinový hovor"; + +/* Incoming named video conference invite from a specific person */ +"VIDEO_CONF_NAMED_FROM_USER" = "Skupinový videohovor od %@: '%@'"; + +/* Incoming named voice conference invite from a specific person */ +"VOICE_CONF_NAMED_FROM_USER" = "Skupinový hovor od %@: '%@'"; + +/* Incoming unnamed video conference invite from a specific person */ +"VIDEO_CONF_FROM_USER" = "Skupinový videohovor od %@"; + +/* Incoming unnamed voice conference invite from a specific person */ +"VOICE_CONF_FROM_USER" = "Skupinový hovor od %@"; + +/* Incoming one-to-one video call */ +"VIDEO_CALL_FROM_USER" = "Videohovor od %@"; + +/** Calls **/ + +/* Incoming one-to-one voice call */ +"VOICE_CALL_FROM_USER" = "Hovor od %@"; + +/* A user's membership has updated in an unknown way */ +"USER_MEMBERSHIP_UPDATED" = "%@ aktualizoval/a svůj profil"; + +/* A user has change their avatar */ +"USER_UPDATED_AVATAR" = "%@ změnil/a svůj avatar"; + +/* A user has change their name to a new name which we don't know */ +"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ změnil/a svůj alias"; + +/** Membership Updates **/ + +/* A user has change their name to a new name */ +"USER_UPDATED_DISPLAYNAME" = "%@ změnil/a svůj alias na %@"; + +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "%@ vás pozval/a do %@"; + +/* A user has invited you to an (unamed) group chat */ +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ vás pozval/a ke skupinové konverzaci"; + +/** Invites **/ + +/* A user has invited you to a chat */ +"USER_INVITE_TO_CHAT" = "%@ vás pozval/a ke konverzaci"; + +/* A user has reacted to a message, but the reaction content is unknown */ +"GENERIC_REACTION_FROM_USER" = "%@ poslal/a reakci"; + +/* New message from a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM" = "%@ něco zveřejnil v %@"; + +/* New file message from a specific person, not referencing a room. */ +"FILE_FROM_USER" = "%@ poslal/a soubor %@"; + +/* New voice message from a specific person, not referencing a room. */ +"VOICE_MESSAGE_FROM_USER" = "%@ poslal/a zvukový soubor"; + +/* New audio message from a specific person, not referencing a room. */ +"AUDIO_FROM_USER" = "%@ poslal/a zvukový soubor %@"; + +/* New video message from a specific person, not referencing a room. */ +"VIDEO_FROM_USER" = "%@ poslal/a video"; + +/* New image message from a specific person in a named room. */ +"IMAGE_FROM_USER_IN_ROOM" = "%@ zveřejnil/a obrázek %@ v %@"; + +/** Media Messages **/ + +/* New image message from a specific person, not referencing a room. */ +"PICTURE_FROM_USER" = "%@ poslal/a obrázek"; + +/* New action message from a specific person in a named room. */ +"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; + +/* New action message from a specific person, not referencing a room. */ +"ACTION_FROM_USER" = "* %@ %@"; + +/* New message from a specific person in a named room. Content included. */ +"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ v %@: %@"; + +/** Single, end-to-end encrypted messages (ie. we don't know what they say) */ + +/* New message from a specific person, not referencing a room */ +"MSG_FROM_USER" = "%@ poslal/a zprávu"; + +/* New message reply from a specific person in a named room. */ +"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ odpověděl/a v %@"; + +/* New message reply from a specific person, not referencing a room. */ +"REPLY_FROM_USER_TITLE" = "%@ odpověděl/a"; + +/** Titles **/ + +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ v %@"; +/** General **/ + +"NOTIFICATION" = "Oznámení"; diff --git a/Riot/Assets/de.lproj/InfoPlist.strings b/Riot/Assets/de.lproj/InfoPlist.strings index 571d56f44..594fb19a0 100644 --- a/Riot/Assets/de.lproj/InfoPlist.strings +++ b/Riot/Assets/de.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSContactsUsageDescription" = "Element zeigt deine Kontakte an, damit du sie zum chatten einladen kannst."; "NSCalendarsUsageDescription" = "Sieh dir deine geplanten Meetings in der App an."; "NSFaceIDUsageDescription" = "Face-ID wird zum Zugriff auf deine App verwendet."; +"NSLocationWhenInUseUsageDescription" = "Wenn du deinen Standort mit Personen teilst, braucht Element Zugriff um ihnen eine Karte anzuzeigen."; diff --git a/Riot/Assets/de.lproj/Localizable.strings b/Riot/Assets/de.lproj/Localizable.strings index 3c7e1f066..a5edb4e62 100644 --- a/Riot/Assets/de.lproj/Localizable.strings +++ b/Riot/Assets/de.lproj/Localizable.strings @@ -116,3 +116,6 @@ /* A user's membership has updated in an unknown way */ "USER_MEMBERSHIP_UPDATED" = "Profil von %@ geupdatet"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ hat den eigenen Standort geteilt"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 63ea02f51..df03d8813 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -55,7 +55,7 @@ "auth_missing_phone" = "Fehlende Telefon-Nummer"; "auth_missing_email_or_phone" = "Fehlende E-Mail-Adresse oder Telefon-Nummer"; "auth_password_dont_match" = "Passwörter stimmen nicht überein"; -"auth_username_in_use" = "Nutzername bereits verwendet"; +"auth_username_in_use" = "Benutzername bereits verwendet"; "auth_forgot_password" = "Passwort vergessen?"; "auth_msisdn_validation_title" = "Verifizierung ausstehend"; "auth_msisdn_validation_message" = "Bitte gib unten den Aktivierungs-Code ein, den wir per SMS verschickt haben."; @@ -114,10 +114,10 @@ "contacts_address_book_no_contact" = "Keine lokalen Kontakte"; "contacts_address_book_permission_required" = "Berechtigungen benötigt um auf lokale Kontakte zuzugreifen"; // Chat participants -"room_participants_title" = "Teilnehmer"; +"room_participants_title" = "Teilnehmende"; "room_participants_add_participant" = "Teilnehmer hinzufügen"; "room_participants_one_participant" = "1 Teilnehmer"; -"room_participants_multi_participants" = "%d Teilnehmer"; +"room_participants_multi_participants" = "%d Teilnehmende"; "room_participants_leave_prompt_title" = "Raum verlassen"; "room_participants_leave_prompt_msg" = "Bist du sicher, dass du den Raum verlassen willst?"; "room_participants_remove_prompt_title" = "Bestätigung"; @@ -136,12 +136,12 @@ "room_participants_action_section_other" = "Optionen"; "room_participants_action_invite" = "Einladen"; "room_participants_action_leave" = "Diesen Raum verlassen"; -"room_participants_action_remove" = "Von diesem Raum entfernen"; +"room_participants_action_remove" = "Aus diesem Raum entfernen"; "room_participants_action_ban" = "Aus diesem Raum bannen"; -"room_participants_action_ignore" = "Alle Nachrichten von diesem Nutzer verbergen"; -"room_participants_action_unignore" = "Zeige alle Nachrichten von diesem Nutzer"; -"room_participants_action_set_moderator" = "Mache zu Moderator"; -"room_participants_action_set_admin" = "Mache zum Administrator"; +"room_participants_action_ignore" = "Alle Nachrichten dieser Person verbergen"; +"room_participants_action_unignore" = "Alle Nachrichten dieser Person zeigen"; +"room_participants_action_set_moderator" = "Moderationsrechte vergeben"; +"room_participants_action_set_admin" = "Administrationsrechte vergeben"; "room_participants_action_start_new_chat" = "Starte neuen Chat"; "room_participants_action_start_video_call" = "Starte Video-Anruf"; "room_participants_action_mention" = "Erwähnen"; @@ -206,9 +206,9 @@ "settings_user_settings" = "NUTZER-EINSTELLUNGEN"; "settings_notifications_settings" = "BENACHRICHTIGUNGS-EINSTELLUNGEN"; "settings_ignored_users" = "IGNORIERTE NUTZER"; -"settings_contacts" = "LOKALE KONTAKTE"; +"settings_contacts" = "GERÄTEKONTAKTE"; "settings_advanced" = "ERWEITERT"; -"settings_other" = "WEITERES"; +"settings_other" = "Weiteres"; "settings_devices" = "SITZUNGEN"; "settings_cryptography" = "VERSCHLÜSSELUNG"; "settings_sign_out" = "Abmelden"; @@ -299,7 +299,7 @@ "room_participants_ago" = "her"; "room_participants_action_section_admin_tools" = "Admin-Werkzeuge"; "room_participants_action_unban" = "Entsperren"; -"room_participants_action_set_default_power_level" = "Zurück auf normale Berechtigung"; +"room_participants_action_set_default_power_level" = "Besondere Berechtigungen entziehen"; "room_participants_action_start_voice_call" = "Starte Sprach-Anruf"; "room_ongoing_conference_call" = "Laufender Konferenz-Anruf. Trete bei als %@ oder %@."; "room_event_action_redact" = "Entfernen"; @@ -397,7 +397,7 @@ "bug_report_send_screenshot" = "Sende Bildschirmfoto"; "bug_report_progress_zipping" = "Sammele Protokolle"; "bug_report_progress_uploading" = "Bericht hochladen"; -"bug_report_send" = "Gesendet"; +"bug_report_send" = "Senden"; "collapse" = "zusammenklappen"; "auth_email_in_use" = "Diese E-Mail-Adresse wird bereits verwendet"; "auth_phone_in_use" = "Diese Telefonnummer wird bereits verwendet"; @@ -509,7 +509,7 @@ "room_action_send_photo_or_video" = "Foto oder Video senden"; "room_action_send_sticker" = "Aufkleber senden"; "settings_deactivate_account" = "DEAKTIVIERTES KONTO"; -"settings_deactivate_my_account" = "Benutzerkonto deaktiveren"; +"settings_deactivate_my_account" = "Mein Konto deaktivieren"; "widget_sticker_picker_no_stickerpacks_alert" = "Du hast aktuell keine Aufkleberpakete aktiviert."; "widget_sticker_picker_no_stickerpacks_alert_add_now" = "Welche hinzufügen?"; // GDPR @@ -544,12 +544,12 @@ "room_event_action_view_decrypted_source" = "Zeige entschlüsselten Quelltext"; "room_recents_server_notice_section" = "SYSTEMBENACHRICHTIGUNGEN"; "room_resource_limit_exceeded_message_contact_1" = " Bitte "; -"room_resource_limit_exceeded_message_contact_2_link" = "kontaktiere deinen Dienst-Administrator"; +"room_resource_limit_exceeded_message_contact_2_link" = "kontaktiere deine Dienst-Administration"; "room_resource_limit_exceeded_message_contact_3" = " um diesen Dienst weiter zu nutzen."; "homeserver_connection_lost" = "Konnte keine Verbindung zum Heimserver herstellen."; -"room_resource_usage_limit_reached_message_1_default" = "Dieser Heimserver hat einer seiner Ressourcengrenzen überschritten, sodass "; -"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Dieser Heimserver hat seine Begrenzung an monatlich aktiven Benutzer überschritten, sodass "; -"room_resource_usage_limit_reached_message_2" = "einige Benutzer nicht in der Lage sein werden, sich anzumelden."; +"room_resource_usage_limit_reached_message_1_default" = "Dieser Heimserver hat eine seiner Ressourcengrenzen überschritten, sodass "; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Dieser Heimserver hat seine Grenze an monatlich aktiven Benutzenden erreicht, sodass "; +"room_resource_usage_limit_reached_message_2" = "einige Benutzende nicht in der Lage sein werden, sich anzumelden."; "room_resource_usage_limit_reached_message_contact_3" = " um diese Obergrenze erhöhen zu lassen."; "auth_accept_policies" = "Bitte Regeln dieses Heimservers ansehen und akzeptieren:"; "settings_key_backup" = "SCHLÜSSEL-SICHERHEITSKOPIE"; @@ -634,13 +634,13 @@ "sign_out_existing_key_backup_alert_sign_out_action" = "Abmelden"; "sign_out_non_existing_key_backup_alert_title" = "Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, wenn du dich jetzt abmeldest"; "sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Beginne Schlüsselsicherung zu nutzen"; -"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Ich möchte meine verschlüsselten Nachrichten nicht"; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Ich brauche meine verschlüsselten Nachrichten nicht"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Du wirst deine verschlüsselten Nachrichten verlieren"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, es sei denn, du sicherst deine Schlüssel, bevor du dich abmeldest."; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Abmelden"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Sicherungskopie"; "sign_out_key_backup_in_progress_alert_title" = "Schlüsselsicherung läuft. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."; -"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Ich möchte meine verschlüsselten Nachrichten nicht"; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Ich brauche meine verschlüsselten Nachrichten nicht"; "sign_out_key_backup_in_progress_alert_cancel_action" = "Ich werde warten"; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "Neue Schlüsselsicherung"; @@ -734,7 +734,7 @@ "close" = "Schließen"; "auth_softlogout_signed_out" = "Du bist abgemeldet"; "auth_softlogout_sign_in" = "Anmelden"; -"auth_softlogout_reason" = "Deine Heimserver-Administrator (%1$@) hat dich von deinem Konto %2$@ (%3$@) abgemeldet."; +"auth_softlogout_reason" = "Die Administration deines Heimservers (%1$@) hat dich von deinem Konto %2$@ (%3$@) abgemeldet."; "auth_softlogout_recover_encryption_keys" = "Melde dich an, um ausschließlich auf diesem Gerät gespeicherte Verschlüsselungsschlüssel wiederherzustellen. Du benötigst sie, um deine verschlüsselten Nachrichten auf jedem Gerät zu lesen."; "auth_softlogout_clear_data" = "Persönliche Daten löschen"; "auth_softlogout_clear_data_message_1" = "Warnung: Deine persönlichen Daten (einschließlich Verschlüsselungsschlüssel) sind noch auf diesem Gerät gespeichert."; @@ -1001,11 +1001,11 @@ "skip" = "Überspringen"; "security_settings_crosssigning_info_not_bootstrapped" = "Quersignierung ist bisher nicht konfiguriert."; "room_member_power_level_admin_in" = "Admin in %@"; -"room_member_power_level_moderator_in" = "Moderator in %@"; -"room_member_power_level_custom_in" = "Benutzerdefiniert (%@) in %@"; +"room_member_power_level_moderator_in" = "Mod in %@"; +"room_member_power_level_custom_in" = "Selbstdefiniert (%@) in %@"; "room_member_power_level_short_admin" = "Admin"; -"room_member_power_level_short_moderator" = "Moderator"; -"room_member_power_level_short_custom" = "Benutzerdefiniert"; +"room_member_power_level_short_moderator" = "Mod"; +"room_member_power_level_short_custom" = "Selbstdefiniert"; "security_settings_secure_backup" = "SICHERE SICHERHEITSKOPIE"; "security_settings_secure_backup_synchronise" = "Synchronisiere"; "security_settings_secure_backup_delete" = "Backup löschen"; @@ -1019,7 +1019,7 @@ "store_promotional_text" = "Privatsphäre-wahrende Kollaborations-App in einem offenen Netzwerk. Dezentral, um dir die Kontrolle zu geben. Keine Datenerfassung, keine Hintertüren und kein Zugriff durch Dritte."; "room_participants_action_security_status_complete_security" = "Vollständige Sicherheit"; "external_link_confirmation_title" = "Überprüfe diesen Link genau"; -"external_link_confirmation_message" = "Der Link %@ braucht zu lange auf der anderen Seite: %@\n\nSicher, dass du fortfahren möchtest?"; +"external_link_confirmation_message" = "Der Link %@ bringt dich auf eine andere Seite: %@\n\nSicher, dass du fortfahren möchtest?"; "security_settings_crypto_sessions_description_2" = "Wenn du dich nicht angemeldet hast, ändere dein Passwort und setze die Sichere Sicherheitskopie zurück."; "security_settings_secure_backup_description" = "Sichere die Schlüssel, um Datenverlust zu verhindern. Sie werden mit einem Sicherungsschlüssel gesichert."; "security_settings_crosssigning_info_exists" = "Dein Konto hat eine Quersignatur-Identität, aber dieser Sitzung wird noch nicht vertraut. Vervollständige die Sicherheit auf diese Sitzung."; @@ -1327,16 +1327,16 @@ "room_accessibility_video_call" = "Videoanruf"; "room_message_replying_to" = "%@ anworten"; "room_message_editing" = "Bearbeitung"; -"space_beta_announce_information" = "Spaces are a new way to group rooms and people. Bald werden sie auch auf iOS verfügbar sein, bis dahin kannst du sie schon auf %@ Web/Desktop testen."; -"space_feature_unavailable_information" = "Wir haben Spaces entwickelt, damit ihr eure vielen Räume besser organisieren könnt.\n\nBald werden sie auch auf iOS verfügbar sein, bis dahin kannst du sie schon auf %@ Web/Desktop testen. Alle Räume die du dort betrittst, sind natürlich auch hier verfügbar."; +"space_beta_announce_information" = "Spaces bieten neue Möglichkeiten um Räume und Personen zu gruppieren. Sie sind noch nicht auf iOS verfügbar, aber du kannst sie jetzt schon mit Web und Desktop nutzen."; +"space_feature_unavailable_information" = "Spaces bieten neue Möglichkeiten um Räume und Personen zu gruppieren.\n\nBald werden sie auch hier verfügbar sein. Für den Moment kannst du ihnen auf einer der anderen Plattformen beitreten und hier auf alle Räume zugreifen, denen du dort beitrittst."; "space_beta_announce_subtitle" = "Die verbesserte Version von Communities"; "space_beta_announce_title" = "Spaces sind bald verfügbar"; "space_beta_announce_badge" = "Beta (in Entwicklung)"; -"space_feature_unavailable_subtitle" = "Spaces sind auf iOS noch nicht verfügbar. Du kannst sie aber schon auf %@ Web oder Desktop ausprobieren"; +"space_feature_unavailable_subtitle" = "Spaces sind auf iOS noch nicht verfügbar. Du kannst sie aber schon auf Web oder Desktop benutzen"; // Mark: - Spaces -"space_feature_unavailable_title" = "Spaces sind noch in der Entwicklung und werden bald verfügbar sein"; +"space_feature_unavailable_title" = "Spaces sind bald verfügbar"; "event_formatter_group_call_incoming" = "%@ in %@"; "event_formatter_group_call_leave" = "Verlassen"; "event_formatter_group_call_join" = "Beitreten"; @@ -1349,7 +1349,7 @@ "event_formatter_call_ringing" = "Läuten…"; "event_formatter_call_connecting" = "Verbinden…"; "settings_labs_enable_ringing_for_group_calls" = "Bei Gruppenanrufen klingeln"; -"room_no_privileges_to_create_group_call" = "Du musst Admin oder Moderator sein, um einen Anruf zu starten."; +"room_no_privileges_to_create_group_call" = "Du musst Administrations- oder Moderationsrechte besitzen, um einen Anruf zu starten."; "room_join_group_call" = "Beitreten"; // Chat @@ -1404,7 +1404,7 @@ "event_formatter_call_incoming_video" = "Eingehender Videoanruf"; "event_formatter_call_has_ended_with_time" = "Anruf beendet • %@"; "voice_message_stop_locked_mode_recording" = "Klicke, um die Aufnahme zu starten oder stoppen"; -"settings_device_notifications" = "Gerätbenachrichtigungen"; +"settings_device_notifications" = "Gerätebenachrichtigungen"; "voice_message_lock_screen_placeholder" = "Sprachnachricht"; "voice_message_remaining_recording_time" = "%@s übrig"; @@ -1439,7 +1439,7 @@ "settings_notify_me_for" = "Benachrichtige mich bei"; "settings_mentions_and_keywords" = "Erwähnungen und Schlüsselwörter"; "settings_confirm_media_size_description" = "Wenn dies aktiviert ist, wirst du beim Senden von Bildern und Videos gefragt, in welcher Größe sie gesendet werden sollen."; -"settings_confirm_media_size" = "Größe beim Senden bestätigen"; +"settings_confirm_media_size" = "Größe beim Senden auswählen"; "settings_notifications" = "BENACHRICHTIGUNGEN"; "settings_mentions_and_keywords_encryption_notice" = "Auf deinem Mobilgerät wirst du keine Benachrichtigungen für Erwähnungen und Schlüsselwörter in verschlüsselten Räumen erhalten."; "version_check_modal_subtitle_supported" = "Wir haben daran gearbeitet %@ zu verbessern um ein schnelleres und bereinigteres Erlebnis zu schaffen. Leider ist deine aktuelle iOS-Version mit einigen dieser Verbesserungen nicht kompatibel und wird daher nicht mehr unterstützt werden.\nWir empfehlen dir die Aktualisierung deines Betriebssystems um %@ weiterhin vollumfänglich zu nutzen."; @@ -1459,12 +1459,12 @@ // Mark: Avatar "space_avatar_view_accessibility_label" = "Avatar"; -"spaces_coming_soon_detail" = "Diese Funktion wurde hier noch nicht eingebaut, kommt aber bald. So lange kannst du dafür Element am Computer nutzen."; +"spaces_coming_soon_detail" = "Diese Funktion wurde hier noch nicht eingebaut, kommt aber bald. So lange kannst du dafür %@ am Computer nutzen."; "spaces_invites_coming_soon_title" = "Einladungen sind bald verfügbar"; "spaces_add_rooms_coming_soon_title" = "Räume hinzufügen ist bald verfügbar"; "spaces_no_result_found_title" = "Keine Ergebnisse gefunden"; "spaces_suggested_room" = "Vorgeschlagen"; -"leave_space_message_admin_warning" = "Du bist Administrator in diesem Space, stelle vor dem Verlassen sicher, dass du die Administrator-Rechte auf ein anderes Mitglied übertragen hast."; +"leave_space_message_admin_warning" = "Du besitzt in diesem Space Administrationsrechte, stelle vor dem Verlassen sicher, dass du die Administrationsrechte auf ein anderes Mitglied übertragen hast."; "leave_space_message" = "Bist du dir sicher, dass du %@ verlassen möchtest? Möchtest du außerdem alle Räume und Spaces in diesem Space verlassen?"; "leave_space_title" = "%@ verlassen"; "space_avatar_view_accessibility_hint" = "Space-Avatar ändern"; @@ -1487,7 +1487,7 @@ "settings_contacts_enable_sync" = "Finde deine Kontakte"; "space_home_show_all_rooms" = "Alle Räume anzeigen"; "service_terms_modal_information_description_integration_manager" = "Ein Integrationsmanager erlaubt dir, externe Funktionen hinzuzufügen."; -"service_terms_modal_information_description_identity_server" = "Der Identitätsserver sucht anhand der Telefonnummern und E-Mails in deinen Kontakten, ob diese einen Matrix-Account haben."; +"service_terms_modal_information_description_identity_server" = "Der Identitätsserver sucht anhand der Telefonnummern und E-Mails deiner Kontakte nach ihren Matrix-Accounts."; "service_terms_modal_information_title_integration_manager" = "Integrationsmanager"; // Alert explaining what an identity server / integration manager is. @@ -1502,8 +1502,97 @@ "service_terms_modal_title_message" = "Zum Fortfahren musst du die Nutzungsbedingungen akzeptieren"; "settings_contacts_enable_sync_description" = "Dies verwendet deinen Identitätsserver um dich mit deinen Kontakten zu verbinden."; "settings_phone_contacts" = "KONTAKTE AM HANDY"; -"room_event_action_forward" = "Weiter"; +"room_event_action_forward" = "Weiterleiten"; "find_your_contacts_identity_service_error" = "Konnte keine Verbindung zum Identitätsserver aufbauen."; "find_your_contacts_button_title" = "Finde deine Kontakte"; "contacts_address_book_permission_denied_alert_message" = "Um Kontakte zu aktivieren, öffne die Einstellungen deines Gerätes."; "contacts_address_book_permission_denied_alert_title" = "Kontakte deaktiviert"; +"poll_edit_form_create_options" = "Erstelle Optionen"; +"settings_discovery_accept_terms" = "Bedingungen des Identitätsservers akzeptieren"; +"find_your_contacts_message" = "Lass dir von %@ deine Kontakte anzeigen um schnell mit denen zu chatten, die du am besten kennst."; +"poll_timeline_votes_count" = "%lu Stimmen"; +"poll_timeline_one_vote" = "1 Stimme"; +"poll_edit_form_add_option" = "Option hinzufügen"; +"poll_edit_form_option_number" = "Option %lu"; +"poll_edit_form_question_or_topic" = "Frage oder Thematik"; +"room_event_action_end_poll" = "Umfrage beenden"; +"room_event_action_remove_poll" = "Umfrage entfernen"; + +// Mark: - Polls + +"poll_edit_form_create_poll" = "Umfrage erstellen"; +"settings_labs_enabled_polls" = "Umfragen"; +"share_extension_send_now" = "Jetzt senden"; +"accessibility_button_label" = "Knopf"; +"settings_analytics_and_crash_data" = "Sende Absturz- und Analysedaten"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Wir senden keine Informationen an Dritte"; +"analytics_prompt_terms_link_new_user" = "hier"; + +// Analytics +"analytics_prompt_title" = "Hilf dabei %@ zu verbessern"; +"settings_about" = "ÜBER"; +"enable" = "Aktivieren"; +"analytics_prompt_message_upgrade" = "Du hast in der Vergangenheit bereits zugestimmt anonyme Nutzungsdaten mit uns zu teilen. Jetzt werden wir als Hilfe, um zu verstehen, wie Personen mehrere Geräte benutzen, eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird."; +"analytics_prompt_message_new_user" = "Hilf uns dabei Probleme zu identifizieren und %@ zu verbessern, indem du anonyme Nutzungsdaten teilst. Um zu verstehen, wie Personen mehrere Geräte benutzen, werden wir eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird."; +"find_your_contacts_title" = "Starte mit der Auflistung deiner Kontakte"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Du kannst unsere gesamten Bedingungen %@ nachlesen."; +"poll_timeline_total_votes" = "%lu Stimmen abgegeben"; +"poll_timeline_total_one_vote" = "1 Stimme abgegeben"; +"poll_timeline_total_no_votes" = "Keine Stimmen abgegeben"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_poll_question_or_topic" = "Frage oder Thema der Umfrage"; +"poll_edit_form_input_placeholder" = "Schreib etwas"; +"analytics_prompt_terms_link_upgrade" = "hier"; +"poll_timeline_not_closed_title" = "Fehler beim Beenden der Abstimmung"; +"poll_timeline_vote_not_registered_subtitle" = "Wir konnten deine Stimme leider nicht erfassen. Versuche es bitte erneut"; +"poll_timeline_total_final_results" = "Es wurden %lu Stimmen abgegeben"; +"poll_timeline_total_final_results_one_vote" = "Es wurde 1 Stimme abgegeben"; +"poll_timeline_total_votes_not_voted" = "%lu Stimmen abgegeben. Stimme ab, um die Ergebnisse zu sehen"; +"poll_timeline_total_one_vote_not_voted" = "1 Stimme abgegeben. Stimme ab, um die Ergebnisse zu sehen"; +"poll_timeline_not_closed_subtitle" = "Versuche es bitte erneut"; +"poll_timeline_vote_not_registered_title" = "Stimme nicht erfasst"; +"poll_edit_form_post_failure_subtitle" = "Versuche es bitte erneut"; +"poll_edit_form_post_failure_title" = "Fehler beim Senden der Abstimmung"; +"share_extension_low_quality_video_message" = "Für eine bessere Qualität sende es in %@ oder sende es in niedriger Qualität."; +"share_extension_low_quality_video_title" = "Das Video wird in niedriger Qualität gesendet werden"; +"analytics_prompt_stop" = "Teilen beenden"; +"analytics_prompt_not_now" = "Nicht jetzt"; +"analytics_prompt_yes" = "Das ist Okay"; +"analytics_prompt_point_3" = "Du kannst dies jederzeit in den Einstellungen deaktivieren"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Wir erfassen und analysieren keine Accountdaten"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Alle unsere Bedingungen lesen %@. Bist du damit einverstanden?"; +"ok" = "OK"; +"location_sharing_settings_toggle_title" = "Standortfreigabe aktivieren"; +"location_sharing_settings_header" = "Standortfreigabe"; +"location_sharing_open_google_maps" = "In Google Maps öffnen"; +"location_sharing_open_apple_maps" = "In Apple Karten öffnen"; +"location_sharing_invalid_authorization_settings" = "Einstellungen"; +"location_sharing_invalid_authorization_not_now" = "Nicht jetzt"; +"location_sharing_invalid_authorization_error_title" = "%@ besitzt keine Berechtigung um auf deinen Standort zuzugreifen. Du kannst den Zugriff unter Einstellungen > Standort erlauben"; +"location_sharing_locating_user_error_title" = "%@ konnte nicht auf deinen Standort zugreifen. Bitte versuche es später noch einmal."; +"location_sharing_loading_map_error_title" = "%@ konnte die Karte nicht laden. Bitte versuche es später noch einmal."; +"location_sharing_share_action" = "Teilen"; +"location_sharing_close_action" = "Schließen"; + +// MARK: - Location sharing + +"location_sharing_title" = "Standort"; +"onboarding_splash_page_4_title_no_pun" = "Nachrichtenaustausch für dein Team."; +"onboarding_splash_page_3_title" = "Sicherer Nachrichtenaustausch."; +"onboarding_splash_page_2_message" = "Wähle wo deine Gespräche liegen, für Kontrolle und Unabhängigkeit. Verbunden mit Matrix."; +"onboarding_splash_page_1_title" = "Nimm deine Gespräche in die eigene Hand."; +"onboarding_splash_login_button_title" = "Ich habe bereits ein Konto"; +"onboarding_splash_page_4_message" = "Element ist auch für die Arbeit großartig. Uns vertrauen einige der sichersten Organisationen der Welt."; +"onboarding_splash_page_3_message" = "Ende-zu-Ende-verschlüsselt und ohne Telefonnummer nutzbar. Keine Werbung oder Datenerfassung."; +"onboarding_splash_page_2_title" = "Du hast die Kontrolle."; +"onboarding_splash_page_1_message" = "Sichere und unabhängige Kommunikation, die für die gleiche Vertraulichkeit sorgt, wie ein Gespräch von Angesicht zu Angesicht in deinem eigenen Zuhause."; + +// Onboarding +"onboarding_splash_register_button_title" = "Konto erstellen"; +"settings_enable_room_message_bubbles" = "Nachrichtenblasen"; diff --git a/Riot/Assets/en.lproj/InfoPlist.strings b/Riot/Assets/en.lproj/InfoPlist.strings index 6d3378a2a..a9513a6ed 100644 --- a/Riot/Assets/en.lproj/InfoPlist.strings +++ b/Riot/Assets/en.lproj/InfoPlist.strings @@ -21,3 +21,4 @@ "NSContactsUsageDescription" = "Element will show your contacts so you can invite them to chat."; "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."; diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index 914bb11e2..f3547d667 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -71,6 +71,9 @@ /* New file message from a specific person, not referencing a room. */ "FILE_FROM_USER" = "%@ sent a file %@"; +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ shared their location"; + /* A single unread message in a room */ "SINGLE_UNREAD_IN_ROOM" = "You received a message in %@"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0102e0fac..be085a6ef 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -41,6 +41,7 @@ "retry" = "Retry"; "on" = "On"; "off" = "Off"; +"enable" = "Enable"; "cancel" = "Cancel"; "save" = "Save"; "join" = "Join"; @@ -65,6 +66,10 @@ "less" = "Less"; "open" = "Open"; "done" = "Done"; +"private" = "Private"; +"public" = "Public"; +"stop" = "Stop"; +"ok" = "OK"; // Call Bar "callbar_only_single_active" = "Tap to return to the call (%@)"; @@ -77,6 +82,19 @@ // Accessibility "accessibility_checkbox_label" = "checkbox"; +"accessibility_button_label" = "button"; + +// Onboarding +"onboarding_splash_register_button_title" = "Create account"; +"onboarding_splash_login_button_title" = "I already have an account"; +"onboarding_splash_page_1_title" = "Own your conversations."; +"onboarding_splash_page_1_message" = "Secure and independent communication that gives you the same level of privacy as a face-to-face conversation in your own home."; +"onboarding_splash_page_2_title" = "You’re in control."; +"onboarding_splash_page_2_message" = "Choose where your conversations are kept, giving you control and independence. Connected via Matrix."; +"onboarding_splash_page_3_title" = "Secure messaging."; +"onboarding_splash_page_3_message" = "End-to-end encrypted and no phone number required. No ads or datamining."; +"onboarding_splash_page_4_title_no_pun" = "Messaging for your team."; +"onboarding_splash_page_4_message" = "Element is also great for the workplace. It’s trusted by the world’s most secure organisations."; // Authentication "auth_login" = "Log in"; @@ -268,6 +286,7 @@ Tap the + to start adding people."; "room_participants_remove_third_party_invite_prompt_msg" = "Are you sure you want to revoke this invite?"; "room_participants_invite_prompt_title" = "Confirmation"; "room_participants_invite_prompt_msg" = "Are you sure you want to invite %@ to this chat?"; +"room_participants_invite_prompt_to_msg" = "Are you sure you want to invite %@ to %@?"; "room_participants_filter_room_members" = "Filter room members"; "room_participants_filter_room_members_for_dm" = "Filter members"; "room_participants_invite_another_user" = "Search / invite by User ID, Name or email"; @@ -323,7 +342,8 @@ Tap the + to start adding people."; "room_member_power_level_short_moderator" = "Mod"; "room_member_power_level_short_custom" = "Custom"; -// Chat +// MARK: - Chat + "room_slide_to_end_group_call" = "Slide to end the call for everyone"; "room_jump_to_first_unread" = "Jump to unread"; "room_accessiblity_scroll_to_bottom" = "Scroll to bottom"; @@ -363,7 +383,8 @@ Tap the + to start adding people."; "room_event_action_more" = "More"; "room_event_action_share" = "Share"; "room_event_action_forward" = "Forward"; -"room_event_action_permalink" = "Permalink"; +"room_event_action_view_in_room" = "View in room"; +"room_event_action_permalink" = "Copy link to message"; "room_event_action_view_source" = "View Source"; "room_event_action_view_decrypted_source" = "View Decrypted Source"; "room_event_action_report" = "Report content"; @@ -380,10 +401,12 @@ Tap the + to start adding people."; "room_event_action_cancel_download" = "Cancel Download"; "room_event_action_view_encryption" = "Encryption Information"; "room_event_action_reply" = "Reply"; +"room_event_action_reply_in_thread" = "Thread"; "room_event_action_edit" = "Edit"; "room_event_action_reaction_show_all" = "Show all"; "room_event_action_reaction_show_less" = "Show less"; "room_event_action_reaction_history" = "Reaction history"; +"room_event_copy_link_info" = "Link copied to clipboard."; "room_warning_about_encryption" = "End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption."; "room_event_failed_to_send" = "Failed to send"; "room_action_camera" = "Take photo or video"; @@ -408,12 +431,27 @@ Tap the + to start adding people."; "room_accessibility_upload" = "Upload"; "room_accessibility_call" = "Call"; "room_accessibility_video_call" = "Video Call"; +"room_accessibility_threads" = "Threads"; "room_accessibility_hangup" = "Hang up"; +"room_accessibility_thread_more" = "More"; "room_place_voice_call" = "Voice call"; "room_open_dialpad" = "Dial pad"; "room_join_group_call" = "Join"; "room_no_privileges_to_create_group_call" = "You need to be an admin or a moderator to start a call."; +// MARK: Threads +"room_thread_title" = "Thread"; +"thread_copy_link_to_thread" = "Copy link to thread"; +"threads_title" = "Threads"; +"threads_action_all_threads" = "All threads"; +"threads_action_my_threads" = "My threads"; +"threads_empty_title" = "Keep discussions organised with threads"; +"threads_empty_info_all" = "Threads help keep your conversations on-topic and easy to track."; +"threads_empty_info_my" = "Reply to an ongoing thread or tap a message and use “Thread” to start a new one."; +"threads_empty_tip" = "Tip: Tap a message and use “Thread” to start one."; +"threads_empty_show_all_threads" = "Show all threads"; +"message_from_a_thread" = "From a thread"; + "media_type_accessibility_image" = "Image"; "media_type_accessibility_audio" = "Audio"; "media_type_accessibility_video" = "Video"; @@ -570,6 +608,7 @@ Tap the + to start adding people."; "settings_labs_message_reaction" = "React to messages with emoji"; "settings_labs_enable_ringing_for_group_calls" = "Ring for group calls"; "settings_labs_enabled_polls" = "Polls"; +"settings_labs_enable_threads" = "Threaded messaging"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; @@ -577,7 +616,7 @@ Tap the + to start adding people."; "settings_term_conditions" = "Terms & Conditions"; "settings_privacy_policy" = "Privacy Policy"; "settings_third_party_notices" = "Third-party Notices"; -"settings_send_crash_report" = "Send anon crash & usage data"; +"settings_analytics_and_crash_data" = "Send crash and analytics data"; "settings_enable_rageshake" = "Rage shake to report bug"; "settings_clear_cache" = "Clear cache"; @@ -652,6 +691,8 @@ Tap the + to start adding people."; "settings_show_NSFW_public_rooms" = "Show NSFW public rooms"; +"settings_enable_room_message_bubbles" = "Message bubbles"; + // Security settings "security_settings_title" = "Security"; "security_settings_crypto_sessions" = "MY SESSIONS"; @@ -902,6 +943,7 @@ Tap the + to start adding people."; "event_formatter_group_call_join" = "Join"; "event_formatter_group_call_leave" = "Leave"; "event_formatter_group_call_incoming" = "%@ in %@"; +"event_formatter_message_deleted" = "Message deleted"; // Events formatter with you "event_formatter_widget_added_by_you" = "You added the widget: %@"; @@ -945,8 +987,24 @@ Tap the + to start adding people."; "no_voip_title" = "Incoming call"; "no_voip" = "%@ is calling you but %@ does not support calls yet.\nYou can ignore this notification and answer the call from another device or you can reject it."; -// Crash report -"google_analytics_use_prompt" = "Would you like to help improve %@ by automatically reporting anonymous crash reports and usage data?"; +// Analytics +"analytics_prompt_title" = "Help improve %@"; +"analytics_prompt_message_new_user" = "Help us identify issues and improve %@ by sharing anonymous usage data. To understand how people use multiple devices, we’ll generate a random identifier, shared by your devices."; +"analytics_prompt_message_upgrade" = "You previously consented to share anonymous usage data with us. Now, to help understand how people use multiple devices, we’ll generate a random identifier, shared by your devices."; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "You can read all our terms %@."; +"analytics_prompt_terms_link_new_user" = "here"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Read all our terms %@. Is that OK?"; +"analytics_prompt_terms_link_upgrade" = "here"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "We don't record or profile any account data"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "We don't share information with third parties"; +"analytics_prompt_point_3" = "You can turn this off anytime in settings"; +"analytics_prompt_not_now" = "Not now"; +"analytics_prompt_yes" = "Yes, that's fine"; +"analytics_prompt_stop" = "Stop sharing"; // Crypto "e2e_enabling_on_app_update" = "%@ now supports end-to-end encryption but you need to log in again to enable it.\n\nYou can do it now or later from the application settings."; @@ -1671,6 +1729,16 @@ Tap the + to start adding people."; "home_empty_view_title" = "Welcome to %@,\n%@"; "home_empty_view_information" = "The all-in-one secure chat app for teams, friends and organisations. Tap the + button below to add people and rooms."; +"home_context_menu_make_dm" = "Move to People"; +"home_context_menu_make_room" = "Move to Rooms"; +"home_context_menu_notifications" = "Notifications"; +"home_context_menu_mute" = "Mute"; +"home_context_menu_unmute" = "Unmute"; +"home_context_menu_favourite" = "Favourite"; +"home_context_menu_unfavourite" = "Remove from Favourites"; +"home_context_menu_low_priority" = "Low priority"; +"home_context_menu_normal_priority" = "Normal priority"; +"home_context_menu_leave" = "Leave"; // MARK: - Favourites @@ -1682,6 +1750,12 @@ Tap the + to start adding people."; "invite_friends_action" = "Invite friends to %@"; "invite_friends_share_text" = "Hey, talk to me on %@: %@"; +// MARK: - Share invite link + +"share_invite_link_action" = "Share invite link"; +"share_invite_link_room_text" = "Hey, join this room on %@"; +"share_invite_link_space_text" = "Hey, join this space on %@"; + // Mark: - Room avatar view "room_avatar_view_accessibility_label" = "avatar"; @@ -1704,6 +1778,15 @@ Tap the + to start adding people."; "room_intro_cell_information_dm_sentence2" = "Only the two of you are in this conversation, no one else can join."; "room_intro_cell_information_multiple_dm_sentence2" = "Only you are in this conversation, unless any of you invites someone to join."; +// Mark: - Room invite + +"room_invite_to_space_option_title" = "To %@"; +"room_invite_to_space_option_detail" = "They can explore %@, but won’t be a member of %@."; +"room_invite_to_room_option_title" = "To just this room"; +"room_invite_to_room_option_detail" = "They won’t be a part of %@."; +"room_invite_not_enough_permission" = "You do not have permission to invite people to this room"; +"space_invite_not_enough_permission" = "You do not have permission to invite people to this space"; + // Mark: - Spaces "space_feature_unavailable_title" = "Spaces aren’t here yet"; @@ -1716,6 +1799,8 @@ Tap the + to start adding people."; "space_beta_announce_information" = "Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop."; "spaces_home_space_title" = "Home"; +"spaces_add_space_title" = "Create space"; +"spaces_create_space_title" = "Create a space"; "spaces_left_panel_title" = "Spaces"; "leave_space_title" = "Leave %@"; "leave_space_message" = "Are you sure you want to leave %@? Do you also want to leave all rooms and spaces of this space?"; @@ -1733,13 +1818,75 @@ Tap the + to start adding people."; "spaces_coming_soon_title" = "Coming soon"; "spaces_add_rooms_coming_soon_title" = "Adding rooms coming soon"; "spaces_invites_coming_soon_title" = "Invites coming soon"; -"spaces_coming_soon_detail" = "This feature hasn’t been implemented here, but it’s on the way. For now, you can do that with Element on your computer."; +"spaces_coming_soon_detail" = "This feature hasn’t been implemented here, but it’s on the way. For now, you can do that with %@ on your computer."; "space_participants_action_remove" = "Remove from this space"; "space_participants_action_ban" = "Ban from this space"; "space_home_show_all_rooms" = "Show all rooms"; "space_private_join_rule" = "Private space"; +"space_private_join_rule_detail" = "Invite only, best for yourself or teams"; "space_public_join_rule" = "Public space"; +"spaces_invite_people" = "Invite people"; +"spaces_add_room" = "Add room"; +"spaces_add_space" = "Add space"; +"space_public_join_rule_detail" = "Open to anyone, best for communities"; + +"space_topic" = "description"; + +// Mark: - Space Creation + +"spaces_creation_hint" = "Spaces are a new way to group rooms and people."; +"spaces_creation_visibility_title" = "What type of space do you want to create?"; +"spaces_creation_visibility_message" = "To join an existing space, you need an invite."; +"spaces_creation_footer" = "You can change this later"; +"spaces_creation_settings_message" = "Add some details to help it stand out. You can change these at any point."; +"spaces_creation_address" = "Address"; +"spaces_creation_empty_room_name_error" = "Name required"; +"spaces_creation_address_default_message" = "Your space will be viewable at\n%@"; +"spaces_creation_address_invalid_characters" = "%@\nhas invalid characters"; +"spaces_creation_address_already_exists" = "%@\nalready exists"; +"spaces_creation_public_space_title" = "Your public space"; +"spaces_creation_private_space_title" = "Your private space"; +"spaces_creation_cancel_title" = "Stop creating a space?"; +"spaces_creation_cancel_message" = "Your progress will be lost."; + +"spaces_creation_new_rooms_title" = "What are some discussions you’ll have?"; +"spaces_creation_new_rooms_message" = "We’ll create a room for each one."; +"spaces_creation_new_rooms_room_name_title" = "Room name"; +"spaces_creation_new_rooms_general" = "General"; +"spaces_creation_new_rooms_random" = "Random"; +"spaces_creation_new_rooms_support" = "Support"; + +"spaces_creation_email_invites_title" = "Invite your team"; +"spaces_creation_email_invites_message" = "You can invite them later too."; +"spaces_creation_email_invites_email_title" = "Email"; + +"spaces_creation_sharing_type_title" = "Who are you working with?"; +"spaces_creation_sharing_type_message" = "Make sure the right people have access %@. You can change this later."; +"spaces_creation_sharing_type_just_me_title" = "Just me"; +"spaces_creation_sharing_type_just_me_detail" = "A private space to organise your rooms"; +"spaces_creation_sharing_type_me_and_teammates_title" = "Me and teammates"; +"spaces_creation_sharing_type_me_and_teammates_detail" = "A private space for you & your teammates"; + +"spaces_creation_add_rooms_title" = "What do you want to add?"; +"spaces_creation_add_rooms_message" = "As this space is just for you, no one will be informed. You can add more later."; + +"spaces_creation_invite_by_username" = "Invite by username"; +"spaces_creation_invite_by_username_title" = "Invite your team"; +"spaces_creation_invite_by_username_message" = "You can invite them later too."; + +"spaces_creation_post_process_creating_space" = "Creating space"; +"spaces_creation_post_process_creating_space_task" = "Creating %@"; +"spaces_creation_post_process_uploading_avatar" = "Uploading avatar"; +"spaces_creation_post_process_creating_room" = "Creating %@"; +"spaces_creation_post_process_adding_rooms" = "Adding %@ rooms"; +"spaces_creation_post_process_inviting_users" = "Inviting %@ users"; + +"spaces_creation_in_spacename" = "in %@"; +"spaces_creation_in_spacename_plus_one" = "in %@ + 1 space"; +"spaces_creation_in_spacename_plus_many" = "in %@ + %@ spaces"; +"spaces_creation_in_many_spaces" = "in %@ spaces"; +"spaces_creation_in_one_space" = "in 1 space"; // Mark: Avatar @@ -1788,6 +1935,8 @@ Tap the + to start adding people."; "poll_edit_form_create_poll" = "Create poll"; +"poll_edit_form_poll_type" = "Poll type"; + "poll_edit_form_poll_question_or_topic" = "Poll question or topic"; "poll_edit_form_question_or_topic" = "Question or topic"; @@ -1804,7 +1953,17 @@ Tap the + to start adding people."; "poll_edit_form_post_failure_subtitle" = "Please try again"; -"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_update_failure_title" = "Failed to update poll"; + +"poll_edit_form_update_failure_subtitle" = "Please try again"; + +"poll_edit_form_poll_type_open" = "Open poll"; + +"poll_edit_form_poll_type_open_description" = "Voters see results as soon as they have voted"; + +"poll_edit_form_poll_type_closed" = "Closed poll"; + +"poll_edit_form_poll_type_closed_description" = "Results are only revealed when you end the poll"; "poll_timeline_one_vote" = "1 vote"; @@ -1828,10 +1987,36 @@ Tap the + to start adding people."; "poll_timeline_vote_not_registered_subtitle" = "Sorry, your vote was not registered, please try again"; -"poll_timeline_vote_not_registered_action" = "OK"; - "poll_timeline_not_closed_title" = "Failed to end poll"; "poll_timeline_not_closed_subtitle" = "Please try again"; -"poll_timeline_not_closed_action" = "OK"; +// MARK: - Location sharing + +"location_sharing_title" = "Location"; + +"location_sharing_close_action" = "Close"; + +"location_sharing_share_action" = "Share"; + +"location_sharing_post_failure_title" = "We couldn’t send your location"; + +"location_sharing_post_failure_subtitle" = "%@ could not send your location. Please try again later."; + +"location_sharing_loading_map_error_title" = "%@ could not load the map. Please try again later."; + +"location_sharing_locating_user_error_title" = "%@ could not access your location. Please try again later."; + +"location_sharing_invalid_authorization_error_title" = "%@ does not have permission to access your location. You can enable access in Settings > Location"; + +"location_sharing_invalid_authorization_not_now" = "Not now"; + +"location_sharing_invalid_authorization_settings" = "Settings"; + +"location_sharing_open_apple_maps" = "Open in Apple Maps"; + +"location_sharing_open_google_maps" = "Open in Google Maps"; + +"location_sharing_settings_header" = "Location sharing"; + +"location_sharing_settings_toggle_title" = "Enable location sharing"; diff --git a/Riot/Assets/et.lproj/InfoPlist.strings b/Riot/Assets/et.lproj/InfoPlist.strings index 25a5d9479..e556d1d6a 100644 --- a/Riot/Assets/et.lproj/InfoPlist.strings +++ b/Riot/Assets/et.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSCalendarsUsageDescription" = "Vaata päevakavasse lisatud koosolekuid vastvast rakendusest."; "NSContactsUsageDescription" = "Element näitab sulle tuttavaid, kellega saad alustada vestlust."; "NSFaceIDUsageDescription" = "Ligipääsuks sinu rakendusele on kasutusel Face ID."; +"NSLocationWhenInUseUsageDescription" = "Kui sa jagad teiste kasutajatega oma asukohta, siis Element vajab õigusi asukoha kuvamiseks kaardil."; diff --git a/Riot/Assets/et.lproj/Localizable.strings b/Riot/Assets/et.lproj/Localizable.strings index 6e4824651..844410852 100644 --- a/Riot/Assets/et.lproj/Localizable.strings +++ b/Riot/Assets/et.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Teavitus"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ jagas oma asukohta"; diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 9e209a1f2..c88b9e297 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -1420,7 +1420,7 @@ "space_private_join_rule" = "Privaatne kogukond"; "space_participants_action_ban" = "Sea selles kogukonnakeskus suhtluskeeld"; "space_participants_action_remove" = "Eemalda sellest kogukonnakeskusest"; -"spaces_coming_soon_detail" = "See funktsionaalsus pole siin rakenduses hetkel veel saadaval, aga üsna varsti saab olema. Seni saad sa seda toimingut teha Element'i töölauarakenduses."; +"spaces_coming_soon_detail" = "See funktsionaalsus pole siin rakenduses hetkel veel saadaval, aga üsna varsti saab olema. Seni saad sa seda toimingut teha %@'i töölauarakenduses."; "spaces_invites_coming_soon_title" = "Varsti lisandub kutsete saatmine"; "spaces_add_rooms_coming_soon_title" = "Varsti on jututubade lisamine võimalik"; "spaces_coming_soon_title" = "Mõne aja pärast on meil uuendusi"; @@ -1474,7 +1474,7 @@ "share_extension_low_quality_video_title" = "Saadame video madalama kvalitediga"; "settings_about" = "TEAVE MEIST"; "poll_edit_form_add_option" = "Lisa valik"; -"poll_edit_form_option_number" = "Valik %d"; +"poll_edit_form_option_number" = "Valik %lu"; "poll_edit_form_create_options" = "Koosta valikud"; "poll_edit_form_input_placeholder" = "Kirjuta midagi"; "poll_edit_form_question_or_topic" = "Küsimus või teema"; @@ -1484,3 +1484,75 @@ "poll_edit_form_create_poll" = "Koosta üks küsitlus"; "settings_discovery_accept_terms" = "Nõustu isikutuvastusserveri tingimustega"; +"poll_timeline_not_closed_action" = "Sobib"; +"poll_timeline_not_closed_subtitle" = "Palun proovi uuesti"; +"poll_timeline_not_closed_title" = "Küsitluse lõpetamine ei õnnestunud"; +"poll_timeline_vote_not_registered_action" = "Sobib"; +"poll_timeline_vote_not_registered_subtitle" = "Vabandust, aga sinu valik jäi salvestamata. Palun proovi uuesti"; +"poll_timeline_vote_not_registered_title" = "Hääl ei salvestunud"; +"poll_timeline_total_final_results" = "%lu'l häälel põhinev lõpptulemus"; +"poll_timeline_total_final_results_one_vote" = "Ühel häälel põhinev lõpptulemus"; +"poll_timeline_total_votes_not_voted" = "%lu hääletanut. Tulemuste nägemiseks osale ise ka küsitluses"; +"poll_timeline_total_one_vote_not_voted" = "1 hääletanu. Tulemuste nägemiseks osale ise ka küsitluses"; +"poll_timeline_total_votes" = "%lu hääletanut"; +"poll_timeline_total_one_vote" = "1 hääletanu"; +"poll_timeline_total_no_votes" = "Hääletanuid ei ole"; +"poll_timeline_votes_count" = "%lu häält"; +"poll_timeline_one_vote" = "1 hääl"; +"poll_edit_form_post_failure_action" = "Sobib"; +"poll_edit_form_post_failure_subtitle" = "Palun proovi uuesti"; +"poll_edit_form_post_failure_title" = "Küsitluse üleslaadimine ei õnnestunud"; +"settings_labs_enabled_polls" = "Küsitlused"; +"room_event_action_end_poll" = "Lõpeta küsitlus"; +"room_event_action_remove_poll" = "Kustuta küsitlus"; +"analytics_prompt_stop" = "Lõpeta andmete jagamine"; +"analytics_prompt_yes" = "Jah, see on sobilik"; +"analytics_prompt_not_now" = "Mitte praegu"; +"analytics_prompt_point_3" = "Seadistustest saad alati määrata, et see funktsionaalsus pole kasutusel"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Meie ei jaga teavet kolmandate osapooltega"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Meie ei salvesta ega profileeri sinu kasutajakonto andmeid"; +"analytics_prompt_terms_link_upgrade" = "siit"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Kõik meie tingimused leiad %@. Kas sa oled nõus?"; +"analytics_prompt_terms_link_new_user" = "siit"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Meie kasutustingimused leiad %@."; +"analytics_prompt_message_upgrade" = "Sa oled varem nõustunud meiega anonüümsete andmete jagamisega. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad, me loome sinu seadmetele ühise juhusliku tunnuse."; +"analytics_prompt_message_new_user" = "Võimalike vigade leidmiseks ja %@'i arendamiseks jaga meiega anonüümseid andmeid. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad, me loome sinu seadmetele ühise juhusliku tunnuse."; + +// Analytics +"analytics_prompt_title" = "Aita arendada %@ rakendust"; +"settings_analytics_and_crash_data" = "Saada rakenduse vigade ja analüütika andmeid"; +"accessibility_button_label" = "nupp"; +"enable" = "Võta kasutusele"; +"location_sharing_settings_toggle_title" = "Luba asukohta jagada"; +"location_sharing_settings_header" = "Asukoha jagamine"; +"location_sharing_open_google_maps" = "Ava rakendusega Google Maps"; +"location_sharing_open_apple_maps" = "Ava rakendusega Apple Maps"; +"location_sharing_invalid_authorization_settings" = "Seadistused"; +"location_sharing_invalid_authorization_not_now" = "Mitte praegu"; +"location_sharing_invalid_authorization_error_title" = "%@ vajab asukoha määramiseks õigusi, mida saad määrata Seadistused > Asukoht valikust"; +"location_sharing_locating_user_error_title" = "%@ ei saanud asukohta tuvastada. Palun proovi hiljem uuesti."; +"location_sharing_loading_map_error_title" = "%@ ei saanud kaarti avada. Palun proovi hiljem uuesti."; +"location_sharing_share_action" = "Jaga"; +"location_sharing_close_action" = "Sulge"; + +// MARK: - Location sharing + +"location_sharing_title" = "Asukoht"; +"ok" = "Sobib"; +"settings_enable_room_message_bubbles" = "Jutumullid"; +"onboarding_splash_page_4_message" = "Element sobib ideaalselt kasutamiseks töökeskkonnas. Ta on kasutusel ka mitmetes üliturvalistes organisatsioonides."; +"onboarding_splash_page_4_title_no_pun" = "Sõnumisuhtlus sinu tiimi või kogukonna jaoks."; +"onboarding_splash_page_3_message" = "Tagatud on andmete läbiv krüptimine ning oma telefoninumbrit ei pea sa jagama. Pole reklaame ega sinu andmete kogumist."; +"onboarding_splash_page_3_title" = "Turvaline sõnumisuhtlus."; +"onboarding_splash_page_2_message" = "Sa ise valid serveri, kus sinu vestlusi hoitakse ning sellega tagadki kontrolli oma andmete üle. Lahendus põhineb Matrix'i võrgul."; +"onboarding_splash_page_2_title" = "Sul on kontroll oma andmete üle."; +"onboarding_splash_page_1_message" = "Turvaline ja sõltumatu suhtluslahendus, mis tagab sama privaatsuse, kui omavaheline vestlus sinu kodus."; +"onboarding_splash_page_1_title" = "Vestlused, mida sa tegelikult ka omad."; +"onboarding_splash_login_button_title" = "Mul on kasutajakonto juba olemas"; + +// Onboarding +"onboarding_splash_register_button_title" = "Loo kasutajakonto"; diff --git a/Riot/Assets/fr.lproj/InfoPlist.strings b/Riot/Assets/fr.lproj/InfoPlist.strings index 464aeac9e..c2a08df8d 100644 --- a/Riot/Assets/fr.lproj/InfoPlist.strings +++ b/Riot/Assets/fr.lproj/InfoPlist.strings @@ -2,6 +2,7 @@ "NSCameraUsageDescription" = "L’appareil photo est utilisé pour prendre des photos, des vidéos et pour passer des appels vidéo."; "NSPhotoLibraryUsageDescription" = "La photothèque est utilisée pour envoyer des photos et des vidéos."; "NSMicrophoneUsageDescription" = "Element doit avoir accès au microphone pour passer des appels, capturer des vidéos et enregistrer des messages vocaux."; -"NSContactsUsageDescription" = "Pour découvrir vos contacts qui utilisent déjà Matrix, Element peut envoyer les adresses e-mail et les numéros de téléphone de votre carnet d’adresse à votre serveur d’identité Matrix. Si votre serveur d’identité le prend en charge, les données personnelles sont hachées avant l’envoi − vérifiez sa politique de confidentialité pour plus de détails."; +"NSContactsUsageDescription" = "Element affichera vos contacts pour que vous puissiez les inviter à parler."; "NSCalendarsUsageDescription" = "Voir vos rendez-vous dans l’application."; "NSFaceIDUsageDescription" = "Face ID est utilisé pour accéder à votre application."; +"NSLocationWhenInUseUsageDescription" = "Element doit accéder à votre emplacement pour vous permettre de la partager aux autres utilisateurs sur une carte."; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 6ff0e4091..0f012f74f 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -62,7 +62,7 @@ "auth_missing_phone" = "Numéro de téléphone manquant"; "auth_missing_email_or_phone" = "Adresse e-mail ou numéro de téléphone manquant"; "auth_password_dont_match" = "Les mots de passe ne correspondent pas"; -"auth_username_in_use" = ""; +"auth_username_in_use" = "Nom d’utilisateur déjà pris"; "auth_forgot_password" = "Mot de passe oublié ?"; "auth_use_server_options" = "Utiliser un serveur personnalisé (avancé)"; "auth_email_validation_message" = "Merci de vérifier vos e-mails pour continuer l’inscription"; @@ -94,7 +94,7 @@ "room_creation_keep_private" = "Garder privée"; "room_creation_make_private" = "Rendre privée"; "room_creation_wait_for_creation" = "Un salon est déjà en cours de création. Veuillez patienter."; -"room_creation_invite_another_user" = "Rechercher/inviter par identifiant, nom ou e-mail"; +"room_creation_invite_another_user" = "Identifiant, nom ou e-mail"; // Room recents "room_recents_directory_section" = "RÉPERTOIRE DES SALONS"; "room_recents_favourites_section" = "FAVORIS"; @@ -251,9 +251,9 @@ "settings_user_settings" = "PRÉFÉRENCES UTILISATEUR"; "settings_notifications_settings" = "PRÉFÉRENCES DE NOTIFICATIONS"; "settings_ignored_users" = "UTILISATEURS IGNORÉS"; -"settings_contacts" = "CONTACTS LOCAUX"; +"settings_contacts" = "CONTACTS DE L’APPAREIL"; "settings_advanced" = "AVANCÉ"; -"settings_other" = "AUTRES"; +"settings_other" = "Autres"; "settings_labs" = "EXPÉRIMENTAL"; "settings_devices" = "SESSIONS"; "settings_cryptography" = "CHIFFREMENT"; @@ -1492,7 +1492,7 @@ "space_private_join_rule" = "Espace privé"; "space_participants_action_ban" = "Supprimer de cet espace"; "space_participants_action_remove" = "Enlever de cet espace"; -"spaces_coming_soon_detail" = "Cette fonctionnalité n’a pas été implémentée ici, mais elle arrive. Pour effectuer cette action, vous pouvez utiliser Element sur votre ordinateur."; +"spaces_coming_soon_detail" = "Cette fonctionnalité n’a pas été implémentée ici, mais elle arrive. Pour effectuer cette action, vous pouvez utiliser %@ sur votre ordinateur."; "spaces_invites_coming_soon_title" = "Les invitations arrivent prochainement"; "spaces_add_rooms_coming_soon_title" = "L’ajout de salons arrive bientôt"; "spaces_coming_soon_title" = "Prochainement"; @@ -1520,3 +1520,116 @@ "room_recents_suggested_rooms_section" = "SALONS RECOMMANDÉS"; "done" = "Terminé"; "open" = "Ouvrir"; +"poll_timeline_not_closed_action" = "Ok"; +"poll_timeline_not_closed_subtitle" = "Merci de réessayer"; +"poll_timeline_not_closed_title" = "Échec de la fermeture du sondage"; +"poll_timeline_vote_not_registered_action" = "Ok"; +"poll_timeline_vote_not_registered_subtitle" = "Désolé votre vote n’a pas été enregistré, veuillez réessayer"; +"poll_timeline_vote_not_registered_title" = "Vote non enregistré"; +"poll_timeline_total_final_results" = "Résultats finaux basés sur %lu votes"; +"poll_timeline_total_final_results_one_vote" = "Résultats finaux basés sur 1 vote"; +"poll_timeline_total_votes_not_voted" = "%lu votes recueillis. Votez pour consulter les résultats"; +"poll_timeline_total_one_vote_not_voted" = "1 vote recueilli. Votez pour connaître les résultats"; +"poll_timeline_total_votes" = "%lu votes recueillis"; +"poll_timeline_total_one_vote" = "1 vote recueilli"; +"poll_timeline_total_no_votes" = "Aucun vote recueilli"; +"poll_timeline_votes_count" = "%lu votes"; +"poll_timeline_one_vote" = "1 vote"; +"poll_edit_form_post_failure_action" = "Ok"; +"poll_edit_form_post_failure_subtitle" = "Merci de réessayer"; +"poll_edit_form_post_failure_title" = "Échec de la publication du sondage"; +"poll_edit_form_add_option" = "Ajouter une option"; +"poll_edit_form_option_number" = "Option %lu"; +"poll_edit_form_create_options" = "Ajouter des options"; +"poll_edit_form_input_placeholder" = "Écrivez quelque chose"; +"poll_edit_form_question_or_topic" = "Question ou sujet"; +"poll_edit_form_poll_question_or_topic" = "Question ou sujet du sondage"; + +// Mark: - Polls + +"poll_edit_form_create_poll" = "Créer un sondage"; +"space_home_show_all_rooms" = "Afficher tous les salons"; +"service_terms_modal_information_description_integration_manager" = "Un gestionnaire d’intégrations vous permet d’ajouter des fonctionnalités de tierces-parties."; +"service_terms_modal_information_description_identity_server" = "Un serveur d’identité vous aide à trouver vos contacts, en recherchant leur numéro de téléphone ou adresse e-mail pour vérifier s’ils ont déjà un compte."; +"service_terms_modal_information_title_integration_manager" = "Gestionnaire d’intégrations"; + +// Alert explaining what an identity server / integration manager is. +"service_terms_modal_information_title_identity_server" = "Serveur d’identité"; +"service_terms_modal_description_integration_manager" = "Ceci vous permettra d’utiliser les robots, passerelles, widgets et jeux d’autocollants."; +"service_terms_modal_description_identity_server" = "Ceci permettra aux personnes qui ont votre numéro de téléphone ou adresse e-mail de sauvegardées dans leurs contacts de vous trouver."; +"service_terms_modal_table_header_integration_manager" = "CONDITIONS D’UTILISATION DU GESTIONNAIRE D’INTÉGRATIONS"; +"service_terms_modal_table_header_identity_server" = "CONDITIONS D’UTILISATION DU SERVEUR D’IDENTITÉ"; +"service_terms_modal_footer" = "Ceci peut être désactivé a n’importe quel moment dans les paramètres."; + +// Service terms +"service_terms_modal_title_message" = "Pour poursuivre, acceptez nos conditions d’utilisation"; +"share_extension_send_now" = "Envoyer maintenant"; +"share_extension_low_quality_video_message" = "Envoyez depuis %@ pour une meilleure qualité. Ou envoyez en mauvaise qualité ci-dessous."; +"share_extension_low_quality_video_title" = "La vidéo sera envoyée en mauvaise qualité"; +"analytics_prompt_stop" = "Arrêter de partager"; +"analytics_prompt_yes" = "Oui, ça me va"; +"analytics_prompt_not_now" = "Pas maintenant"; +"analytics_prompt_point_3" = "Vous pouvez désactiver ceci à tout moment dans les paramètres"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Nous ne partageons pas les données avec des entités tierces"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Nous n’enregistrons et ne profilons pas de données liées à votre compte"; +"analytics_prompt_terms_link_upgrade" = "ici"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Lisez nos conditions d’utilisation %@. Êtes vous d’accord ?"; +"analytics_prompt_terms_link_new_user" = "ici"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Vous pouvez lire nos conditions d’utilisation %@."; +"analytics_prompt_message_upgrade" = "Vous aviez consenti précédemment à partager des rapports d’utilisation avec nous. Désormais, pour nous aider à comprendre comment les gens utilisent cette application sur plusieurs appareils, nous allons générer un identifiant aléatoire commun à tous vos appareils."; +"analytics_prompt_message_new_user" = "Aidez nous à identifier les problèmes et améliorer %@ en envoyant des rapports d’usage anonymes. Pour comprendre de quelle manière les gens utilisent Element sur plusieurs appareils, nous créeront un identifiant aléatoire commun à tous vos appareils."; + +// Analytics +"analytics_prompt_title" = "Aidez à améliorer %@"; +"settings_discovery_accept_terms" = "Accepter les conditions du serveur d’identité"; +"settings_analytics_and_crash_data" = "Envoyer les rapports de plantages et d’utilisation"; +"settings_labs_enabled_polls" = "Sondages"; +"settings_about" = "À PROPOS"; +"settings_phone_contacts" = "CONTACTS DU TÉLÉPHONE"; +"room_event_action_forward" = "Transférer"; +"room_event_action_end_poll" = "Mettre fin au sondage"; +"room_event_action_remove_poll" = "Supprimer le sondage"; +"find_your_contacts_identity_service_error" = "Impossible de se connecter au serveur d’identité."; +"find_your_contacts_button_title" = "Trouvez vos contacts"; +"find_your_contacts_message" = "Permettez à %@ d’accéder à vos contacts pour commencer à discuter rapidement avec ceux que vous connaissez le mieux."; +"contacts_address_book_permission_denied_alert_message" = "Pour activer les contacts, rendez vous dans les paramètres de votre appareil."; +"contacts_address_book_permission_denied_alert_title" = "Contacts désactivés"; +"accessibility_button_label" = "bouton"; +"enable" = "Activer"; +"find_your_contacts_footer" = "Cette fonctionnalité peut être désactivé à tout moment à partir des paramètres."; +"find_your_contacts_title" = "Commencez par lister vos contacts"; +"settings_contacts_enable_sync_description" = "Cette fonctionnalité utilisera votre serveur d'identité pour vous connecter avec vos contacts, ainsi que pour les aider à vous trouver."; +"settings_contacts_enable_sync" = "Trouvez vos contacts"; +"location_sharing_settings_toggle_title" = "Activer le partage de localisation"; +"location_sharing_settings_header" = "Partage de localisation"; +"location_sharing_open_google_maps" = "Ouvrir dans Google Maps"; +"location_sharing_open_apple_maps" = "Ouvrir dans Apple Plans"; +"location_sharing_invalid_authorization_settings" = "Paramètres"; +"location_sharing_invalid_authorization_not_now" = "Pas maintenant"; +"location_sharing_invalid_authorization_error_title" = "%@ n’a pas les autorisations pour accéder à votre localisation. Vous pouvez l’autoriser dans Réglages > Confidentialité"; +"location_sharing_locating_user_error_title" = "%@ n’a pas pu déterminer votre localisation. Veuillez ré-essayer plus tard."; +"location_sharing_loading_map_error_title" = "%@ n’a pas pu charger la carte. Veuillez ré-essayer plus tard."; +"location_sharing_share_action" = "Partager"; +"location_sharing_close_action" = "Fermer"; + +// MARK: - Location sharing + +"location_sharing_title" = "Localisation"; +"settings_enable_room_message_bubbles" = "Messages en bulles"; +"onboarding_splash_page_4_message" = "Element est parfaite pour le bureau. Les sociétés aux plus gros besoins de sécurité lui font confiance."; +"onboarding_splash_page_4_title_no_pun" = "Une messagerie pour votre équipe."; +"onboarding_splash_page_3_message" = "Chiffrée de bout en bout, aucun numéro de téléphone requis. Pas de pub ni d’exploitation de données."; +"onboarding_splash_page_3_title" = "Messagerie sécurisée."; +"onboarding_splash_page_2_message" = "Décidez où vos conversations sont stockées, en toute liberté et indépendance. Grâce à Matrix."; +"onboarding_splash_page_2_title" = "C’est vous qui décidez."; +"onboarding_splash_page_1_message" = "Une messagerie sécurisée et indépendante qui vous garantit le même niveau de confidentialité qu’une conversation en face à face chez vous."; +"onboarding_splash_page_1_title" = "Maîtrisez vos conversations."; +"onboarding_splash_login_button_title" = "J’ai déjà un compte"; + +// Onboarding +"onboarding_splash_register_button_title" = "Créer un compte"; +"ok" = "Ok"; diff --git a/Riot/Assets/hu.lproj/InfoPlist.strings b/Riot/Assets/hu.lproj/InfoPlist.strings index 0edf4d429..75e748c58 100644 --- a/Riot/Assets/hu.lproj/InfoPlist.strings +++ b/Riot/Assets/hu.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSContactsUsageDescription" = "Element megmutatja a névjegyzéket, hogy beszélgetésbe meghívhasd őket."; "NSCalendarsUsageDescription" = "Nézd meg a találkozóidat az alkalmazásban."; "NSFaceIDUsageDescription" = "Arc felismerés használata az alkalmazás eléréséhez."; +"NSLocationWhenInUseUsageDescription" = "Ha megosztod másokkal a földrajzi helyzetedet, akkor az Elementnek hozzáférésre van szüksége a térképen való megjelenítéshez."; diff --git a/Riot/Assets/hu.lproj/Localizable.strings b/Riot/Assets/hu.lproj/Localizable.strings index caa2f34ad..d0ed4447e 100644 --- a/Riot/Assets/hu.lproj/Localizable.strings +++ b/Riot/Assets/hu.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Értesítés"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ megosztotta a földrajzi helyzetét"; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 0d8050079..29e3902c1 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -1483,7 +1483,7 @@ "space_private_join_rule" = "Privát tér"; "space_participants_action_ban" = "Kitiltás a térről"; "space_participants_action_remove" = "Eltávolítás a térről"; -"spaces_coming_soon_detail" = "Ez a lehetőség még nincs meg, de fejlesztés alatt van. Egyenlőre a számítógépeden Elementtel tudod ezt megtenni."; +"spaces_coming_soon_detail" = "Ez a lehetőség még nincs meg, de fejlesztés alatt van. Egyenlőre a számítógépeden %@ alkalmazással tudod ezt megtenni."; "spaces_invites_coming_soon_title" = "Meghívók küldése hamarosan érkezik"; "spaces_add_rooms_coming_soon_title" = "Szobák hozzáadása hamarosan érkezik"; "spaces_coming_soon_title" = "Hamarosan"; @@ -1533,7 +1533,7 @@ "space_home_show_all_rooms" = "Minden szoba megjelenítése"; "room_event_action_forward" = "Továbbítás"; "poll_edit_form_add_option" = "Lehetőség hozzáadása"; -"poll_edit_form_option_number" = "%d lehetőség"; +"poll_edit_form_option_number" = "%lu lehetőség"; "poll_edit_form_create_options" = "Lehetőségek hozzáadása"; "poll_edit_form_input_placeholder" = "Írjon valamit"; "poll_edit_form_question_or_topic" = "Kérdés vagy téma"; @@ -1547,3 +1547,75 @@ "share_extension_low_quality_video_title" = "Alacsony minőségű videó lesz elküldve"; "settings_discovery_accept_terms" = "Azonosítási Szolgáltatás felhasználási feltételeinek elfogadása"; "settings_about" = "NÉVJEGY"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_not_closed_subtitle" = "Kérlek próbáld újra"; +"poll_timeline_not_closed_title" = "Nem sikerült a szavazás lezárása"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_timeline_vote_not_registered_subtitle" = "Sajnos a szavazatod nem lett rögzítve. Kérlek ismételd meg újra"; +"poll_timeline_vote_not_registered_title" = "Szavazás nem sikerült"; +"poll_timeline_total_final_results" = "Végeredmény %lu szavazat alapján"; +"poll_timeline_total_final_results_one_vote" = "Eredmény 1 szavazat alapján"; +"poll_timeline_total_votes_not_voted" = "%lu szavazatot adtak le. Szavazz az eredmény megtekintéséhez"; +"poll_timeline_total_one_vote_not_voted" = "1 szavazatot adtak le. Szavazz az eredmény megtekintéséhez"; +"poll_timeline_total_votes" = "%lu szavazatot adtak le"; +"poll_timeline_total_one_vote" = "1 szavazatot adtak le"; +"poll_timeline_total_no_votes" = "Nem adtak le szavazatot"; +"poll_timeline_votes_count" = "%lu szavazat"; +"poll_timeline_one_vote" = "1 szavazat"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_post_failure_subtitle" = "Kérlek próbáld újra"; +"poll_edit_form_post_failure_title" = "A szavazást nem sikerült beküldeni"; +"analytics_prompt_stop" = "Megosztás megállítása"; +"analytics_prompt_yes" = "Igen, rendben van"; +"analytics_prompt_not_now" = "Nem most"; +"analytics_prompt_point_3" = "Ezt bármikor kikapcsolhatod a beállításokban"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Nem osztjuk meg az információt harmadik féllel"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Nem küldünk és nem profilozunk semmilyen fiók adatot"; +"analytics_prompt_terms_link_upgrade" = "itt"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Olvasd el minden feltételünket: %@. Rendben?"; +"analytics_prompt_terms_link_new_user" = "itt"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Az összes feltételünket elolvashatod itt: %@."; +"analytics_prompt_message_new_user" = "Segíts észrevennünk a hibákat, és jobbá tenni a(z) %@ alkalmazást a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, ami az eszközeid között meg lesz osztva."; + +// Analytics +"analytics_prompt_title" = "Segíts jobbá tenni %@"; +"settings_analytics_and_crash_data" = "Összeomlás és analitikai adatok küldése"; +"settings_labs_enabled_polls" = "Szavazások"; +"room_event_action_end_poll" = "Szavazás lezárása"; +"room_event_action_remove_poll" = "Szavazás törlése"; +"accessibility_button_label" = "gomb"; +"enable" = "Engedélyezés"; +"analytics_prompt_message_upgrade" = "Korábban beleegyeztél, hogy velünk anonimizált adatokat osztasz meg. Most, hogy jobban megértsük, hogyan használnak több eszközt az emberek, véletlenszerű azonosítót állítunk elő amit az eszközeid használni fognak."; +"location_sharing_settings_toggle_title" = "Földrajzi hely megosztás engedélyezése"; +"location_sharing_settings_header" = "Földrajzi hely megosztása"; +"location_sharing_open_google_maps" = "Megnyitás a Google Térképen"; +"location_sharing_open_apple_maps" = "Megnyitás az Apple Térképen"; +"location_sharing_invalid_authorization_settings" = "Beállítások"; +"location_sharing_invalid_authorization_not_now" = "Nem most"; +"location_sharing_invalid_authorization_error_title" = "%@ alkalmazásnak nincs jogosultsága a helyadatod eléréséhez. Engedélyezheted a Beállítások > Helyadatokban"; +"location_sharing_loading_map_error_title" = "%@ nem tudja betölteni a térképet. Próbáld újra később."; +"location_sharing_locating_user_error_title" = "%@ nem fér hozzá a helyadatodhoz. Próbáld újra később."; +"location_sharing_share_action" = "Megoszt"; +"location_sharing_close_action" = "Bezár"; + +// MARK: - Location sharing + +"location_sharing_title" = "Földrajzi helyzet"; +"ok" = "OK"; +"onboarding_splash_page_4_message" = "Element megállja a helyét a munkahelyen is. A világ legbiztonságosabb szervezetei bíznak meg benne."; +"onboarding_splash_page_3_message" = "Telefonszám nélkül végpontok között titkosított. Reklámok és adatbányászat nélkül."; +"onboarding_splash_page_2_message" = "Válaszd meg hol legyenek a beszélgetéseid tárolva, visszaadja az irányítást és függetlenné tesz. Csatlakozva a Matrixhoz."; +"onboarding_splash_page_1_message" = "Biztonságos és független kommunikáció ami olyan biztonságos mintha valakivel négyszemközt beszélgetnél a házadban."; +"settings_enable_room_message_bubbles" = "Üzenet buborékok"; +"onboarding_splash_page_4_title_no_pun" = "Üzenetküldés a csoportodnak."; +"onboarding_splash_page_3_title" = "Biztonságos üzenetküldés."; +"onboarding_splash_page_2_title" = "Te irányítasz."; +"onboarding_splash_page_1_title" = "Az ön beszélgetései csak az öné."; +"onboarding_splash_login_button_title" = "Már van fiókom"; + +// Onboarding +"onboarding_splash_register_button_title" = "Fiók létrehozása"; diff --git a/Riot/Assets/id.lproj/InfoPlist.strings b/Riot/Assets/id.lproj/InfoPlist.strings index e74ebc244..3459f17dd 100644 --- a/Riot/Assets/id.lproj/InfoPlist.strings +++ b/Riot/Assets/id.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "NSCalendarsUsageDescription" = "Lihat pertemuan yang sudah dijadwalkan di aplikasi."; "NSMicrophoneUsageDescription" = "Element membutuhkan akses ke mikrofon Anda untuk melakukan dan menerima panggilan, mengambil video, dan merekam pesan suara."; "NSPhotoLibraryUsageDescription" = "Galeri digunakan untuk mengirim foto dan video."; +"NSLocationWhenInUseUsageDescription" = "Ketika Anda membagikan lokasi Anda ke orang-orang, Element membutuhkan akses untuk menampilkan mereka sebuah peta."; diff --git a/Riot/Assets/id.lproj/Localizable.strings b/Riot/Assets/id.lproj/Localizable.strings index 679a5cbb8..dc244763c 100644 --- a/Riot/Assets/id.lproj/Localizable.strings +++ b/Riot/Assets/id.lproj/Localizable.strings @@ -165,3 +165,6 @@ /** General **/ "NOTIFICATION" = "Notifikasi"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ membagikan lokasinya"; diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index 62d27b60d..a4fddb9be 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -102,7 +102,7 @@ "joined" = "Bergabung"; "collapse" = "tutup"; "store_promotional_text" = "Aplikasi perpesanan dan kolaborasi yang menjaga privasi, pada jaringan terbuka. Terdesentralisasi untuk Anda kendali. Tidak ada penambangan data, tidak ada pintu belakang dan tidak ada akses pihak ketiga."; -"store_full_description" = "Element adalah aplikasi messenger dan kolaborasi tipe baru yang:\n\n1. Menempatkan Anda dalam kendali untuk mempertahankan privasi Anda\n2. Memungkinkan Anda berkomunikasi dengan siapa pun di jaringan Matrix, dan bahkan di luar dengan mengintegrasikan dengan aplikasi seperti Slack\n3. Melindungi Anda dari iklan, menambangan data, pintu belakang, dan taman berdinding\n4. Mengamankan Anda melalui enkripsi ujung-ke-ujung, dengan penandatanganan silang untuk memverifikasi orang lain\n\nElement benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lain karena Element terdesentralisasi dan sumber terbuka.\n\nElement memungkinkan Anda host sendiri - atau memilih host - sehingga Anda memiliki privasi, kepemilikan, dan kontrol data dan obrolan Anda. Ini memberi Anda akses ke jaringan terbuka, jadi Anda tidak hanya terjebak berbicara dengan pengguna Element. Itu sangat aman.\n\nElement dapat melakukan semua ini karena beroperasi pada Matrix - standar untuk komunikasi terdesentralisasi terbuka.\n\nElement menempatkan Anda dalam kendali dengan membiarkan Anda memilih siapa yang menghost percakapan Anda. Dari aplikasi Element, Anda dapat memilih untuk menghost dengan cara yang berbeda:\n\n1. Dapatkan akun gratis pada server publik matrix.org\n2. Host sendiri akun Anda dengan menjalankan server pada perangkat keras Anda sendiri\n3. Mendaftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services\n\nMengapa memilih Element?\n\nMILIKI DATA ANDA: Anda memutuskan di mana untuk menyimpan data dan pesan Anda. Anda memilikinya dan mengendalikannya, bukan perusahaan besar yang menambang data Anda atau memberikan akses ke pihak ketiga.\n\nPESAN DAN KOLABORASI TERBUKA: Anda dapat mengobrol dengan orang lain di jaringan Matrix, jika mereka menggunakan Element atau aplikasi Matrix lain, dan bahkan jika mereka menggunakan sistem perpesanan seperti Slack, IRC atau XMPP.\n\nSANGAT AMAN: Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan penandatanganan silang untuk memverifikasi perangkat anggota obrolan.\n\nKOMUNIKASI LENGKAP: Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.\n\nDI MANA PUN ANDA BERADA: Tetap berkomunikasi di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io/."; +"store_full_description" = "Element adalah aplikasi messenger dan kolaborasi tipe baru yang:\n\n1. Menempatkan Anda dalam kendali untuk mempertahankan privasi Anda\n2. Memungkinkan Anda berkomunikasi dengan siapa pun di jaringan Matrix, dan bahkan di luar dengan mengintegrasikan dengan aplikasi seperti Slack\n3. Melindungi Anda dari iklan, menambangan data, pintu belakang, dan taman berdinding\n4. Mengamankan Anda melalui enkripsi ujung-ke-ujung, dengan penandatanganan silang untuk memverifikasi orang lain\n\nElement benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lain karena Element terdesentralisasi dan sumber terbuka.\n\nElement memungkinkan Anda host sendiri — atau memilih host — sehingga Anda memiliki privasi, kepemilikan, dan kontrol data dan obrolan Anda. Ini memberi Anda akses ke jaringan terbuka, jadi Anda tidak hanya terjebak berbicara dengan pengguna Element. Itu sangat aman.\n\nElement dapat melakukan semua ini karena beroperasi pada Matrix — standar untuk komunikasi terdesentralisasi terbuka.\n\nElement menempatkan Anda dalam kendali dengan membiarkan Anda memilih siapa yang menghost percakapan Anda. Dari aplikasi Element, Anda dapat memilih untuk menghost dengan cara yang berbeda:\n\n1. Dapatkan akun gratis pada server publik matrix.org\n2. Host sendiri akun Anda dengan menjalankan server pada perangkat keras Anda sendiri\n3. Mendaftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services\n\nMengapa memilih Element?\n\nMILIKI DATA ANDA: Anda memutuskan di mana untuk menyimpan data dan pesan Anda. Anda memilikinya dan mengendalikannya, bukan perusahaan besar yang menambang data Anda atau memberikan akses ke pihak ketiga.\n\nPESAN DAN KOLABORASI TERBUKA: Anda dapat mengobrol dengan orang lain di jaringan Matrix, jika mereka menggunakan Element atau aplikasi Matrix lain, dan bahkan jika mereka menggunakan sistem perpesanan seperti Slack, IRC atau XMPP.\n\nSANGAT AMAN: Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan penandatanganan silang untuk memverifikasi perangkat anggota obrolan.\n\nKOMUNIKASI LENGKAP: Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.\n\nDI MANA PUN ANDA BERADA: Tetap berkomunikasi di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io/."; // String for App Store "store_short_description" = "Obrolan/VoIP terdesentralisasi aman"; @@ -611,13 +611,13 @@ "open" = "Buka"; "version_check_banner_subtitle_deprecated" = "Kami tidak mendukung %@ di iOS %@ lagi. Untuk terus menggunakan %@ secara maksimal, kami menyarankan Anda untuk meningkatkan versi iOS Anda."; "version_check_banner_subtitle_supported" = "Kami akan segera mengakhiri dukungan untuk %@ di iOS %@. Untuk terus menggunakan %@ secara maksimal, kami menyarankan Anda untuk meningkatkan versi iOS Anda."; -"spaces_coming_soon_detail" = "Fitur ini belum ada di sini, tetapi sedang dalam proses. Untuk saat ini, Anda dapat melakukannya dengan Element di komputer Anda."; +"spaces_coming_soon_detail" = "Fitur ini belum ada di sini, tetapi sedang dalam proses. Untuk saat ini, Anda dapat melakukannya dengan %@ di komputer Anda."; "spaces_no_member_found_detail" = "Mencari seseorang yang tidak ada di %@? Untuk saat ini, Anda dapat mengundangnya di web atau desktop."; "spaces_no_room_found_detail" = "Beberapa hasil mungkin disembunyikan karena ruangannya pribadi dan Anda memerlukan sebuah undangan untuk bergabung."; "spaces_empty_space_detail" = "Beberapa ruangan mungkin disembunyikan karena ruangannya pribadi dan Anda memerlukan sebuah undangan."; "leave_space_message" = "Apakah anda Anda yakin ingin keluar dari %@? Apakah Anda juga ingin meninggalkan semua ruangan dan space lainnya di space ini?"; "space_beta_announce_information" = "Space adalah cara baru untuk mengelompokkan ruangan dan orang. Mereka belum ada di iOS, tetapi Anda dapat menggunakannya sekarang di Web dan Desktop."; -"favourites_empty_view_information" = "Anda dapat memfavoritkan dengan beberapa cara - yang tercepat adalah dengan menekan dan menahan. Ketuk ikon bintang dan mereka akan secara otomatis muncul di sini."; +"favourites_empty_view_information" = "Anda dapat memfavoritkan dengan beberapa cara — yang tercepat adalah dengan menekan dan menahan. Ketuk ikon bintang dan mereka akan secara otomatis muncul di sini."; "home_empty_view_information" = "Aplikasi obrolan aman semua-dalam-satu untuk tim, teman, dan organisasi. Ketuk tombol + di bawah untuk menambahkan orang dan ruangan."; "pin_protection_explanatory" = "Menyiapkan PIN memungkinkan Anda melindungi data seperti pesan dan kontak, jadi hanya Anda yang dapat mengaksesnya dengan memasukkan PIN di awal aplikasi."; "major_update_information" = "Kami senang mengumumkan bahwa kami telah mengubah nama kami! Aplikasi Anda telah diperbarui dan Anda masuk ke akun Anda."; @@ -1538,7 +1538,7 @@ "space_feature_unavailable_information" = "Space adalah cara baru untuk mengelompokkan ruangan dan pengguna.\n\nMereka akan segera datang. Untuk saat ini, jika Anda bergabung sebuah space di platform lain, Anda akan dapat mengakses ruang mana saja yang Anda ikuti di sini."; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Kunci Anda sedang dicadangkan.\n\nKunci Keamanan Anda adalah jaring pengaman - Anda dapat menggunakannya untuk memulihkan akses ke pesan terenkripsi jika Anda lupa frasa sandi.\n\nSimpan Kunci Keamanan Anda di suatu tempat yang sangat aman, seperti pengelola kata sandi (atau brankas)."; +"key_backup_setup_success_from_passphrase_info" = "Kunci Anda sedang dicadangkan.\n\nKunci Keamanan Anda adalah jaring pengaman — Anda dapat menggunakannya untuk memulihkan akses ke pesan terenkripsi jika Anda lupa frasa sandi.\n\nSimpan Kunci Keamanan Anda di suatu tempat yang sangat aman, seperti pengelola kata sandi (atau brankas)."; "key_backup_setup_passphrase_info" = "Kami akan menyimpan salinan terenkripsi dari kunci Anda di server kami. Lindungi cadangan Anda dengan frasa agar tetap aman.\n\nUntuk keamanan maksimum, ini harus berbeda dari kata sandi akun Anda."; "key_backup_setup_intro_info" = "Pesan di ruang terenkripsi diamankan dengan enkripsi ujung-ke-ujung. Hanya Anda dan penerima yang memiliki kunci untuk membaca pesan ini.\n\nCadangkan kunci Anda dengan aman untuk menghindari kehilangannya."; "deactivate_account_informations_part5" = "Jika Anda ingin kami melupakan pesan Anda, silakan centang kotak di bawah ini\n\nVisibilitas pesan di Matrix mirip dengan email. Kami melupakan pesan Anda berarti bahwa pesan yang telah Anda kirim tidak akan dibagikan dengan pengguna baru atau tidak terdaftar, tetapi pengguna terdaftar yang sudah memiliki akses ke pesan ini akan tetap memiliki akses ke salinannya."; @@ -1649,7 +1649,7 @@ "group_details_title" = "Detail Komunitas"; "room_event_action_forward" = "Teruskan"; "poll_edit_form_add_option" = "Tambahkan opsi"; -"poll_edit_form_option_number" = "Opsi %d"; +"poll_edit_form_option_number" = "Opsi %lu"; "poll_edit_form_create_options" = "Buat opsi"; "poll_edit_form_input_placeholder" = "Tulis sesuatu"; "poll_edit_form_question_or_topic" = "Pertanyaan atau topik"; @@ -1663,3 +1663,75 @@ "share_extension_low_quality_video_title" = "Video akan dikirim dalam kualitas rendah"; "settings_discovery_accept_terms" = "Terima Persyaratan Server Identitas"; "settings_about" = "TENTANG"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_not_closed_subtitle" = "Mohon coba lagi"; +"poll_timeline_not_closed_title" = "Gagal untuk mengakhiri poll"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_timeline_vote_not_registered_subtitle" = "Maaf, suara Anda tidak diberikan, mohon coba lagi"; +"poll_timeline_vote_not_registered_title" = "Suara tidak diberikan"; +"poll_timeline_total_final_results" = "Hasil akhir berdasarkan %lu suara"; +"poll_timeline_total_final_results_one_vote" = "Hasil akhir berdasarkan 1 suara"; +"poll_timeline_total_votes_not_voted" = "%lu suara diberikan. Berikan suara untuk melihat hasilnya"; +"poll_timeline_total_one_vote_not_voted" = "1 suara diberikan. Berikan suara untuk melihat hasilnya"; +"poll_timeline_total_no_votes" = "Tidak ada suara yang diberikan"; +"poll_timeline_total_votes" = "%lu suara diberikan"; +"poll_timeline_total_one_vote" = "1 suara diberikan"; +"poll_timeline_votes_count" = "%lu suara"; +"poll_timeline_one_vote" = "1 suara"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_post_failure_subtitle" = "Silakan coba lagi"; +"poll_edit_form_post_failure_title" = "Gagal untuk mengirim poll"; +"settings_labs_enabled_polls" = "Poll"; +"room_event_action_end_poll" = "Akhiri poll"; +"room_event_action_remove_poll" = "Hapus poll"; +"analytics_prompt_stop" = "Berhenti membagikan"; +"analytics_prompt_yes" = "Iya, saya tidak keberatan"; +"analytics_prompt_not_now" = "Jangan sekarang"; +"analytics_prompt_point_3" = "Anda dapat mematikannya kapan saja di pengaturan"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Kami tidak membagikan informasi ini dengan pihak ketiga"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Kami tidak merekam atau memprofil data akun apapun"; +"analytics_prompt_terms_link_upgrade" = "di sini"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Baca semua kebijakan kami %@. Apakah Anda tidak keberatan?"; +"analytics_prompt_terms_link_new_user" = "di sini"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Anda dapat membaca semua kebijakan kami %@."; +"analytics_prompt_message_upgrade" = "Anda sebelumnya setuju untuk mengirimkan data penggunaan anonim dengan kami. Sekarang, supaya kami dapat memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda."; +"analytics_prompt_message_new_user" = "Bantu kami mengidentifikasi masalah-masalah dan membuat %@ lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda."; + +// Analytics +"analytics_prompt_title" = "Bantu membuat %@ lebih baik"; +"settings_analytics_and_crash_data" = "Kirim data crash dan analitik"; +"accessibility_button_label" = "tombol"; +"enable" = "Aktifkan"; +"location_sharing_invalid_authorization_error_title" = "%@ tidak memiliki izin untuk mengakses lokasi. Anda dapat mengaktifkan akses di Pengaturan > Lokasi"; +"location_sharing_settings_toggle_title" = "Aktifkan pembagian lokasi"; +"location_sharing_settings_header" = "Pembagian lokasi"; +"location_sharing_open_google_maps" = "Buka di Google Maps"; +"location_sharing_open_apple_maps" = "Buka di Apple Maps"; +"location_sharing_invalid_authorization_settings" = "Pengaturan"; +"location_sharing_invalid_authorization_not_now" = "Jangan sekarang"; +"location_sharing_locating_user_error_title" = "%@ tidak dapat mengakses lokasi Anda. Silakan coba lagi nanti."; +"location_sharing_loading_map_error_title" = "%@ tidak dapat memuat peta. Silakan coba lagi nanti."; +"location_sharing_share_action" = "Bagikan"; +"location_sharing_close_action" = "Tutup"; + +// MARK: - Location sharing + +"location_sharing_title" = "Lokasi"; +"ok" = "OKE"; +"settings_enable_room_message_bubbles" = "Gelembung pesan"; +"onboarding_splash_page_4_message" = "Element juga bagus untuk tempat kerja. Terpercayai oleh organisasi paling aman di dunia."; +"onboarding_splash_page_4_title_no_pun" = "Perpesanan untuk tim Anda."; +"onboarding_splash_page_3_message" = "Terenkripsi secara ujung-ke-ujung dan tidak memerlukan nomor telepon. Tidak ada iklan atau penambangan data."; +"onboarding_splash_page_3_title" = "Perpesanan aman."; +"onboarding_splash_page_2_message" = "Pilihlah di mana percakapan Anda disimpan, memberikan Anda kendali dan kebebasan. Terhubung via Matrix."; +"onboarding_splash_page_2_title" = "Anda dalam kendali."; +"onboarding_splash_page_1_message" = "Komunikasi aman dan independen yang memberikan tingkat privasi yang sama seperti percakapan tatap muka di rumah Anda sendiri."; +"onboarding_splash_page_1_title" = "Miliki percakapan Anda."; +"onboarding_splash_login_button_title" = "Saya sudah punya akun"; + +// Onboarding +"onboarding_splash_register_button_title" = "Buat akun"; diff --git a/Riot/Assets/it.lproj/InfoPlist.strings b/Riot/Assets/it.lproj/InfoPlist.strings index 6382ef48e..989ac76d3 100644 --- a/Riot/Assets/it.lproj/InfoPlist.strings +++ b/Riot/Assets/it.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSContactsUsageDescription" = "Element mostrerà i tuoi contatti così da poterli invitare in chat."; "NSCalendarsUsageDescription" = "Vedi le tue riunioni programmate nell'app."; "NSFaceIDUsageDescription" = "Face ID viene usato per accedere all'app."; +"NSLocationWhenInUseUsageDescription" = "Quando condividi la tua posizione con altre persone, Element richiede l'accesso per mostrare loro una mappa."; diff --git a/Riot/Assets/it.lproj/Localizable.strings b/Riot/Assets/it.lproj/Localizable.strings index 25a6a0cca..d8ec6e0cd 100644 --- a/Riot/Assets/it.lproj/Localizable.strings +++ b/Riot/Assets/it.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Notifica"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ ha condiviso la sua posizione"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index ce4d6c96d..26065f880 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -1454,7 +1454,7 @@ "space_private_join_rule" = "Spazio privato"; "space_participants_action_ban" = "Bandisci da questo spazio"; "space_participants_action_remove" = "Rimuovi da questo spazio"; -"spaces_coming_soon_detail" = "Questa funzionalità non è stata implementata qui, ma è in arrivo. Per adesso, puoi farlo con Element sul tuo computer."; +"spaces_coming_soon_detail" = "Questa funzionalità non è stata implementata qui, ma è in arrivo. Per adesso, puoi farlo con %@ sul tuo computer."; "spaces_invites_coming_soon_title" = "Inviti in arrivo"; "spaces_add_rooms_coming_soon_title" = "Aggiunta di stanze in arrivo"; "spaces_coming_soon_title" = "Prossimamente"; @@ -1504,7 +1504,7 @@ "space_home_show_all_rooms" = "Mostra tutte le stanze"; "room_event_action_forward" = "Inoltra"; "poll_edit_form_add_option" = "Aggiungi opzione"; -"poll_edit_form_option_number" = "Opzione %d"; +"poll_edit_form_option_number" = "Opzione %lu"; "poll_edit_form_create_options" = "Crea opzioni"; "poll_edit_form_input_placeholder" = "Scrivi qualcosa"; "poll_edit_form_question_or_topic" = "Domanda o argomento"; @@ -1518,3 +1518,75 @@ "share_extension_low_quality_video_title" = "Il video verrà inviato in bassa qualità"; "settings_discovery_accept_terms" = "Accetta termini del server d'identità"; "settings_about" = "INFORMAZIONI"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_not_closed_subtitle" = "Riprova"; +"poll_timeline_not_closed_title" = "Chiusura del sondaggio fallita"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_timeline_vote_not_registered_subtitle" = "Spiacenti, il tuo voto non è stato registrato, riprova"; +"poll_timeline_vote_not_registered_title" = "Voto non registrato"; +"poll_timeline_total_final_results" = "Risultato finale basato su %lu voti"; +"poll_timeline_total_final_results_one_vote" = "Risultato finale basato su 1 voto"; +"poll_timeline_total_votes_not_voted" = "%lu voti inviati. Vota per vedere i risultati"; +"poll_timeline_total_one_vote_not_voted" = "1 voto inviato. Vota per vedere i risultati"; +"poll_timeline_total_votes" = "%lu voti"; +"poll_timeline_total_one_vote" = "1 voto"; +"poll_timeline_total_no_votes" = "Nessun voto"; +"poll_timeline_votes_count" = "%lu voti"; +"poll_timeline_one_vote" = "1 voto"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_post_failure_subtitle" = "Riprova"; +"poll_edit_form_post_failure_title" = "Invio del sondaggio fallito"; +"settings_labs_enabled_polls" = "Sondaggi"; +"room_event_action_end_poll" = "Termina sondaggio"; +"room_event_action_remove_poll" = "Rimuovi sondaggio"; +"analytics_prompt_stop" = "Non condividere più"; +"analytics_prompt_yes" = "Sì, va bene"; +"analytics_prompt_not_now" = "Non ora"; +"analytics_prompt_point_3" = "Puoi disattivarlo in qualsiasi momento nelle impostazioni"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Non condividiamo informazioni con terze parti"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Non registriamo o profiliamo alcun dato dell'account"; +"analytics_prompt_terms_link_upgrade" = "qui"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Leggi i nostri termini di servizio %@. Accetti?"; +"analytics_prompt_terms_link_new_user" = "qui"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Puoi leggere i nostri termini di servizio %@."; +"analytics_prompt_message_upgrade" = "Hai acconsentito precedentemente a condividere con noi dati di utilizzo anonimi. Ora, per capire come le persone usano diversi dispositivi, genereremo un identificativo casuale, condiviso dai tuoi dispositivi."; +"analytics_prompt_message_new_user" = "Aiutaci a identificare problemi e a migliorare %@ condividendo dati di utilizzo anonimi. Per capire come le persone usano diversi dispositivi, genereremo un identificativo casuale, condiviso dai tuoi dispositivi."; + +// Analytics +"analytics_prompt_title" = "Aiuta a migliorare %@"; +"settings_analytics_and_crash_data" = "Invia crash e dati analitici"; +"accessibility_button_label" = "pulsante"; +"enable" = "Attiva"; +"location_sharing_settings_toggle_title" = "Attiva condivisione posizione"; +"location_sharing_settings_header" = "Condivisione posizione"; +"location_sharing_open_google_maps" = "Apri in Google Maps"; +"location_sharing_open_apple_maps" = "Apri in Apple Maps"; +"location_sharing_invalid_authorization_settings" = "Impostazioni"; +"location_sharing_invalid_authorization_not_now" = "Non ora"; +"location_sharing_invalid_authorization_error_title" = "%@ non ha l'autorizzazione per accedere alla tua posizione. Puoi attivarne l'accesso in Impostazioni > Localizzazione"; +"location_sharing_locating_user_error_title" = "%@ non ha potuto rilevare la tua posizione. Riprova più tardi."; +"location_sharing_loading_map_error_title" = "%@ non ha potuto caricare la mappa. Riprova più tardi."; +"location_sharing_share_action" = "Condividi"; +"location_sharing_close_action" = "Chiudi"; + +// MARK: - Location sharing + +"location_sharing_title" = "Posizione"; +"ok" = "OK"; +"settings_enable_room_message_bubbles" = "Messaggi"; +"onboarding_splash_page_4_message" = "Element è ottimo anche per i luoghi di lavoro. È considerato affidabile dalle più sicure organizzazioni del mondo."; +"onboarding_splash_page_4_title_no_pun" = "Messaggistica per la tua squadra."; +"onboarding_splash_page_3_message" = "Crittografia end-to-end e nessun numero di telefono richiesto. Niente pubblicità o raccolta di dati."; +"onboarding_splash_page_3_title" = "Messaggistica sicura."; +"onboarding_splash_page_2_message" = "Scegli dove vengono tenute le tue conversazioni, dandoti controllo e indipendenza. Connesso via Matrix."; +"onboarding_splash_page_2_title" = "Tu hai il controllo."; +"onboarding_splash_page_1_message" = "Comunicazioni sicure e indipendenti che ti danno lo stesso livello di riservatezza di una conversazione faccia a faccia in casa tua."; +"onboarding_splash_page_1_title" = "Prendi il controllo delle tue conversazioni."; +"onboarding_splash_login_button_title" = "Ho già un account"; + +// Onboarding +"onboarding_splash_register_button_title" = "Crea account"; diff --git a/Riot/Assets/ko.lproj/Vector.strings b/Riot/Assets/ko.lproj/Vector.strings index 0baff0850..a88ae30cd 100644 --- a/Riot/Assets/ko.lproj/Vector.strings +++ b/Riot/Assets/ko.lproj/Vector.strings @@ -1068,3 +1068,64 @@ "contacts_address_book_permission_denied_alert_message" = "연락처를 사용하려면, 설정으로 이동하십시오."; "contacts_address_book_permission_denied_alert_title" = "연락처 사용 안 함"; "rooms_empty_view_title" = "방"; + +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "이 앱은 해당 홈서버의 인증 구조를 지원하지 않습니다."; +"security_settings_user_password_description" = "신원을 확인하기 위해 계정 비밀번호를 입력해주세요."; +"event_formatter_widget_removed_by_you" = "위젯을 제거함 : %@"; + +// Events formatter with you +"event_formatter_widget_added_by_you" = "위젯을 추가함 : %@"; +"event_formatter_group_call_leave" = "떠나기"; +"event_formatter_group_call_join" = "들어가기"; +"event_formatter_group_call" = "그룹 전화"; +"event_formatter_call_end_call" = "통화 종료"; +"event_formatter_call_retry" = "재시도"; +"event_formatter_call_decline" = "거부"; +"event_formatter_call_back" = "통화 재시도"; +"event_formatter_call_connection_failed" = "연결 실패"; +"event_formatter_call_missed_voice" = "음성 통화 부재중"; +"event_formatter_call_missed_video" = "영상 통화 부재중"; +"event_formatter_call_you_declined" = "통화 거부"; +"event_formatter_call_active_voice" = "음성 통화 활성"; +"event_formatter_call_active_video" = "영상 통화 활성"; +"event_formatter_call_incoming_video" = "영상 통화 수신"; +"event_formatter_call_incoming_voice" = "음성 통화 수신"; +"event_formatter_call_has_ended_with_time" = "통화 끊김 %@"; +"event_formatter_call_has_ended" = "통화 끊기"; +"event_formatter_call_ringing" = "통화중…"; +"event_formatter_call_connecting" = "연결중…"; +"room_notifs_settings_encrypted_room_notice" = "암호화된 방에서는 멘션 및 키워드 알림이 작동하지 않습니다."; +"room_notifs_settings_account_settings" = "계정 설정"; +"room_notifs_settings_cancel_action" = "취소"; +"room_notifs_settings_done_action" = "적용"; +"room_notifs_settings_none" = "알림받지 않기"; +"room_notifs_settings_mentions_and_keywords" = "멘션과 키워드만"; +"room_notifs_settings_all_messages" = "모든 메시지"; +"room_details_advanced_e2e_encryption_disabled_for_dm" = "이 방의 암호화가 활성화되지 않음."; +"room_details_advanced_e2e_encryption_enabled_for_dm" = "이 방의 암호화 활성화됨"; +"room_details_advanced_room_id_for_dm" = "아이디 :"; +"room_details_no_local_addresses_for_dm" = "이 방은 로컬 주소를 가지고 있지 않음"; +"room_details_access_section_anyone_for_dm" = "게스트를 포함한 초대 링크를 알고 있는 누구나"; +"room_details_access_section_anyone_apart_from_guest_for_dm" = "게스트를 제외하고 초대 링크를 알고 있는 누구나"; +"room_details_access_section_for_dm" = "누가 접근할 수 있나요?"; +"room_details_notifs" = "알림"; +"room_details_room_name_for_dm" = "이름"; +"room_details_photo_for_dm" = "사진"; +"room_details_integrations" = "통합"; +"room_details_search" = "방 검색"; +"room_details_title_for_dm" = "정보"; +"manage_session_sign_out" = "세션 연결 끊기"; +"manage_session_not_trusted" = "신뢰하지 않음"; +"manage_session_trusted" = "신뢰하도록 설정됨"; +"manage_session_name" = "세션 이름"; +"manage_session_info" = "세션 정보"; + +// Manage session +"manage_session_title" = "세션 관리"; +"settings_analytics_and_crash_data" = "오류 및 분석 데이터 전송"; +"settings_labs_enabled_polls" = "투표"; +"room_event_action_end_poll" = "투표 종료"; +"room_event_action_remove_poll" = "투표 제거"; +"accessibility_button_label" = "버튼"; +"enable" = "활성화"; diff --git a/Riot/Assets/nl.lproj/InfoPlist.strings b/Riot/Assets/nl.lproj/InfoPlist.strings index c3c846e97..9a4450097 100644 --- a/Riot/Assets/nl.lproj/InfoPlist.strings +++ b/Riot/Assets/nl.lproj/InfoPlist.strings @@ -21,3 +21,4 @@ "NSContactsUsageDescription" = "Element zal uw contacten tonen zodat u ze kunt uitnodigen om te chatten."; "NSCalendarsUsageDescription" = "Bekijk uw geplande afspraken in de app."; "NSFaceIDUsageDescription" = "Face ID wordt gebruikt om toegang te krijgen tot uw app."; +"NSLocationWhenInUseUsageDescription" = "Wanneer u uw locatie deelt met mensen heeft Element toegang nodig om dit op een kaart te tonen."; diff --git a/Riot/Assets/nl.lproj/Localizable.strings b/Riot/Assets/nl.lproj/Localizable.strings index f8c52bb83..7ff69454b 100644 --- a/Riot/Assets/nl.lproj/Localizable.strings +++ b/Riot/Assets/nl.lproj/Localizable.strings @@ -149,3 +149,6 @@ /** General **/ "NOTIFICATION" = "Meldingen"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ deelde hun locatie"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 7f4072640..b8eefab2b 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -1583,7 +1583,7 @@ "space_private_join_rule" = "Privé Space"; "space_participants_action_ban" = "Uit deze Space verbannen"; "space_participants_action_remove" = "Uit deze kamer verwijderen"; -"spaces_coming_soon_detail" = "Deze functies is nog niet klaar, maar er wordt aan gewerkt. Voor nu kan u dit wel doen met Element via uw computer."; +"spaces_coming_soon_detail" = "Deze functies is nog niet klaar, maar er wordt aan gewerkt. Voor nu kan u dit wel doen met %@ via uw computer."; "spaces_invites_coming_soon_title" = "Uitnodigen komt binnenkort"; "spaces_add_rooms_coming_soon_title" = "Kamers toevoegen komt binnenkort"; "spaces_coming_soon_title" = "Komt binnenkort"; @@ -1633,7 +1633,7 @@ "contacts_address_book_permission_denied_alert_message" = "Om contacten in te schakelen, ga naar uw apparaatinstellingen."; "contacts_address_book_permission_denied_alert_title" = "Contacten uitgeschakeld"; "poll_edit_form_add_option" = "Optie toevoegen"; -"poll_edit_form_option_number" = "Optie %d"; +"poll_edit_form_option_number" = "Optie %lu"; "poll_edit_form_create_options" = "Opties maken"; "poll_edit_form_input_placeholder" = "Schrijf iets"; "poll_edit_form_question_or_topic" = "Vraag of onderwerp"; @@ -1647,3 +1647,75 @@ "share_extension_low_quality_video_title" = "Video zal in lage kwaliteit worden verstuurd"; "settings_discovery_accept_terms" = "Identiteitsserver-voorwaarden aanvaarden"; "settings_about" = "OVER"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_not_closed_subtitle" = "Probeer het opnieuw"; +"poll_timeline_not_closed_title" = "Sluiten van de poll mislukt"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_timeline_vote_not_registered_subtitle" = "Sorry, uw stem is niet geregistreerd. Probeer het opnieuw"; +"poll_timeline_vote_not_registered_title" = "Stem niet geregistreerd"; +"poll_timeline_total_final_results" = "Uitslag gebaseerd op %lu stemmen"; +"poll_timeline_total_final_results_one_vote" = "Uitslag gebaseerd op 1 stem"; +"poll_timeline_total_votes_not_voted" = "%lu stemmen uitgebracht. Stem om de resultaten te zien"; +"poll_timeline_total_one_vote_not_voted" = "1 stem uitgebracht. Stem om de resultaten te zien"; +"poll_timeline_total_votes" = "%lu stemmen uitgebracht"; +"poll_timeline_total_one_vote" = "1 stem uitgebracht"; +"poll_timeline_total_no_votes" = "Geen stemmen uitgebracht"; +"poll_timeline_votes_count" = "%lu stemmen"; +"poll_timeline_one_vote" = "1 stem"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_post_failure_subtitle" = "Probeer het opnieuw"; +"poll_edit_form_post_failure_title" = "Poll plaatsen mislukt"; +"analytics_prompt_stop" = "Delen stoppen"; +"analytics_prompt_yes" = "Ja, dat is prima"; +"analytics_prompt_not_now" = "Niet nu"; +"analytics_prompt_point_3" = "U kunt dit op elk moment uitzetten in de instellingen"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Wij delen geen informatie met derden"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "We registreren of profileren geen accountgegevens"; +"analytics_prompt_terms_link_upgrade" = "hier"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Lees al onze voorwaarden %@. Is dit akkoord?"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "U kunt al onze voorwaarden %@ lezen."; +"analytics_prompt_terms_link_new_user" = "hier"; +"analytics_prompt_message_upgrade" = "U heeft eerder toestemming gegeven om anonieme gebruiksgegevens met ons te delen. Om beter te begrijpen hoe mensen meerdere apparaten gebruiken, genereren we nu een willekeurige identificatiecode die door uw apparaten wordt gedeeld."; +"analytics_prompt_message_new_user" = "Help ons bij het identificeren van problemen en het verbeteren van %@ door anonieme gebruiksgegevens te delen. Om te begrijpen hoe mensen meerdere apparaten gebruiken genereren we een willekeurige identificatie die we verspreiden over uw apparaten."; + +// Analytics +"analytics_prompt_title" = "Help %@ verbeteren"; +"settings_analytics_and_crash_data" = "Crash en analytische data versturen"; +"settings_labs_enabled_polls" = "Polls"; +"room_event_action_end_poll" = "Poll sluiten"; +"room_event_action_remove_poll" = "Poll verwijderen"; +"accessibility_button_label" = "knop"; +"enable" = "Inschakelen"; +"location_sharing_settings_toggle_title" = "Locatie delen inschakelen"; +"location_sharing_settings_header" = "Locatie delen"; +"location_sharing_open_google_maps" = "In Google Maps openen"; +"location_sharing_open_apple_maps" = "In Apple Maps openen"; +"location_sharing_invalid_authorization_settings" = "Instellingen"; +"location_sharing_invalid_authorization_not_now" = "Niet nu"; +"location_sharing_invalid_authorization_error_title" = "%@ heeft geen toegang tot uw locatie. U kan dit inschakelen via Instellingen > Locatie"; +"location_sharing_locating_user_error_title" = "%@ heeft geen toegang tot uw locatie. Probeer het later opnieuw."; +"location_sharing_loading_map_error_title" = "%@ kan de kaart niet laden. Probeer het later opnieuw."; +"location_sharing_share_action" = "Delen"; +"location_sharing_close_action" = "Sluiten"; + +// MARK: - Location sharing + +"location_sharing_title" = "Locatie"; +"ok" = "OK"; +"settings_enable_room_message_bubbles" = "Bericht bubbels"; +"onboarding_splash_page_4_message" = "Element is ook zeer geschikt voor op de werkvloer. Het wordt vertrouwd door 's werelds veiligste organisaties."; +"onboarding_splash_page_4_title_no_pun" = "Berichten voor uw team."; +"onboarding_splash_page_3_message" = "End-to-end versleuteld en geen telefoonnummer vereist. Geen advertenties of datamining."; +"onboarding_splash_page_3_title" = "Veilig berichtenverkeer."; +"onboarding_splash_page_2_message" = "Kies waar u gesprekken worden gehouden, zodat u controle en onafhankelijkheid heeft. Verbonden via Matrix."; +"onboarding_splash_page_2_title" = "U heeft de controle."; +"onboarding_splash_page_1_message" = "Veilige en onafhankelijke communicatie die u dezelfde mate van privacy geeft als een persoonlijk gesprek in uw eigen huis."; +"onboarding_splash_page_1_title" = "Word eigenaar van uw gesprekken."; +"onboarding_splash_login_button_title" = "Ik heb al een account"; + +// Onboarding +"onboarding_splash_register_button_title" = "Account aanmaken"; diff --git a/Riot/Assets/nn.lproj/Vector.strings b/Riot/Assets/nn.lproj/Vector.strings new file mode 100644 index 000000000..1eec1c558 --- /dev/null +++ b/Riot/Assets/nn.lproj/Vector.strings @@ -0,0 +1,11 @@ + + +"title_rooms" = "Rom"; +"title_people" = "Folk"; +"title_favourites" = "Favorittar"; + +// Titles +"title_home" = "Heim"; +"warning" = "Åtvaring"; +// String for App Store +"store_short_description" = "Sikker desentralisert chat/IP-telefoni"; diff --git a/Riot/Assets/pl.lproj/Localizable.strings b/Riot/Assets/pl.lproj/Localizable.strings index 761802de0..3be30e2fb 100644 --- a/Riot/Assets/pl.lproj/Localizable.strings +++ b/Riot/Assets/pl.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Powiadomienie"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ udostępnił(-a) swoją lokację"; diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index c4445fe9b..4f027c5db 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -217,9 +217,9 @@ "settings_calls_settings" = "POŁĄCZENIA"; "settings_user_interface" = "INTERFEJS UŻYTKOWNIKA"; "settings_ignored_users" = "IGNOROWANI UŻYTKOWNICY"; -"settings_contacts" = "LOKALNE KONTAKTY"; +"settings_contacts" = "KONTAKTY NA URZĄDZENIU"; "settings_advanced" = "ZAAWANSOWANE"; -"settings_other" = "Pozostałe powiadomienia"; +"settings_other" = "Pozostałe ustawienia"; "settings_devices" = "SESJE"; "settings_cryptography" = "KRYPTOGRAFIA"; "settings_deactivate_account" = "DEZAKTYWUJ KONTO"; @@ -416,7 +416,7 @@ "auth_email_not_found" = "Nie udało się wysłać wiadomości e-mail: Adres e-mail nie został znaleziony"; "auth_accept_policies" = "Przeczytaj i zaakceptuj zasady tego serwera domowego:"; "room_creation_wait_for_creation" = "Pokój jest już tworzony. Proszę czekaj."; -"room_creation_invite_another_user" = "Szukaj / zaproś przez ID Użytkownika, Nazwę lub e-mail"; +"room_creation_invite_another_user" = "ID użytkownika, nazwa lub email"; "search_people_placeholder" = "Szukaj przez ID Użytkownika, Nazwę lub e-mail"; "contacts_user_directory_offline_section" = "KATALOG UŻYTKOWNIKÓW (offline)"; "room_participants_remove_prompt_msg" = "Czy na pewno chcesz usunąć %@ z tej rozmowy?"; @@ -1265,7 +1265,7 @@ // Widget Picker "widget_picker_title" = "Integracje"; -"widget_integration_manager_disabled" = "Musisz włączyć Menadżer Integracji w ustawieniach"; +"widget_integration_manager_disabled" = "Musisz włączyć menadżer integracji w ustawieniach"; "widget_menu_remove" = "Usuń dla wszystkich"; "widget_menu_revoke_permission" = "Odbierz mi dostęp"; "widget_menu_open_outside" = "Otwórz w przeglądarce"; @@ -1321,7 +1321,7 @@ "identity_server_settings_description" = "Obecnie używasz %@ do wyszukiwania i bycia znalezionym przez kontakty, które znasz."; // Identity server settings -"identity_server_settings_title" = "Serwer Tożsamości"; +"identity_server_settings_title" = "Serwer tożsamości"; // AuthenticatedSessionViewControllerFactory "authenticated_session_flow_not_supported" = "Ta aplikacja nie obsługuje mechanizmu uwierzytelniania na Twoim serwerze domowym."; @@ -1365,7 +1365,7 @@ "settings_discovery_terms_not_signed" = "Zaakceptuj warunki korzystania z usługi serwera tożsamości (%@), aby umożliwić innym osobom wyszukiwanie Ciebie za pomocą adresu e-mail lub numeru telefonu."; "settings_discovery_no_identity_server" = "Obecnie nie używasz serwera tożsamości. Pomyśl o tym, aby go dodać by móc być odnalezionym przez osoby, które znasz."; "settings_devices_description" = "Publiczna nazwa sesji jest widoczna dla osób, z którymi się komunikujesz"; -"settings_integrations_allow_description" = "Użyj Menedżera Integracji (%@) do zarządzania botami, mostami, widżetami i pakietami naklejek.\n\nMenedżerowie integracji otrzymują dane konfiguracyjne i mogą modyfikować widżety, wysyłać zaproszenia do pokojów i ustawiać poziomy mocy w Twoim imieniu."; +"settings_integrations_allow_description" = "Użyj menedżera integracji (%@) do zarządzania botami, mostami, widżetami i pakietami naklejek.\n\nMenedżerowie integracji otrzymują dane konfiguracyjne i mogą modyfikować widżety, wysyłać zaproszenia do pokojów i ustawiać poziomy mocy w Twoim imieniu."; "settings_calls_stun_server_fallback_description" = "Zezwalaj na użycie rezerwowego serwera połączeń %@ w przypadku, gdy Twój serwer domowy go nie oferuje (Twój adres IP będzie udostępniany podczas połączenia)."; "settings_three_pids_management_information_part1" = "Wybierz, które adresy e-mail lub numery telefonów chcesz wykorzystać do logowania się lub odzyskiwania konta. Kontroluj, kto może Cię dzięki nim znaleźć przechodząc do "; "room_multiple_typing_notification" = "%@ i inni"; @@ -1535,7 +1535,58 @@ "settings_default" = "Powiadomienia dotyczące wiadomości"; "settings_notifications" = "POWIADOMIENIA"; "settings_show_url_previews_description" = "Podgląd URL będzie wyświetlany tylko w niezaszyfrowanych pokojach."; -"settings_show_url_previews" = "Pokaż podgląd adresów URL"; +"settings_show_url_previews" = "Pokaż podgląd stron"; "settings_confirm_media_size_description" = "Gdy ta opcja jest włączona, zostaniesz poproszony o potwierdzenie, w jakim rozmiarze obrazy i filmy będą wysyłane."; "settings_confirm_media_size" = "Potwierdź rozmiar podczas wysyłania"; "settings_sending_media" = "WYSYŁANIE ZDJĘĆ I WIDEO"; +"service_terms_modal_table_header_integration_manager" = "WARUNKI KORZYSTANIA Z MENEDŻERA INTEGRACJI"; +"service_terms_modal_table_header_identity_server" = "WARUNKI KORZYSTANIA Z SERWERA TOŻSAMOŚCI"; +"service_terms_modal_footer" = "To może być wyłączone później w ustawieniach."; + +// Service terms +"service_terms_modal_title_message" = "Aby kontynuować, wyraź zgodę na poniższe warunki użytkowania"; +"share_extension_send_now" = "Udostępnij teraz"; +"share_extension_low_quality_video_message" = "Udostępnij w %@ by uzyskać lepszą jakość, lub udostępnij w niskiej jakości poniżej."; +"share_extension_low_quality_video_title" = "Video będzie udostępnianie w niskiej jakości"; +"analytics_prompt_stop" = "Przestań udostępniać"; +"analytics_prompt_yes" = "Tak, to jest w porządku"; +"analytics_prompt_not_now" = "Nie teraz"; +"analytics_prompt_point_3" = "Możesz to wyłączyć w dowolnym momencie w ustawieniach"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Nie dzielimy się informacjami z żadnymi firmami zewnętrznymi"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Nie gromadzimy ani profilujemy żadnych danych na temat kont"; +"analytics_prompt_terms_link_upgrade" = "tutaj"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Przeczytaj nasze warunki użytkowania %@. Czy odpowiadają one Tobie?"; +"analytics_prompt_terms_link_new_user" = "tutaj"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Tutaj możesz przeczytać nasze warunki użytkowania %@."; +"analytics_prompt_message_upgrade" = "Wcześniej dostaliśmy Twoją zgodę na używanie zanonimizowanych danych na temat użytkowania. Teraz, by zrozumieć jak użytkownicy korzystają z wielu urządzeń, wygenerujemy identyfikator używany przez nie wszystkie."; +"analytics_prompt_message_new_user" = "Pomóż nam zidentyfikować problemy i poprawić @% udostępniając zanonimizowane dane o użytkowaniu. W celu zrozumienia jak użytkownicy korzystają z wielu urządzeń wygenerujemy identyfikator używany przez nie wszystkie."; + +// Analytics +"analytics_prompt_title" = "Pomóż ulepszyć %@"; +"settings_discovery_accept_terms" = "Zaakceptuj warunki używania serwera tożsamości"; +"settings_analytics_and_crash_data" = "Wysyłaj dane analityczne i o zawieszeniu aplikacji"; +"settings_labs_enabled_polls" = "Ankiety"; +"settings_contacts_enable_sync" = "Znajdź swoje kontakty"; +"settings_phone_contacts" = "KONTAKTY TELEFONICZNE"; +"settings_links" = "LINKI"; +"room_event_action_forward" = "Przekaż dalej"; +"room_event_action_end_poll" = "Zakończ ankietę"; +"room_event_action_remove_poll" = "Usuń ankietę"; +"find_your_contacts_identity_service_error" = "Nie można połączyć się s serwerem tożsamości."; +"contacts_address_book_permission_denied_alert_message" = "Aby włączyć kontakty, wejdź do ustawień Twojego urządzenia."; +"contacts_address_book_permission_denied_alert_title" = "Kontakty wyłączone"; +"room_recents_suggested_rooms_section" = "SUGEROWANE POKOJE"; +"onboarding_splash_page_1_message" = "Bezpieczna i niezależna komunikacja dająca Tobie ten sam poziom prywatności jak rozmowa twarzą w twarz w Twoim domu."; +"onboarding_splash_login_button_title" = "Mam już konto"; + +// Onboarding +"onboarding_splash_register_button_title" = "Stwórz konto"; +"accessibility_button_label" = "przycisk"; +"done" = "Gotowe"; +"open" = "Otwórz"; +"enable" = "Włącz"; +"ok" = "OK"; diff --git a/Riot/Assets/pt.lproj/InfoPlist.strings b/Riot/Assets/pt.lproj/InfoPlist.strings new file mode 100644 index 000000000..9cf939453 --- /dev/null +++ b/Riot/Assets/pt.lproj/InfoPlist.strings @@ -0,0 +1,7 @@ + + +"NSContactsUsageDescription" = "O Element vai mostrar os seus contactos para que os possa convidar para conversar."; +"NSMicrophoneUsageDescription" = "O Element necessita de aceder ao seu microfone para fazer e receber chamadas e para gravar mensagens de voz."; +"NSPhotoLibraryUsageDescription" = "A biblioteca de fotos é usada para enviar fotos e vídeos."; +// Permissions usage explanations +"NSCameraUsageDescription" = "A câmara é usada para tirar fotos e vídeos e fazer videochamadas."; diff --git a/Riot/Assets/pt.lproj/Localizable.strings b/Riot/Assets/pt.lproj/Localizable.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/pt.lproj/Localizable.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/pt.lproj/Vector.strings b/Riot/Assets/pt.lproj/Vector.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/pt.lproj/Vector.strings @@ -0,0 +1 @@ + diff --git a/Riot/Assets/pt_BR.lproj/InfoPlist.strings b/Riot/Assets/pt_BR.lproj/InfoPlist.strings index bb44cdb98..28cf8e2b8 100644 --- a/Riot/Assets/pt_BR.lproj/InfoPlist.strings +++ b/Riot/Assets/pt_BR.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSContactsUsageDescription" = "Element vai mostrar seus contatos para que você possa convidá-los a conversar."; "NSCalendarsUsageDescription" = "Ver suas reuniões agendadas no app."; "NSFaceIDUsageDescription" = "Face ID é usada para acessar seu app."; +"NSLocationWhenInUseUsageDescription" = "Quando você compartilha sua localização para pessoas, Element precisa de acesso para mostrar-lhes um mapa."; diff --git a/Riot/Assets/pt_BR.lproj/Localizable.strings b/Riot/Assets/pt_BR.lproj/Localizable.strings index 165ee183e..51f9faf5f 100644 --- a/Riot/Assets/pt_BR.lproj/Localizable.strings +++ b/Riot/Assets/pt_BR.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Notificação"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ compartilhou a localização dela(e)"; diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index ba473313e..8e6dbc79e 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -87,7 +87,7 @@ "auth_reset_password_error_unauthorized" = "Falha para verificar endereço de email: assegure que você clicou no link no email"; "auth_reset_password_error_not_found" = "Seu endereço de email não parece estar associado com uma ID Matrix neste servidorcasa."; "auth_reset_password_success_message" = "Sua senha tem sido resettada.\n\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para re-habilitar notificações, refaça login em cada dispositivo."; -"auth_add_email_and_phone_warning" = "Registro com email e número de telefone ao mesmo tempo não é suportado ainda até que a api exista. Somente o número de telefone vai ser levado em conta. Você pode adicionar seu email a seu perfil em configurações."; +"auth_add_email_and_phone_warning" = "Registro com email e número de telefone ao mesmo tempo não é suportado ainda até que a api exista. Somente o número de telefone vai ser levado em conta. Você pode adicionar seu email a seu perfil em ajustes."; "room_creation_appearance" = "Aparência"; "room_creation_appearance_name" = "Nome"; "room_creation_appearance_picture" = "Imagem de Chat (opcional)"; @@ -275,7 +275,7 @@ "room_preview_try_join_an_unknown_room" = "Você está tentando acessar %@. Você gostaria de se juntar para participar na discussão?"; "room_preview_try_join_an_unknown_room_default" = "uma sala"; // Settings -"settings_title" = "Configurações"; +"settings_title" = "Ajustes"; "account_logout_all" = "Fazer logout de todas as contas"; "settings_config_no_build_info" = "Nenhuma info de build"; "settings_mark_all_as_read" = "Marcar todas as mensagens como lidas"; @@ -283,7 +283,7 @@ "settings_config_home_server" = "Servidorcasa é %@"; "settings_config_identity_server" = "Servidor de identidade é %@"; "settings_config_user_id" = "Feito login como %@"; -"settings_user_settings" = "CONFIGURAÇÕES DE USUÁRIA(O)"; +"settings_user_settings" = "AJUSTES DE USUÁRIA(O)"; "settings_notifications_settings" = "CONFIGURAÇÕES DE NOTIFICAÇÃO"; "settings_calls_settings" = "CHAMADAS"; "settings_user_interface" = "INTERFACE DE USUÁRIA(O)"; @@ -316,7 +316,7 @@ "settings_fail_to_update_profile" = "Falha para atualizar perfil"; "settings_enable_push_notif" = "Notificações neste dispositivo"; "settings_show_decrypted_content" = "Mostrar conteúdo decriptado"; -"settings_global_settings_info" = "Configurações de notificação globais estão disponíveis em seu cliente web %@"; +"settings_global_settings_info" = "Ajustes de notificação globais estão disponíveis em seu cliente web %@"; "settings_enable_callkit" = "Chamamento integrado"; "settings_callkit_info" = "Receba chamadas entrantes em sua tela de bloqueio. Veja suas chamadas %@ no histórico de chamadas do sistema. Se iCloud está habilitado, este histórico de chamadas vai ser compartilhado com Apple."; "settings_ui_language" = "Língua"; @@ -361,7 +361,7 @@ "settings_deactivate_my_account" = "Desativar minha conta"; "room_details_people" = "Membros"; "room_details_files" = "Uploads"; -"room_details_settings" = "Configurações"; +"room_details_settings" = "Ajustes"; "room_details_photo" = "Foto de Sala"; "room_details_room_name" = "Nome de Sala"; "room_details_topic" = "Tópico"; @@ -482,7 +482,7 @@ "bug_report_prompt" = "O aplicativo tem crashado da última vez. Você gostaria de submeter um reporte de crash?"; "rage_shake_prompt" = "Você parece estar agitando o telefone em frustração. Você gostaria de submeter um reporte de bug?"; "do_not_ask_again" = "Não perguntar de novo"; -"camera_access_not_granted" = "%@ não tem permissão para usar Câmera, por favor mude configurações de privacidade"; +"camera_access_not_granted" = "%@ não tem permissão para usar Câmera, por favor mude ajustes de privacidade"; "large_badge_value_k_format" = "%.1fK"; // Call "call_incoming_voice_prompt" = "Chamada de voz entrante de %@"; @@ -497,7 +497,7 @@ // Crash report "google_analytics_use_prompt" = "Você gostaria de ajudar a melhorar %@ ao reportar automaticamente reportes de crash e dados de uso anônimos?"; // Crypto -"e2e_enabling_on_app_update" = "%@ agora suporta encriptação ponta-a-ponta mas você precisa fazer login de novo para habilitá-la.\n\nVocê pode fazê-lo agora ou mais tarde desde as configurações do aplicativo."; +"e2e_enabling_on_app_update" = "%@ agora suporta encriptação ponta-a-ponta mas você precisa fazer login de novo para habilitá-la.\n\nVocê pode fazê-lo agora ou mais tarde desde os ajustes do aplicativo."; "e2e_need_log_in_again" = "Você precisa fazer login de volta para gerar chaves de encriptação ponta-a-ponta para esta sessão e submeter a chave pública a seu servidorcasa.\nIsto é só desta vez; desculpe pela inconveniência."; // Bug report "bug_report_title" = "Report de Bug"; @@ -528,7 +528,7 @@ "widget_integration_room_not_visible" = "Sala %@ não está visível."; // Share extension "share_extension_auth_prompt" = "Faça login no app principal para compartilhar conteúdo"; -"share_extension_failed_to_encrypt" = "Falha para enviar. Cheque no app principal as configurações de encriptação para esta sala"; +"share_extension_failed_to_encrypt" = "Falha para enviar. Cheque no app principal os ajustes de encriptação para esta sala"; // Room key request dialog "e2e_room_key_request_title" = "Requisição de chave de encriptação"; "e2e_room_key_request_message_new_device" = "Você adicionou uma nova sessão '%@', que está requisitando chaves de encriptação."; @@ -586,8 +586,8 @@ "room_does_not_exist" = "%@ não existe"; // Key backup wrong version "e2e_key_backup_wrong_version_title" = "Novo Backup de Chave"; -"e2e_key_backup_wrong_version" = "Um novo backup de chave de mensagem segura tem sido detectado.\n\nSe isto não foi você, defina uma nova Frase de Segurança em Configurações."; -"e2e_key_backup_wrong_version_button_settings" = "Configurações"; +"e2e_key_backup_wrong_version" = "Um novo backup de chave de mensagem segura tem sido detectado.\n\nSe isto não foi você, defina uma nova Frase de Segurança em Ajustes."; +"e2e_key_backup_wrong_version_button_settings" = "Ajustes"; "e2e_key_backup_wrong_version_button_wasme" = "Foi eu"; "key_backup_setup_title" = "Backup de Chave"; "key_backup_setup_skip_alert_title" = "Você tem certeza?"; @@ -625,7 +625,7 @@ "key_backup_recover_title" = "Mensagens Seguras"; "key_backup_recover_invalid_passphrase_title" = "Frase de Segurança Incorreta"; "key_backup_recover_invalid_passphrase" = "Backup não pôde ser decriptado com esta frase: por favor verifique que você entrou a Frase de Segurança correta."; -"key_backup_recover_invalid_recovery_key_title" = "Disparidade de Chave de Segurança"; +"key_backup_recover_invalid_recovery_key_title" = "Correspondência Errada de Chave de Segurança"; "key_backup_recover_invalid_recovery_key" = "Backup não pôde ser decriptografado com esta chave: por favor verifique que você entrou a Chave de Segurança correta."; "key_backup_recover_from_passphrase_info" = "Use sua Frase de Segurança para destrancar seu histórico de mensagens seguras"; "key_backup_recover_from_passphrase_passphrase_title" = "Entrar"; @@ -638,7 +638,7 @@ "key_backup_recover_from_recovery_key_recovery_key_title" = "Entrar"; "key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Entrar Chave de Segurança"; "key_backup_recover_from_recovery_key_recover_action" = "Destrancar Histórico"; -"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Perdeu sua Chave de Segurança? Você pode configurar uma nova em configurações."; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Perdeu sua Chave de Segurança? Você pode configurar uma nova em ajustes."; "key_backup_recover_success_info" = "Backup Restaurado!"; "key_backup_recover_done_action" = "Feito"; "key_backup_setup_banner_title" = "Nunca perca mensagens criptografadas"; @@ -677,7 +677,7 @@ "secure_key_backup_setup_existing_backup_error_unlock_it" = "Destrancá-lo"; "secure_key_backup_setup_existing_backup_error_delete_it" = "Deletá-lo"; "secure_key_backup_setup_cancel_alert_title" = "Você tem certeza?"; -"secure_key_backup_setup_cancel_alert_message" = "Se você cancelar agora, você pode perder mensagens & dados encriptados se você perder acesso a seus logins.\n\nVocê também pode configurar Backup Seguro & gerenciar suas chaves em Configurações."; +"secure_key_backup_setup_cancel_alert_message" = "Se você cancelar agora, você pode perder mensagens & dados encriptados se você perder acesso a seus logins.\n\nVocê também pode configurar Backup Seguro & gerenciar suas chaves em Ajustes."; "secure_backup_setup_banner_title" = "Backup Seguro"; "secure_backup_setup_banner_subtitle" = "Salvaguardar-se contra perda de acesso a mensagens & dados encriptados"; // Recover from private key @@ -722,7 +722,7 @@ "settings_integrations_allow_button" = "Gerenciar integrações"; "settings_integrations_allow_description" = "Use um gerenciador de integrações (%@) para gerenciar bots, bridges, widgets e pacotes de stickers.\n\nGerenciadores de integrações recebem dados de configuração, e podem modificar widgets, enviar convites de sala e definir níveis de poder em seu nome."; "settings_add_3pid_password_title_email" = "Adicionar endereço de email"; -"settings_discovery_three_pids_management_information_part2" = "Configurações de Usuária(o)"; +"settings_discovery_three_pids_management_information_part2" = "Ajustes de Usuária(o)"; "settings_discovery_three_pids_management_information_part3" = "."; "settings_discovery_three_pid_details_share_action" = "Compartilhar"; "settings_discovery_three_pid_details_revoke_action" = "Revogar"; @@ -747,7 +747,7 @@ // Media picker "media_picker_title" = "Biblioteca de mídia"; "image_picker_action_library" = "Escolher de biblioteca"; -"photo_library_access_not_granted" = "%@ não tem permissão para acessar biblioteca de fotos, por favor mude configurações de privacidade"; +"photo_library_access_not_granted" = "%@ não tem permissão para acessar biblioteca de fotos, por favor mude ajustes de privacidade"; "room_widget_permission_display_name_permission" = "Seu nome de exibição"; "device_verification_security_advice_emoji" = "Compare os emoji únicos, garantindo que eles aparecem na mesma ordem."; "device_verification_self_verify_alert_message" = "Verifique o novo login acessando sua conta: %@"; @@ -794,14 +794,14 @@ "identity_server_settings_disconnect_info" = "Desconectar-se de seu servidor de identidade vai significar que você não vai ser descobertável por outras(os) usuárias(os) e ser capaz de convidar outras(os) por email ou telefone."; "identity_server_settings_alert_disconnect_still_sharing_3pid" = "Você ainda está compartilhando seus dados pessoais no servidor de identidade %@.\n\nNós recomendamos que você remova seus endereços de email e números de telefone do servidor de identidade antes de se desconectar."; "call_no_stun_server_error_title" = "Chamada falhou devido a servidor malconfigurado"; -"widget_integration_manager_disabled" = "Você precisa habilitar gerenciador de integrações em configurações"; +"widget_integration_manager_disabled" = "Você precisa habilitar gerenciador de integrações em ajustes"; "service_terms_modal_description_for_identity_server_1" = "Encontrar outras(os) por telefone ou email"; "service_terms_modal_description_for_identity_server_2" = "Ser encontrada(o) por telefone ou email"; "device_verification_self_verify_wait_additional_information" = "Isto funciona com %@ e outros clientes Matrix capazes de assinatura cruzada."; "key_verification_verified_user_information" = "Mensagens com esta(e) usuária(o) são encriptadas ponta-a-ponta e não podem ser lidas por terceiros."; "device_verification_emoji_smiley" = "Smiley"; // Generic errors -"error_invite_3pid_with_no_identity_server" = "Adicione um servidor de identidade em suas configurações para convidar por email."; +"error_invite_3pid_with_no_identity_server" = "Adicione um servidor de identidade em seus ajustes para convidar por email."; "key_verification_bootstrap_not_setup_message" = "Você precisa fazer bootstrap de assinatura cruzada primeiro."; "user_verification_sessions_list_user_trust_level_warning_title" = "Aviso"; "user_verification_sessions_list_information" = "Mensagens com esta(e) usuária(o) nesta sala são encriptadas ponta-a-ponta e não podem ser lidas por terceiros."; @@ -907,7 +907,7 @@ "event_formatter_widget_added_by_you" = "Você adicionou o widget: %@"; "event_formatter_widget_removed_by_you" = "Você removeu o widget: %@"; "camera_unavailable" = "A câmera está indisponível em seu dispositivo"; -"call_no_stun_server_error_message_2" = "Alternativamente, você pode tentar usar o servidor público em %@, mas isto não vai ser tão confiável, e vai compartilhar seu endereço de IP com esse servidor. Você também pode gerenciar isto em Configurações"; +"call_no_stun_server_error_message_2" = "Alternativamente, você pode tentar usar o servidor público em %@, mas isto não vai ser tão confiável, e vai compartilhar seu endereço de IP com esse servidor. Você também pode gerenciar isto em Ajustes"; "call_no_stun_server_error_use_fallback_button" = "Tentar usar %@"; // Widget "widget_no_integrations_server_configured" = "Nenhum servidor de integrações configurado"; @@ -974,7 +974,7 @@ "key_verification_verify_sas_cancel_action" = "Eles não correspondem"; "key_verification_verify_sas_validate_action" = "Eles correspondem"; "key_verification_verify_sas_additional_information" = "Para segurança ótima, use um outro meio de comunicação confiado ou faça isto em pessoa."; -"key_verification_manually_verify_device_instruction" = "Confirme ao comparar o seguinte com as Configurações de Usuária(o) em sua outra sessão:"; +"key_verification_manually_verify_device_instruction" = "Confirme ao comparar o seguinte com os Ajustes de Usuária(o) em sua outra sessão:"; "key_verification_manually_verify_device_name_title" = "Nome de sessão"; "key_verification_manually_verify_device_id_title" = "ID de sessão"; "key_verification_manually_verify_device_key_title" = "Chave de sessão"; @@ -1350,7 +1350,7 @@ "side_menu_app_version" = "Versão %@"; "side_menu_action_feedback" = "Feedback"; "side_menu_action_help" = "Ajuda"; -"side_menu_action_settings" = "Configurações"; +"side_menu_action_settings" = "Ajustes"; "side_menu_action_invite_friends" = "Convidar amigas(os)"; // Mark: - Side menu @@ -1370,13 +1370,13 @@ "security_settings_secure_backup_reset" = "Resettar"; "security_settings_secure_backup_info_valid" = "Esta sessão está fazendo backup de suas chaves."; "security_settings_secure_backup_info_checking" = "Checando…"; -"settings_ui_theme_picker_message_match_system_theme" = "\"Auto\" corresponde ao tema de sistema de seu dispositivo"; -"settings_ui_theme_picker_message_invert_colours" = "\"Auto\" usa as configurações \"Inverter Cores\" de seu dispositivo"; +"settings_ui_theme_picker_message_match_system_theme" = "\"Auto\" corresponde com o tema de sistema de seu dispositivo"; +"settings_ui_theme_picker_message_invert_colours" = "\"Auto\" usa os ajustes \"Inverter Cores\" de seu dispositivo"; "room_recents_unknown_room_error_message" = "Não dá para encontrar esta sala. Assegure que ela existe"; "room_creation_dm_error" = "Nós não conseguimos criar sua DM. Por favor cheque as/os usuárias(os) que você quer convidar e tente de novo."; "key_verification_verify_qr_code_scan_code_other_device_action" = "Scannar com este dispositivo"; "room_notifs_settings_encrypted_room_notice" = "Por favor note que notificações de menções & palavrachave não estão disponíveis em salas encriptadas no celular."; -"room_notifs_settings_account_settings" = "Configurações de conta"; +"room_notifs_settings_account_settings" = "Ajustes de conta"; "room_notifs_settings_manage_notifications" = "Você pode gerenciar notificações em %@"; "room_notifs_settings_cancel_action" = "Cancelar"; "room_notifs_settings_done_action" = "Feito"; @@ -1395,7 +1395,7 @@ "voice_message_release_to_send" = "Segure para gravar, solte para enviar"; "settings_labs_voice_messages" = "Mensagens de voz"; "settings_notifications_disabled_alert_title" = "Notificações desabilitadas"; -"settings_notifications_disabled_alert_message" = "Para habilitar notificações, vá para suas configurações de dispositivo."; +"settings_notifications_disabled_alert_message" = "Para habilitar notificações, vá para os ajustes de seu dispositivo."; "settings_device_notifications" = "Notificações de dispositivo"; "event_formatter_call_incoming_video" = "Chamada de vídeo entrante"; "event_formatter_call_incoming_voice" = "Chamada de voz entrante"; @@ -1451,7 +1451,7 @@ "space_private_join_rule" = "Espaço privado"; "space_participants_action_ban" = "Banir deste espaço"; "space_participants_action_remove" = "Remover deste espaço"; -"spaces_coming_soon_detail" = "Esta funcionalidade não tem sido implementada ainda, mas está a caminho. Por enquanto, você pode fazer isso com Element em seu computador."; +"spaces_coming_soon_detail" = "Esta funcionalidade não tem sido implementada ainda, mas está a caminho. Por enquanto, você pode fazer isso com %@ em seu computador."; "spaces_invites_coming_soon_title" = "Convites chegando em breve"; "spaces_add_rooms_coming_soon_title" = "Adicionar salas chegando em breve"; "spaces_coming_soon_title" = "Chegando em breve"; @@ -1484,7 +1484,7 @@ "service_terms_modal_description_identity_server" = "Isto vai permitir alguém encontrar você se ela/ele tem seu número de telefone ou email salvo nos contatos de telefone dela/dele."; "service_terms_modal_table_header_integration_manager" = "TERMOS DE GERENCIADOR DE INTEGRAÇÕES"; "service_terms_modal_table_header_identity_server" = "TERMOS DE SERVIDOR DE IDENTIDADE"; -"service_terms_modal_footer" = "Isto pode ser desabilitado a qualquer hora em configurações."; +"service_terms_modal_footer" = "Isto pode ser desabilitado a qualquer hora em ajustes."; // Service terms "service_terms_modal_title_message" = "Para continuar, aceite os termos e condições abaixo"; @@ -1492,16 +1492,16 @@ "settings_contacts_enable_sync" = "Encontre seus contatos"; "settings_phone_contacts" = "CONTATOS DE TELEFONE"; "find_your_contacts_identity_service_error" = "Incapaz de se conectar ao servidor de identidade."; -"find_your_contacts_footer" = "Isto pode ser desabilitado a qualquer hora a partir de configurações."; +"find_your_contacts_footer" = "Isto pode ser desabilitado a qualquer hora a partir de ajustes."; "find_your_contacts_button_title" = "Encontre seus contatos"; "find_your_contacts_message" = "Deixe %@ mostrar seus contatos para que você possa rapidamente começar a conversar com aqueles que você conhece melhor."; "find_your_contacts_title" = "Comece por listar seus contatos"; -"contacts_address_book_permission_denied_alert_message" = "Para habilitar contatos, vá para as configurações de seu dispositivo."; +"contacts_address_book_permission_denied_alert_message" = "Para habilitar contatos, vá para os ajustes de seu dispositivo."; "contacts_address_book_permission_denied_alert_title" = "Contatos desabilitados"; "space_home_show_all_rooms" = "Mostrar todas as salas"; "room_event_action_forward" = "Encaminhar"; "poll_edit_form_add_option" = "Adicionar opção"; -"poll_edit_form_option_number" = "Opção %d"; +"poll_edit_form_option_number" = "Opção %lu"; "poll_edit_form_create_options" = "Criar opções"; "poll_edit_form_input_placeholder" = "Escreva algo"; "poll_edit_form_question_or_topic" = "Pergunta ou tópico"; @@ -1515,3 +1515,75 @@ "share_extension_low_quality_video_title" = "Vídeo vai ser enviado em baixa qualidade"; "settings_discovery_accept_terms" = "Aceitar Termos de Servidor de Identidade"; "settings_about" = "SOBRE"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_not_closed_subtitle" = "Por favor tente de novo"; +"poll_timeline_not_closed_title" = "Falha para terminar sondagem"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_timeline_vote_not_registered_subtitle" = "Desculpe, seu voto não foi registrado, por favor tente de novo"; +"poll_timeline_vote_not_registered_title" = "Voto não registrado"; +"poll_timeline_total_final_results" = "Resultados finais baseados em %lu votos"; +"poll_timeline_total_final_results_one_vote" = "Resultados finais baseados em 1 voto"; +"poll_timeline_total_votes_not_voted" = "%lu votos lançados. Vote para ver os resultados"; +"poll_timeline_total_one_vote_not_voted" = "1 voto lançado. Vote para ver os resultados"; +"poll_timeline_total_votes" = "%lu votos lançados"; +"poll_timeline_total_one_vote" = "1 voto lançado"; +"poll_timeline_total_no_votes" = "Nenhum voto lançado"; +"poll_timeline_votes_count" = "%lu votos"; +"poll_timeline_one_vote" = "1 voto"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_post_failure_subtitle" = "Por favor tente de novo"; +"poll_edit_form_post_failure_title" = "Falha para postar sondagem"; +"settings_labs_enabled_polls" = "Sondagens"; +"room_event_action_end_poll" = "Terminar sondagem"; +"room_event_action_remove_poll" = "Remover sondagem"; +"analytics_prompt_stop" = "Parar de compartilhar"; +"analytics_prompt_yes" = "Sim, pode ser"; +"analytics_prompt_not_now" = "Não agora"; +"analytics_prompt_point_3" = "Você pode desativar isto a qualquer hora em ajustes"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Nós não compartilhamos informação com terceiros"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Nós não gravamos ou perfilamos quaisquer dados de conta"; +"analytics_prompt_terms_link_upgrade" = "aqui"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Leia todos os nossos termos %@. Isso está OK?"; +"analytics_prompt_terms_link_new_user" = "aqui"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Você pode ler todos os nossos termos %@."; +"analytics_prompt_message_upgrade" = "Você previamente consentiu a compartilhar dados de uso anônimos conosco. Agora, para ajudar a entender como pessoas usam múltiplos dispositivos, nós vamos gerar um identificador aleatório, compartilhado por seus dispositivos."; +"analytics_prompt_message_new_user" = "Ajude-nos a identificar problemas e melhorar %@ ao compartilhar dados de uso anônimos. Para entender como pessoas usam múltiplos dispositivos, nós geramos um identificador aleatório, compartilhado por seus dispositivos."; + +// Analytics +"analytics_prompt_title" = "Ajude a melhorar %@"; +"settings_analytics_and_crash_data" = "Enviar dados de crash e analítica"; +"accessibility_button_label" = "botão"; +"enable" = "Habilitar"; +"location_sharing_settings_toggle_title" = "Habilitar compartilhamento de local"; +"location_sharing_settings_header" = "Compartilhamento de local"; +"location_sharing_invalid_authorization_error_title" = "%@ não tem permissão para acessar seu local. Você pode habilitar acesso em Ajustes > Local"; +"location_sharing_locating_user_error_title" = "%@ não pôde acessar seu local. Por favor tente de novo mais tarde."; + +// MARK: - Location sharing + +"location_sharing_title" = "Local"; +"location_sharing_open_google_maps" = "Abrir em Google Maps"; +"location_sharing_open_apple_maps" = "Abrir em Apple Mapas"; +"location_sharing_invalid_authorization_settings" = "Ajustes"; +"location_sharing_invalid_authorization_not_now" = "Não agora"; +"location_sharing_loading_map_error_title" = "%@ não pôde carregar o mapa. Por favor tente de novo mais tarde."; +"location_sharing_share_action" = "Compartilhar"; +"location_sharing_close_action" = "Fechar"; +"ok" = "OK"; +"onboarding_splash_page_1_message" = "Comunicação segura e independente que lhe dá o mesmo nível de privacidade que uma conversa face-a-face em sua própria casa."; +"settings_enable_room_message_bubbles" = "Bolhas de mensagem"; +"onboarding_splash_page_4_message" = "Elemente também é ótimo para o lugar de trabalho. É confiado pelas organizações mais seguras do mundo."; +"onboarding_splash_page_4_title_no_pun" = "Mensageria para seu time."; +"onboarding_splash_page_3_message" = "Encriptado ponta-a-ponta e nenhum número de telefone requerido. Sem publicidade ou datamining."; +"onboarding_splash_page_3_title" = "Mensageria segura."; +"onboarding_splash_page_2_message" = "Escolha onde suas conversas são mantidas, dando-lhe controle e independência. Conectado via Matrix."; +"onboarding_splash_page_2_title" = "Você está em controle."; +"onboarding_splash_page_1_title" = "Tenha posse de suas conversas."; +"onboarding_splash_login_button_title" = "Eu já tenho uma conta"; + +// Onboarding +"onboarding_splash_register_button_title" = "Criar conta"; diff --git a/Riot/Assets/sk.lproj/InfoPlist.strings b/Riot/Assets/sk.lproj/InfoPlist.strings index 9d8892825..704899a7f 100644 --- a/Riot/Assets/sk.lproj/InfoPlist.strings +++ b/Riot/Assets/sk.lproj/InfoPlist.strings @@ -7,3 +7,4 @@ "NSPhotoLibraryUsageDescription" = "Knižnica fotografií sa používa na odosielanie fotografií a videí."; // Permissions usage explanations "NSCameraUsageDescription" = "Fotoaparát sa používa na snímanie fotografií a videí, uskutočňovanie videohovorov."; +"NSLocationWhenInUseUsageDescription" = "Keď zdieľate svoju polohu s ľuďmi, Element potrebuje prístup, aby im mohol zobraziť mapu."; diff --git a/Riot/Assets/sk.lproj/Localizable.strings b/Riot/Assets/sk.lproj/Localizable.strings index 950c96d36..fffb34696 100644 --- a/Riot/Assets/sk.lproj/Localizable.strings +++ b/Riot/Assets/sk.lproj/Localizable.strings @@ -75,3 +75,96 @@ /** General **/ "NOTIFICATION" = "Oznámenia"; + +/* New message from a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM" = "%@ napísal v %@"; + +/** Key verification **/ + +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ žiada o overenie"; + +/* Group call from user, CallKit caller name */ +"GROUP_CALL_FROM_USER" = "%@ (Skupinový hovor)"; + +/* A user added a Jitsi call to a room */ +"GROUP_CALL_STARTED" = "Začal sa skupinový hovor"; + +/* Incoming named video conference invite from a specific person */ +"VIDEO_CONF_NAMED_FROM_USER" = "Skupinový videohovor od používateľa %@: '%@'"; + +/* Incoming named voice conference invite from a specific person */ +"VOICE_CONF_NAMED_FROM_USER" = "Skupinový hovor od používateľa %@: '%@'"; + +/* Incoming unnamed video conference invite from a specific person */ +"VIDEO_CONF_FROM_USER" = "Skupinový videohovor od používateľa %@"; + +/* Incoming unnamed voice conference invite from a specific person */ +"VOICE_CONF_FROM_USER" = "Skupinový hovor od používateľa %@"; + +/** Calls **/ + +/* Incoming one-to-one voice call */ +"VOICE_CALL_FROM_USER" = "Hovor od používateľa %@"; + +/* Incoming one-to-one video call */ +"VIDEO_CALL_FROM_USER" = "Videohovor od používateľa %@"; + +/* A user's membership has updated in an unknown way */ +"USER_MEMBERSHIP_UPDATED" = "%@ aktualizoval/a svoj profil"; + +/* A user has change their avatar */ +"USER_UPDATED_AVATAR" = "%@ zmenil/a svoj obrázok"; + +/* A user has change their name to a new name which we don't know */ +"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ zmenil/a svoje meno"; + +/** Membership Updates **/ + +/* A user has change their name to a new name */ +"USER_UPDATED_DISPLAYNAME" = "%@ zmenil/a svoje meno na %@"; + +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "%@ vás pozval/a do %@"; + +/* A user has invited you to an (unamed) group chat */ +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ vás pozval/a na skupinovú konverzáciu"; + +/** Invites **/ + +/* A user has invited you to a chat */ +"USER_INVITE_TO_CHAT" = "%@ vás pozval/a na konverzáciu"; + +/* A user has reacted to a message, but the reaction content is unknown */ +"GENERIC_REACTION_FROM_USER" = "%@ poslal/a reakciu"; + +/** Reactions **/ + +/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */ +"REACTION_FROM_USER" = "%@ reagoval/a %@"; + +/* Look, stuff's happened, alright? Just open the app. */ +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ nových správ v %@, %@ a ďalších"; + +/* Multiple messages in two rooms */ +"MSGS_IN_TWO_ROOMS" = "%@ nových správ v %@ a %@"; + +/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ +"MSGS_FROM_TWO_PLUS_USERS" = "%@ nových správ od %@, %@ a ďalších"; + +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ poslal/a nálepku"; + +/* New file message from a specific person, not referencing a room. */ +"FILE_FROM_USER" = "%@ poslal/a súbor %@"; + +/* New voice message from a specific person, not referencing a room. */ +"VOICE_MESSAGE_FROM_USER" = "%@ poslal/a zvukovú správu"; + +/* New audio message from a specific person, not referencing a room. */ +"AUDIO_FROM_USER" = "%@ poslal/a zvukový súbor %@"; + +/* New message reply from a specific person in a named room. */ +"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ odpovedal/a v %@"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ zdieľal/a svoju polohu"; diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index f485f1f64..b86434589 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -165,3 +165,1502 @@ "callbar_only_single_active" = "Ťuknutím sa vrátite k hovoru (%@)"; "switch" = "Prepnúť"; "sending" = "Odosielanie"; +"settings_discovery_three_pids_management_information_part2" = "Používateľské nastavenia"; +"settings_key_backup_delete_confirmation_prompt_title" = "Vymazať zálohu"; +"settings_key_backup_button_delete" = "Vymazať zálohu"; +"settings_key_backup_info_algorithm" = "Algoritmus: %@"; +"settings_crypto_export" = "Exportovať kľúče"; +"settings_crypto_device_key" = "\nKľúč relácie:\n"; +"settings_crypto_device_id" = "\nID relácie: "; +"settings_crypto_device_name" = "Názov relácie: "; +"settings_confirm_password" = "Potvrdiť heslo"; +"settings_new_password" = "nové heslo"; +"settings_old_password" = "staré heslo"; +"settings_third_party_notices" = "Poznámky tretích strán"; +"settings_privacy_policy" = "Zásady ochrany súkromia"; +"settings_version" = "Verzia %s"; +"settings_labs_e2e_encryption" = "End-to-End šifrovanie"; +"settings_integrations_allow_button" = "Spravovať integrácie"; +"settings_room_upgrades" = "Aktualizácia miestnosti"; +"settings_messages_containing_user_name" = "My používateľské meno"; +"settings_group_messages" = "Skupinové správy"; +"settings_direct_messages" = "Priame správy"; +"settings_default" = "Predvolené oznámenia"; +"settings_notifications_disabled_alert_title" = "Oznámenia vypnuté"; +"settings_device_notifications" = "Oznámenia zariadenia"; +"settings_night_mode" = "Nočný režim"; +"settings_change_password" = "Zmeniť heslo"; +"settings_first_name" = "Krstné meno"; +"settings_display_name" = "Zobrazované meno"; +"settings_profile_picture" = "Profilový obrázok"; +"settings_sign_out" = "Odhlásiť sa"; +"settings_deactivate_account" = "DEAKTIVOVAŤ ÚČET"; +"settings_key_backup" = "ZÁLOHA KĽÚČA"; +"settings_phone_contacts" = "KONTAKTY TELEFÓNU"; +"settings_contacts" = "KONTAKTY ZARIADENIA"; +"settings_ignored_users" = "IGNOROVANÍ POUŽÍVATELIA"; +"settings_user_interface" = "POUŽÍVATEĽSKÉ ROZHRANIE"; +"settings_identity_server_settings" = "SERVER TOTOŽNOSTÍ"; +"settings_user_settings" = "POUŽÍVATEĽSKÉ NASTAVENIA"; +"settings_clear_cache" = "Vyprázdniť vyrovnávaciu pamäť"; +"settings_report_bug" = "Nahlásiť chybu"; +"room_preview_try_join_an_unknown_room_default" = "miestnosť"; +"room_title_one_member" = "1 člen"; +"room_title_members" = "%@ členovia"; +"room_title_invite_members" = "Pozvať členov"; + +// Room Title +"room_title_new_room" = "Nová miestnosť"; +"unknown_devices_title" = "Neznáme relácie"; +"unknown_devices_call_anyway" = "Napriek tomu zavolať"; +"unknown_devices_send_anyway" = "Napriek tomu odoslať"; +"room_place_voice_call" = "Audio hovor"; +"room_accessibility_hangup" = "Zavesiť"; +"room_accessibility_video_call" = "Video hovor"; +"room_action_send_file" = "Poslať súbor"; +"room_action_send_sticker" = "Odoslať nálepku"; +"room_event_action_reaction_show_less" = "Zobraziť menej"; +"room_event_action_reaction_show_all" = "Zobraziť všetko"; +"room_event_action_cancel_download" = "Zrušiť sťahovanie"; +"room_event_action_cancel_send" = "Zrušiť odosielanie"; +"room_event_action_report" = "Nahlásiť obsah"; +"room_event_action_view_source" = "Zobraziť zdroj"; +"room_prompt_cancel" = "Zrušiť všetky"; +"room_prompt_resend" = "Znovu odoslať všetky"; +"directory_cell_description" = "%tu miestnosti"; +"search_no_result" = "Žiadne výsledky"; + +// Room recents +"room_recents_directory_section" = "PRIEČINOK MIESTNOSTI"; + +// Social login + +"social_login_list_title_continue" = "Pokračovať s"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_vote_not_registered_action" = "OK"; +"poll_edit_form_post_failure_action" = "OK"; +"settings_labs_enabled_polls" = "Ankety"; +"side_menu_action_feedback" = "Spätná väzba"; +"side_menu_action_help" = "Pomocník"; +"side_menu_action_settings" = "Nastavenia"; + +// Mark: - User avatar view + +"user_avatar_view_accessibility_label" = "obrázok"; + +// Mark: Avatar + +"space_avatar_view_accessibility_label" = "obrázok"; +"spaces_suggested_room" = "Navrhované"; +"spaces_left_panel_title" = "Priestory"; +"spaces_home_space_title" = "Domov"; +"space_beta_announce_badge" = "BETA"; +"room_intro_cell_information_dm_sentence1_part3" = ". "; +"room_intro_cell_information_room_sentence1_part3" = ". "; + +// Mark: - Room avatar view + +"room_avatar_view_accessibility_label" = "obrázok"; +"call_transfer_error_title" = "Chyba"; +"call_transfer_contacts_all" = "Všetky"; +"call_transfer_contacts_recent" = "Nedávne"; +"call_transfer_users" = "Používatelia"; +"room_info_list_section_other" = "Ostatné"; +"create_room_placeholder_address" = "#testroom:matrix.org"; +"create_room_placeholder_topic" = "Téma"; +"create_room_placeholder_name" = "Názov"; +"biometrics_cant_unlocked_alert_message_retry" = "Skúsiť znovu"; +"pin_protection_settings_section_header" = "PIN"; +"pin_protection_reset_alert_action_reset" = "Obnoviť predvolené"; +"pin_protection_choose_pin_welcome_after_register" = "Vitajte."; +"secrets_reset_reset_action" = "Obnoviť predvolené"; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Potvrdiť"; +"secrets_setup_recovery_passphrase_validate_action" = "Hotovo"; +"secrets_setup_recovery_key_done_action" = "Hotovo"; +"secrets_setup_recovery_key_export_action" = "Uložiť"; +"secrets_setup_recovery_key_loading" = "Načítavanie…"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part3" = "."; + +// Session details + +"user_verification_session_details_trusted_title" = "Dôveryhodné"; +"user_verification_sessions_list_session_trusted" = "Dôveryhodné"; +"user_verification_sessions_list_table_title" = "Relácie"; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Neznámy"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Upozornenie"; + +// Sessions list + +"user_verification_sessions_list_user_trust_level_trusted_title" = "Dôveryhodné"; +"key_verification_tile_conclusion_done_title" = "Overené"; +"key_verification_tile_request_incoming_approval_decline" = "Odmietnuť"; +"key_verification_tile_request_incoming_approval_accept" = "Prijať"; +"key_verification_tile_request_status_waiting" = "Čakanie…"; + +// MARK: - Key Verification + +"key_verification_bootstrap_not_setup_title" = "Chyba"; + +// MARK: Reaction history +"reaction_history_title" = "Reakcie"; +"emoji_picker_symbols_category" = "Symboly"; +"emoji_picker_objects_category" = "Objekty"; +"emoji_picker_activity_category" = "Aktivity"; +"device_verification_emoji_headphones" = "Slúchadlá"; +"device_verification_emoji_anchor" = "Kotva"; +"device_verification_emoji_trumpet" = "Trúbka"; +"device_verification_emoji_guitar" = "Gitara"; +"device_verification_emoji_ball" = "Lopta"; +"device_verification_emoji_trophy" = "Trofej"; +"device_verification_emoji_rocket" = "Raketa"; +"device_verification_emoji_aeroplane" = "Lietadlo"; +"device_verification_emoji_bicycle" = "Bicykel"; +"device_verification_emoji_flag" = "Zástava"; +"device_verification_emoji_telephone" = "Telefón"; +"device_verification_emoji_hammer" = "Kladivo"; +"device_verification_emoji_key" = "Kľúč"; +"device_verification_emoji_lock" = "Zámka"; +"device_verification_emoji_scissors" = "Nožnice"; +"device_verification_emoji_paperclip" = "Kancelárska spinka"; +"device_verification_emoji_pencil" = "Ceruzka"; +"device_verification_emoji_book" = "Kniha"; +"device_verification_emoji_gift" = "Darček"; +"device_verification_emoji_clock" = "Budík"; +"device_verification_emoji_hourglass" = "Presýpacie hodiny"; +"device_verification_emoji_umbrella" = "Dáždnik"; +"device_verification_emoji_santa" = "Mikuláš"; +"device_verification_emoji_glasses" = "Okuliare"; +"device_verification_emoji_hat" = "Klobúk"; +"device_verification_emoji_robot" = "Robot"; +"device_verification_emoji_smiley" = "Úsmev"; +"device_verification_emoji_heart" = "Srdce"; +"device_verification_emoji_cake" = "Koláč"; +"device_verification_emoji_pizza" = "Pizza"; +"device_verification_emoji_corn" = "Kukurica"; +"device_verification_emoji_strawberry" = "Jahoda"; +"device_verification_emoji_apple" = "Jablko"; +"device_verification_emoji_banana" = "Banán"; +"device_verification_emoji_fire" = "Oheň"; +"device_verification_emoji_cloud" = "Oblak"; +"device_verification_emoji_moon" = "Mesiac"; +"device_verification_emoji_globe" = "Zemeguľa"; +"device_verification_emoji_mushroom" = "Huba"; +"device_verification_emoji_cactus" = "Kaktus"; +"device_verification_emoji_tree" = "Strom"; +"device_verification_emoji_flower" = "Kvet"; +"device_verification_emoji_butterfly" = "Motýľ"; +"device_verification_emoji_octopus" = "Chobotnica"; +"device_verification_emoji_fish" = "Ryba"; +"device_verification_emoji_turtle" = "Korytnačka"; +"device_verification_emoji_penguin" = "Tučniak"; +"device_verification_emoji_rooster" = "Kohút"; +"device_verification_emoji_panda" = "Panda"; +"device_verification_emoji_rabbit" = "Zajac"; +"device_verification_emoji_elephant" = "Slon"; +"device_verification_emoji_pig" = "Prasa"; +"device_verification_emoji_unicorn" = "Jednorožec"; +"device_verification_emoji_horse" = "Kôň"; +"device_verification_emoji_lion" = "Lev"; +"device_verification_emoji_cat" = "Mačka"; + +// MARK: Emoji +"device_verification_emoji_dog" = "Pes"; + +// MARK: Verified + +// Device + +"device_verification_verified_title" = "Overený!"; +"key_verification_manually_verify_device_validate_action" = "Overiť"; +"key_verification_self_verify_current_session_alert_validate_action" = "Overiť"; +"device_verification_self_verify_start_waiting" = "Čakanie…"; +"device_verification_self_verify_alert_validate_action" = "Overiť"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Zálohovať"; +"key_backup_recover_done_action" = "Hotovo"; +"key_backup_recover_from_recovery_key_recovery_key_title" = "Enter"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; +"key_backup_recover_from_passphrase_passphrase_title" = "Enter"; +"key_backup_setup_success_from_passphrase_done_action" = "Hotovo"; + +// Success + +"key_backup_setup_success_title" = "Úspech!"; +"key_backup_setup_passphrase_confirm_passphrase_valid" = "Super!"; +"key_backup_setup_passphrase_confirm_passphrase_title" = "Potvrdiť"; +"key_backup_setup_passphrase_passphrase_valid" = "Super!"; +"key_backup_setup_passphrase_passphrase_title" = "Enter"; +"key_backup_setup_intro_manual_export_info" = "(Pokročilé)"; +"key_backup_setup_skip_alert_skip_action" = "Preskočiť"; +"deactivate_account_forget_messages_information_part2_emphasize" = "Upozornenie"; +"service_terms_modal_decline_button" = "Odmietnuť"; +"service_terms_modal_accept_button" = "Prijať"; + +// Widget Picker +"widget_picker_title" = "Integrácie"; +"widget_menu_refresh" = "Obnoviť"; +"bug_report_send" = "Odoslať"; +"e2e_key_backup_wrong_version_button_settings" = "Nastavenia"; +"large_badge_value_k_format" = "%.1fK"; +"yesterday" = "Včera"; +"today" = "Dnes"; + +// Others +"or" = "alebo"; +"event_formatter_group_call_leave" = "Opustiť"; +"event_formatter_group_call_join" = "Vstúpiť"; +"event_formatter_call_retry" = "Skúsiť znovu"; +"event_formatter_call_answer" = "Odpovedať"; +"event_formatter_call_decline" = "Odmietnuť"; +"event_formatter_call_ringing" = "Vyzváňanie…"; +"event_formatter_call_connecting" = "Pripájanie…"; +"event_formatter_message_edited_mention" = "(upravené)"; +"directory_server_placeholder" = "matrix.org"; + +// Directory +"directory_title" = "Priečinok"; +"media_picker_select" = "Vybrať"; +"media_picker_library" = "Knižnica"; +"group_participants_invited_section" = "POZVANÍ"; +"group_participants_invite_prompt_title" = "Potvrdenie"; +"group_participants_remove_prompt_title" = "Potvrdenie"; +"group_details_rooms" = "Miestnosti"; +"group_details_people" = "Ľudia"; +"group_details_home" = "Domov"; +"room_notifs_settings_cancel_action" = "Zrušiť"; +"room_notifs_settings_done_action" = "Hotovo"; +"room_notifs_settings_none" = "Žiadne"; +"room_details_advanced_room_id_for_dm" = "ID:"; +"room_details_advanced_section" = "Pokročilé"; +"room_details_addresses_section" = "Adresy"; +"room_details_history_section_anyone" = "Ktokoľvek"; +"room_details_notifs" = "Oznámenia"; +"room_details_favourite_tag" = "Obľúbiť"; +"room_details_topic" = "Téma"; +"room_details_room_name_for_dm" = "Názov"; +"room_details_photo_for_dm" = "Fotka"; +"room_details_settings" = "Nastavenia"; +"room_details_integrations" = "Integrácie"; +"room_details_people" = "Členovia"; +"room_details_title_for_dm" = "Podrobnosti"; +"identity_server_settings_alert_disconnect_button" = "Odpojiť"; +"identity_server_settings_disconnect" = "Odpojiť"; +"identity_server_settings_change" = "Zmeniť"; +"identity_server_settings_add" = "Pridať"; +"security_settings_advanced" = "POKROČILÉ"; +"security_settings_cryptography" = "KRYPTOGRAFIA"; +"security_settings_crosssigning_reset" = "Obnoviť predvolené"; +"security_settings_crosssigning" = "KRÍŽOVÉ PODPISOVANIE"; +"security_settings_secure_backup_reset" = "Obnoviť predvolené"; +"security_settings_secure_backup_info_checking" = "Kontrolovanie…"; + +// Security settings +"security_settings_title" = "Zabezpečenie"; +"settings_discovery_three_pid_details_revoke_action" = "Odvolať"; +"settings_discovery_three_pid_details_share_action" = "Zdieľať"; +"settings_discovery_three_pids_management_information_part3" = "."; +"settings_key_backup_info_checking" = "Kontrolovanie…"; +"settings_copyright" = "Autorské práva"; +"settings_ui_theme_black" = "Čierny"; +"settings_ui_theme_dark" = "Tmavý"; +"settings_ui_theme_light" = "Svetlý"; +"settings_ui_theme" = "Vzhľad"; +"settings_ui_language" = "Jazyk"; +"settings_messages_containing_keywords" = "Kľúčové slová"; +"settings_messages_containing_at_room" = "@miestnosť"; +"settings_other" = "Ostatné"; +"settings_security" = "BEZPEČNOSŤ"; +"settings_three_pids_management_information_part3" = "."; +"settings_three_pids_management_information_part2" = "Objavovanie"; +"settings_phone_number" = "Telefón"; +"settings_email_address" = "Email"; +"settings_remove_prompt_title" = "Potvrdenie"; +"settings_surname" = "Priezvisko"; +"settings_cryptography" = "KRYPTOGRAFIA"; +"settings_devices" = "RELÁCIE"; +"settings_about" = "O APLIKÁCII"; +"settings_advanced" = "POKROČILÉ"; +"settings_integrations" = "INTEGRÁCIE"; +"settings_discovery_settings" = "OBJAVOVANIE"; +"settings_calls_settings" = "HOVORY"; +"settings_notifications" = "OZNÁMENIA"; +"settings_links" = "ODKAZY"; + +// Settings +"settings_title" = "Nastavenia"; +"unknown_devices_verify" = "Overiť…"; +"media_type_accessibility_sticker" = "Nálepka"; +"media_type_accessibility_file" = "Súbor"; +"media_type_accessibility_location" = "Miesto"; +"media_type_accessibility_video" = "Video"; +"media_type_accessibility_audio" = "Audio"; +"media_type_accessibility_image" = "Obrázok"; +"room_join_group_call" = "Vstúpiť"; +"room_accessibility_call" = "Hovor"; +"room_accessibility_upload" = "Nahrať"; +"room_accessibility_integrations" = "Integrácie"; +"room_accessibility_search" = "Hľadať"; +"room_resource_limit_exceeded_message_contact_1" = " Prosím "; +"room_action_reply" = "Odpovedať"; +"room_event_action_edit" = "Upraviť"; +"room_event_action_reply" = "Odpovedať"; +"room_event_action_delete" = "Vymazať"; +"room_event_action_resend" = "Znovu odoslať"; +"room_event_action_save" = "Uložiť"; +"room_event_action_permalink" = "Trvalý odkaz"; +"room_event_action_forward" = "Preposlať"; +"room_event_action_share" = "Zdieľať"; +"room_event_action_more" = "Viac"; +"room_event_action_redact" = "Odstrániť"; +"room_event_action_quote" = "Citovať"; +"room_event_action_copy" = "Kopírovať"; +"room_ongoing_conference_call_close" = "Zavrieť"; +"search_in_progress" = "Prehľadávanie…"; +"search_messages" = "Správy"; + +// Search +"search_rooms" = "Miestnosti"; +"group_section" = "KOMUNITY"; + +// Groups tab +"group_invite_section" = "POZVANIA"; + +// MARK: - Home + +"home_empty_view_title" = "Vitajte v %@,\n%@"; +"room_member_power_level_short_custom" = "Vlastná úroveň"; +"room_member_power_level_short_moderator" = "Moderátor"; +"room_member_power_level_short_admin" = "Správca"; +"room_member_power_level_custom_in" = "Vlastné (%@) v %@"; +"room_member_power_level_moderator_in" = "Moderátor v %@"; +"room_member_power_level_admin_in" = "Správca v %@"; +"room_participants_security_information_room_encrypted_for_dm" = "Správy sú tu šifrované end-to-end.\n\nVaše správy sú zabezpečené zámkami a jedinečné kľúče na ich odomknutie máte len vy a príjemca."; +"room_participants_security_information_room_encrypted" = "Správy v tejto miestnosti sú šifrované end-to-end.\n\nVaše správy sú zabezpečené zámkami a jedinečné kľúče na ich odomknutie máte len vy a príjemca."; +"room_participants_security_information_room_not_encrypted" = "Správy v tejto miestnosti nie sú šifrované end-to-end (od vás až k príjemcovi)."; +"room_participants_security_information_room_not_encrypted_for_dm" = "Správy tu nie sú šifrované end-to-end (od vás až k príjemcovi)."; +"room_participants_security_loading" = "Načítavanie…"; +"room_participants_action_security_status_loading" = "Načítavanie…"; +"room_participants_action_security_status_warning" = "Upozornenie"; +"room_participants_action_security_status_verify" = "Overiť"; +"room_participants_action_security_status_verified" = "Overený"; +"room_participants_action_mention" = "Zmieniť sa"; +"room_participants_action_start_video_call" = "Uskutočniť video hovor"; +"room_participants_action_start_voice_call" = "Uskutočniť hlasový hovor"; +"room_participants_action_start_new_chat" = "Začať novú konverzáciu"; +"room_participants_action_set_admin" = "Zmeniť na správcu"; +"room_participants_action_set_moderator" = "Udeliť stav moderátora"; +"room_participants_action_unignore" = "Zobraziť všetky správy od tohoto používateľa"; +"room_participants_action_ignore" = "Skryť všetky správy od tohoto používateľa"; +"room_participants_action_unban" = "Zrušiť zákaz"; +"room_participants_action_ban" = "Vylúčiť z tejto miestnosti"; +"room_participants_action_remove" = "Odstrániť z tejto miestnosti"; +"room_participants_action_leave" = "Opustiť túto miestnosť"; +"room_participants_action_invite" = "Pozvať"; +"room_participants_action_section_security" = "Zabezpečenie"; +"room_participants_action_section_other" = "Možnosti"; +"room_participants_action_section_devices" = "Relácie"; +"room_participants_action_section_direct_chats" = "Priame konverzácie"; +"room_participants_action_section_admin_tools" = "Nástroje správcu"; +"room_participants_filter_room_members_for_dm" = "Filtrovať členov"; +"room_participants_filter_room_members" = "Filtrovať členov v miestnosti"; +"room_participants_invite_prompt_msg" = "Ste si istí, že chcete pozvať používateľa %s do tejto konverzácie?"; +"room_participants_remove_third_party_invite_prompt_msg" = "Ste si istí, že chcete odvolať toto pozvanie?"; +"room_participants_remove_prompt_msg" = "Ste si istí, že chcete používateľa %s odstrániť z tejto konverzácie?"; +"room_participants_leave_prompt_msg_for_dm" = "Ste si istí, že chcete odísť?"; +"room_participants_leave_prompt_msg" = "Ste si istí, že chcete opustiť miestnosť?"; +"room_participants_leave_prompt_title" = "Opustiť miestnosť"; +"room_participants_multi_participants" = "%d účastníkov"; +"room_participants_one_participant" = "1 účastník"; +"room_participants_add_participant" = "Pridať účastníka"; +"contacts_address_book_permission_denied" = "Aplikácii %@ ste nepovolili prístup k svojim lokálnym kontaktom"; + +// Contacts +"contacts_address_book_section" = "LOKÁLNE KONTAKTY"; +"contacts_address_book_no_contact" = "Žiadne lokálne kontakty"; +"contacts_address_book_no_identity_server" = "Nebol nastavený server totožností"; +"contacts_address_book_matrix_users_toggle" = "Len používatelia Matrix"; +"directory_searching_title" = "Vyhľadávanie v priečinku…"; + +// Directory +"directory_cell_title" = "Prezrieť priečinok"; +"receipt_status_read" = "Prečítané: "; +"room_details_files" = "Nahrané súbory"; +"settings_ui_theme_auto" = "Automaticky"; + +// Success from passphrase +"key_backup_setup_success_from_passphrase_info" = "Vaše kľúče sa zálohujú.\n\nVáš bezpečnostný kľúč je bezpečnostná sieť - môžete ho použiť na obnovenie prístupu k zašifrovaným správam, ak zabudnete svoju prístupovú frázu.\n\nBezpečnostný kľúč uchovávajte na veľmi bezpečnom mieste, napríklad v správcovi hesiel (alebo v trezore)."; +"poll_timeline_not_closed_subtitle" = "Prosím, skúste to znova"; +"settings_unignore_user" = "Zobraziť všetky správy od %@?"; +"accessibility_button_label" = "tlačidlo"; +"settings_add_3pid_invalid_password_message" = "Neplatné poverovacie údaje"; +"security_settings_crypto_sessions" = "MOJE RELÁCIE"; + +// Deactivate account + +"deactivate_account_title" = "Deaktivovať účet"; +"service_terms_modal_information_title_integration_manager" = "Správca integrácie"; + +// Alert explaining what an identity server / integration manager is. +"service_terms_modal_information_title_identity_server" = "Server totožností"; +"e2e_room_key_request_ignore_request" = "Ignorovať žiadosť"; +"e2e_room_key_request_start_verification" = "Spustiť overovanie…"; +"share_extension_send_now" = "Odoslať teraz"; +"room_widget_permission_room_id_permission" = "ID miestnosti"; +"room_widget_permission_widget_id_permission" = "ID widgetu"; +"room_widget_permission_theme_permission" = "Váš vzhľad"; + +// Room widget permissions +"room_widget_permission_title" = "Načítať Widget"; +"widget_picker_manage_integrations" = "Spravovať integrácie…"; +"bug_report_send_screenshot" = "Odoslať snímku obrazovky"; +"event_formatter_group_call" = "Skupinový hovor"; +"event_formatter_call_end_call" = "Ukončiť hovor"; +"event_formatter_call_connection_failed" = "Pripojenie zlyhalo"; +"event_formatter_call_you_declined" = "Hovor odmietnutý"; +"event_formatter_call_has_ended" = "Hovor ukončený"; + +// Image picker +"image_picker_action_camera" = "Urobiť fotografiu"; +"group_participants_invite_malformed_id_title" = "Chyba pozvania"; +"group_participants_leave_prompt_title" = "Opustiť skupinu"; + +// Group participants +"group_participants_add_participant" = "Pridať účastníka"; +"group_home_multi_rooms_format" = "%tu miestnosti"; +"group_home_one_room_format" = "1 miestnosť"; +"group_home_multi_members_format" = "%tu členovia"; + +// Group Home +"group_home_one_member_format" = "1 člen"; + +// Group Details +"group_details_title" = "Podrobnosti o komunite"; +"room_notifs_settings_account_settings" = "Nastavenia účtu"; +"room_notifs_settings_all_messages" = "Všetky správy"; +"room_details_advanced_room_id" = "ID miestnosti:"; +"room_details_banned_users_section" = "Zakázaní používatelia"; +"room_details_flair_invalid_id_prompt_title" = "Neplatný formát"; +"room_details_direct_chat" = "Priama konverzácia"; +"room_details_mute_notifs" = "Stlmiť oznámenia"; +"room_details_low_priority_tag" = "Nízka priorita"; +"room_details_room_name" = "Názov miestnosti"; +"room_details_photo" = "Obrázok miestnosti"; +"room_details_search" = "Hľadať miestnosť"; +"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Napriek tomu sa odpojiť"; + +// Identity server settings +"identity_server_settings_title" = "Server totožností"; +"manage_session_not_trusted" = "Nedôveryhodné"; +"manage_session_name" = "Názov relácie"; +"manage_session_info" = "INFORMÁCIE O RELÁCII"; + +// Manage session +"manage_session_title" = "Spravovať reláciu"; +"security_settings_secure_backup_setup" = "Nastaviť"; +"security_settings_secure_backup" = "BEZPEČNÉ ZÁLOHOVANIE"; +"security_settings_crypto_sessions_loading" = "Načítavanie relácií…"; +"security_settings_blacklist_unverified_devices" = "Nikdy neodosielať správy do nedôveryhodných relácií"; +"security_settings_crosssigning_bootstrap" = "Nastaviť"; +"security_settings_crosssigning_info_not_bootstrapped" = "Krížové podpisovanie ešte nie je nastavené."; +"security_settings_backup" = "ZÁLOHOVANIE SPRÁVY"; +"security_settings_secure_backup_delete" = "Vymazať zálohu"; +"bug_report_description" = "Popíšte prosím chybu. Čo ste urobili? Čo ste očakávali, že sa stane? Čo sa skutočne stalo?"; + +// Bug report +"bug_report_title" = "Hlásenie o chybe"; +"e2e_need_log_in_again" = "Musíte sa znova prihlásiť, aby ste vygenerovali end-to-end šifrovacie kľúče pre túto reláciu a odoslali verejný kľúč na váš domovský server.\nToto je jednorazové; ospravedlňujeme sa za nepríjemnosti."; + +// No VoIP support +"no_voip_title" = "Prichádzajúci hovor"; +"call_no_stun_server_error_message_2" = "Prípadne môžete skúsiť použiť verejný server na adrese %@, ale nebude to tak spoľahlivé a vaša IP adresa bude zdieľaná s týmto serverom. Môžete to spravovať aj v nastaveniach"; +"call_incoming_voice" = "Prichádzajúci hovor…"; + +// Call +"call_incoming_voice_prompt" = "Prichádzajúci hlasový hovor od %@"; +"call_incoming_video_prompt" = "Prichádzajúci videohovor od %@"; +"room_does_not_exist" = "%@ neexistuje"; +"camera_unavailable" = "Fotoaparát nie je k dispozícii na vašom zariadení"; +"camera_access_not_granted" = "%@ nemá povolenie používať fotoaparát, zmeňte to prosím nastaveniach ochrany súkromia"; +"do_not_ask_again" = "Viac sa nepýtať"; +"bug_report_prompt" = "Posledné spustenie aplikácie skončilo pádom. Chcete odoslať správu o páde?"; +"public_room_section_title" = "Verejné miestnosti (na %@):"; +"homeserver_connection_lost" = "Nepodarilo sa pripojiť k domovskému serveru."; +"key_backup_recover_invalid_passphrase_title" = "Nesprávna bezpečnostná fráza"; + +// MARK: Key backup recover + +"key_backup_recover_title" = "Zabezpečené správy"; + +// Success from secure backup +"key_backup_setup_success_from_secure_backup_info" = "Vaše kľúče sa zálohujú."; +"key_backup_setup_success_from_recovery_key_made_copy_action" = "Vytvoril som si kópiu"; +"key_backup_setup_success_from_recovery_key_make_copy_action" = "Vytvorte si kópiu"; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Bezpečnostný kľúč"; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Uložte svoj bezpečnostný kľúč"; +"key_backup_setup_passphrase_set_passphrase_action" = "Nastaviť frázu"; +"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "Potvrdiť frázu"; +"key_backup_setup_passphrase_passphrase_placeholder" = "Zadajte frázu"; + +// Passphrase + +"key_backup_setup_passphrase_title" = "Zabezpečte si svoju zálohu pomocou bezpečnostnej frázy"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Pripojenie tohto zariadenia k službe Zálohovanie kľúčov"; +"key_backup_setup_intro_setup_action_without_existing_backup" = "Začať používať zálohovanie kľúčov"; +"key_backup_setup_skip_alert_message" = "Ak sa odhlásite alebo stratíte svoje zariadenie, môžete prísť o zabezpečené správy."; + + +// MARK: Key backup setup + +"key_backup_setup_title" = "Zálohovanie kľúčov"; +"secure_backup_setup_banner_subtitle" = "Zabezpečte sa proti strate šifrovaných správ a údajov"; + +// Banner + +"secure_backup_setup_banner_title" = "Bezpečné zálohovanie"; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Použiť bezpečnostnú frázu"; +"secure_key_backup_setup_intro_use_security_key_title" = "Použiť bezpečnostný kľúč"; +"secure_key_backup_setup_intro_info" = "Zabezpečte sa pred stratou šifrovaných správ a údajov zálohovaním šifrovacích kľúčov na domovskom serveri."; + +// MARK: Secure backup setup + +// Intro + +"secure_key_backup_setup_intro_title" = "Bezpečné zálohovanie"; +"rerequest_keys_alert_message" = "Spustite prosím %@ na inom zariadení, ktoré dokáže dešifrovať správu, aby mohlo poslať kľúče do tejto relácie."; + +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Žiadosť odoslaná"; +"deactivate_account_password_alert_message" = "Aby ste mohli pokračovať, prosím zadajte svoje heslo"; +"deactivate_account_password_alert_title" = "Deaktivovať účet"; +"deactivate_account_validate_action" = "Deaktivovať účet"; +"deactivate_account_informations_part2_emphasize" = "Táto akcia je nezvratná."; + +// Recovery passphrase + +"secrets_setup_recovery_passphrase_title" = "Nastaviť bezpečnostnú frázu"; +"secrets_setup_recovery_key_storage_alert_title" = "Udržujte ho v bezpečí"; +"secrets_setup_recovery_key_storage_alert_message" = "✓ Vytlačte ho a uložte na bezpečné miesto\n✓ Uložte ho na USB kľúč alebo záložný disk\n✓ Skopírujte si ho do osobného cloudového úložiska"; +"secrets_setup_recovery_key_information" = "Bezpečnostný kľúč uložte na bezpečné miesto. Môžete ho použiť na odomknutie zašifrovaných správ a údajov."; + +// MARK: - Secrets set up + +// Recovery Key + +"secrets_setup_recovery_key_title" = "Uložte svoj bezpečnostný kľúč"; +"secrets_recovery_with_key_invalid_recovery_key_message" = "Skontrolujte, či ste zadali správny bezpečnostný kľúč."; +"secrets_recovery_with_key_invalid_recovery_key_title" = "Nie je možné získať prístup k tajnému úložisku"; +"secrets_recovery_with_key_recovery_key_placeholder" = "Zadajte bezpečnostný kľúč"; +"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Skontrolujte prosím, či ste zadali správnu bezpečnostnú frázu."; +"secrets_recovery_with_passphrase_invalid_passphrase_title" = "Nie je možné získať prístup k tajnému úložisku"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "použiť váš bezpečnostný kľúč"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Neviete svoju bezpečnostnú frázu? Môžete "; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "Zadajte bezpečnostnú frázu"; +"secrets_recovery_with_passphrase_information_verify_device" = "Na overenie tohto zariadenia použite bezpečnostnú frázu."; +"user_verification_session_details_information_untrusted_current_user" = "Overte túto reláciu, aby ste ju označili za dôveryhodnú a udelili jej prístup k zašifrovaným správam:"; +"user_verification_session_details_information_trusted_other_user_part2" = " ste ju overili:"; +"user_verification_session_details_information_trusted_other_user_part1" = "Táto relácia je dôveryhodná pre bezpečné zasielanie správ, pretože "; +"user_verification_session_details_information_trusted_current_user" = "Táto relácia je dôveryhodná pre bezpečné zasielanie správ, pretože ste ju overili:"; +"user_verification_sessions_list_information" = "Správy s týmto používateľom v tejto miestnosti sú end-to-end šifrované a tretie strany ich nemôžu čítať."; +"user_verification_start_additional_information" = "Aby ste si boli istý, urobte to osobne alebo použite iný dôveryhodný spôsob komunikácie."; +"user_verification_start_waiting_partner" = "Čaká sa na %@…"; +"user_verification_start_information_part2" = " skontrolovaním jednorazového kódu na oboch zariadeniach."; +"user_verification_start_information_part1" = "Pre väčšiu bezpečnosť overte "; +"key_verification_scan_confirmation_scanned_device_information" = "Zobrazuje druhé zariadenie rovnaký štít?"; +"key_verification_scan_confirmation_scanned_user_information" = "Zobrazuje %@ rovnaký štít?"; +"key_verification_scan_confirmation_scanning_device_waiting_other" = "Čakanie na druhé zariadenie…"; +"key_verification_scan_confirmation_scanning_user_waiting_other" = "Čaká sa na %@…"; + +// MARK: Scan confirmation + +// Scanning +"key_verification_scan_confirmation_scanning_title" = "Už je to takmer hotové! Čaká sa na potvrdenie…"; +"key_verification_verify_qr_code_scan_other_code_success_message" = "QR kód bol úspešne overený."; +"key_verification_verify_qr_code_other_scan_my_code_title" = "Naskenoval druhý používateľ QR kód úspešne?"; +"key_verification_verify_qr_code_start_emoji_action" = "Overte pomocou emoji"; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Skenovať pomocou tohto zariadenia"; +"key_verification_verify_qr_code_scan_code_action" = "Naskenujte ich kód"; +"key_verification_verify_qr_code_emoji_information" = "Overenie porovnaním jedinečnej kombinácie emotikonov."; +"key_verification_verify_qr_code_information_other_device" = "Naskenujte nižšie uvedený kód na overenie:"; +"key_verification_verify_qr_code_information" = "Naskenujte kód, aby ste sa navzájom bezpečne overili."; + +// MARK: QR code + +"key_verification_verify_qr_code_title" = "Overte naskenovaním"; + +// Incoming key verification request + +"key_verification_incoming_request_incoming_alert_message" = "%@ žiada o overenie"; +"key_verification_tile_conclusion_warning_title" = "Nedôveryhodné prihlásenie"; +"error_not_supported_on_mobile" = "Toto nemôžete urobiť z mobilného telefónu %@."; +"emoji_picker_flags_category" = "Vlajky"; +"emoji_picker_places_category" = "Cestovanie a miesta"; +"emoji_picker_foods_category" = "Jedlo a nápoje"; +"emoji_picker_nature_category" = "Zvieratá a príroda"; + +// MARK: Emoji picker +"emoji_picker_title" = "Reakcie"; +"device_verification_emoji_bell" = "Zvonec"; +"device_verification_emoji_train" = "Vlak"; +"device_verification_emoji_spanner" = "Vidlicový kľúč"; + +// User + +"key_verification_verified_user_information" = "Správy s týmto používateľom sú end-to-end šifrované a tretie strany ich nemôžu čítať."; +"key_verification_verified_this_session_information" = "Na tomto zariadení teraz môžete čítať zabezpečené správy a ostatní používatelia budú vedieť, že mu môžu dôverovať."; +"key_verification_verified_new_session_information" = "Na novom zariadení teraz môžete čítať zabezpečené správy a ostatní používatelia budú vedieť, že mu môžu dôverovať."; +"key_verification_verified_other_session_information" = "Teraz môžete čítať zabezpečené správy vo vašej druhej relácii a ostatní používatelia budú vedieť, že jej môžu dôverovať."; +"key_verification_verified_new_session_title" = "Nová relácia overená!"; +"room_recents_unknown_room_error_message" = "Túto miestnosť nemôžeme nájsť. Uistite sa, že existuje"; +"room_creation_error_invite_user_by_email_without_identity_server" = "Nie je nakonfigurovaný žiadny server totožnosti, takže nemôžete pridať účastníka s e-mailom."; +"room_creation_appearance_picture" = "Obrázok konverzácie (nepovinné)"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Môžete si prečítať všetky naše podmienky %@."; +"analytics_prompt_terms_link_new_user" = "tu"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Prečítajte si všetky naše podmienky %@. Je to v poriadku?"; +"analytics_prompt_terms_link_upgrade" = "tu"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Nezaznamenávame ani neprofilujeme žiadne údaje o účte"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Nezdieľame informácie s tretími stranami"; +"analytics_prompt_point_3" = "Túto funkciu môžete kedykoľvek vypnúť v nastaveniach"; +"analytics_prompt_not_now" = "Teraz nie"; +"room_message_reply_to_placeholder" = "Odoslať odpoveď (nešifrovanú)…"; + + +// Room Details +"room_details_title" = "Podrobnosti o miestnosti"; +"settings_sending_media" = "ODOSIELANIE OBRÁZKOV A VIDEÍ"; +"settings_config_user_id" = "Prihlásený ako %@"; +"settings_mark_all_as_read" = "Označiť všetky správy ako prečítané"; +"settings_config_no_build_info" = "Žiadne informácie o zostavení"; +"room_preview_try_join_an_unknown_room" = "Pokúšate sa zobraziť %s. Chcete vstúpiť a pridať sa k diskusii?"; +"room_preview_unlinked_email_warning" = "Toto pozvanie bolo odoslané na emailovú adresu %s, ktorá nie je priradená k tomuto účtu. Môžete sa prihlásiť k inému účtu, alebo pridať túto emailovú adresu do vášho účtu."; +"room_preview_subtitle" = "Toto je náhľad do miestnosti. Všetky akcie pre túto miestnosť sú zakázané."; + +// Room Preview +"room_preview_invitation_format" = "Do tejto miestnosti vás pozval používateľ %@"; +"unknown_devices_answer_anyway" = "Odpovedať aj tak"; +"unknown_devices_alert" = "Táto miestnosť obsahuje neznáme relácie, ktoré neboli overené.\nTo znamená, že nie je zaručené, že relácie patria naozaj tým používateľom, o ktorých to tvrdia.\nPred pokračovaním odporúčame prejsť procesom overenia každej relácie, ale ak chcete, môžete správu poslať znova bez overenia."; + +// Unknown devices +"unknown_devices_alert_title" = "V miestnosti sú neznáme relácie"; +"external_link_confirmation_message" = "Odkaz %@ vás presmeruje na inú stránku: %@\n\nSte si istí, že chcete pokračovať?"; +"room_no_privileges_to_create_group_call" = "Ak chcete začať hovor, musíte byť administrátorom alebo moderátorom."; +"room_open_dialpad" = "Číselník"; +"room_message_edits_history_title" = "Úpravy správy"; +"room_resource_usage_limit_reached_message_2" = "niektorí používatelia sa nebudú môcť prihlásiť."; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Tento domovský server dosiahol svoj mesačný limit aktívnych používateľov, takže "; +"room_resource_usage_limit_reached_message_1_default" = "Bol prekročený limit využitia prostriedkov pre tento domovský server, takže "; +"room_resource_limit_exceeded_message_contact_2_link" = "kontaktovať vášho správcu služieb"; +"room_predecessor_link" = "Kliknutím sem zobrazíte staršie správy."; +"room_predecessor_information" = "Táto miestnosť je pokračovaním inej konverzácie."; +"room_replacement_link" = "Konverzácia pokračuje tu."; +"room_replacement_information" = "Táto miestnosť bola nahradená a nie je viac aktívna."; +"room_action_send_photo_or_video" = "Odoslať fotografiu alebo video"; +"room_action_camera" = "Spraviť fotografiu alebo video"; +"room_warning_about_encryption" = "End-to-end šifrovanie je vo fáze beta a nemusí byť spoľahlivé.\n\nNa zabezpečenie údajov by ste mu zatiaľ nemali dôverovať.\n\nZariadenia zatiaľ nebudú schopné dešifrovať históriu z obdobia pred ich pripojením do miestnosti.\n\nŠifrované správy nebudú viditeľné v klientoch, ktoré ešte nemajú zavedené šifrovanie."; +"room_event_action_reaction_history" = "História reakcií"; +"room_event_action_view_encryption" = "Informácie o šifrovaní"; +"room_event_action_delete_confirmation_message" = "Určite chcete túto neodoslanú správu vymazať?"; +"room_event_action_report_prompt_ignore_user" = "Chcete skryť všetky správy od tohto používateľa?"; +"room_event_action_ban_prompt_reason" = "Dôvod zákazu tohto používateľa"; +"room_event_action_kick_prompt_reason" = "Dôvod vylúčenia tohto používateľa"; +"room_event_action_report_prompt_reason" = "Dôvod nahlásenia tohto obsahu"; +"room_conference_call_no_power" = "Potrebujete povolenie na správu konferenčného hovoru v tejto miestnosti"; +"room_unsent_messages_cancel_message" = "Určite chcete vymazať všetky neodoslané správy v tejto miestnosti?"; +"room_unsent_messages_notification" = "Správy sa nepodarilo odoslať."; +"room_offline_notification" = "Spojenie so serverom bolo prerušené."; +"encrypted_room_message_reply_to_placeholder" = "Odoslať šifrovanú odpoveď…"; +"encrypted_room_message_placeholder" = "Odoslať šifrovanú správu…"; +"room_do_not_have_permission_to_post" = "Nemáte povolenie písať do tejto miestnosti"; +"room_message_replying_to" = "Odpoveď na %@"; +"room_message_unable_open_link_error_message" = "Nie je možné otvoriť odkaz."; +"room_message_placeholder" = "Odoslať správu (nešifrovane)…"; +"room_many_users_are_typing" = "%@, %@ a ďalší píšu…"; +"room_two_users_are_typing" = "%@ a %@ píšu…"; +"room_accessiblity_scroll_to_bottom" = "Prejsť na koniec"; + +// Chat +"room_slide_to_end_group_call" = "Posuňte na ukončenie výzvy pre všetkých"; +"room_participants_action_set_default_power_level" = "Obnoviť na normálnu úroveň používateľa"; +"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Nie je nastavený žiadny server totožností, takže nemôžete začať konverzáciu s kontaktom pomocou e-mailu."; +"room_participants_invite_malformed_id_title" = "Chyba pozvania"; +"room_participants_invite_another_user" = "Vyhľadať / pozvať podľa ID používateľa, mena alebo e-mailu"; +"find_your_contacts_identity_service_error" = "Nie je možné sa pripojiť k serveru totožností."; +"find_your_contacts_footer" = "Túto funkciu môžete kedykoľvek vypnúť v nastaveniach."; +"contacts_user_directory_section" = "POUŽÍVATEĽSKÝ ADRESÁR"; +"contacts_user_directory_offline_section" = "POUŽÍVATEĽSKÝ ADRESÁR (offline)"; +"contacts_address_book_permission_denied_alert_message" = "Ak chcete povoliť kontakty, prejdite do nastavení zariadenia."; +"contacts_address_book_permission_denied_alert_title" = "Kontakty sú vypnuté"; +"contacts_address_book_permission_required" = "Povolenie potrebné na prístup k lokálnym kontaktom"; +"directory_search_results_more_than" = ">%tu nájdených výsledkov pre %@"; +"directory_search_results" = "%tu nájdených výsledkov pre %@"; +"search_people_placeholder" = "Vyhľadať podľa ID používateľa, mena alebo e-mailu"; +"rooms_empty_view_information" = "Miestnosti sú skvelé na akýkoľvek skupinový rozhovor, súkromný alebo verejný. Ťuknutím na tlačidlo + vyhľadajte existujúce miestnosti alebo vytvorte nové."; +"people_empty_view_information" = "Bezpečne komunikujte s kýmkoľvek.Ťuknutím na tlačidlo + začnite pridávať ľudí."; +"room_creation_wait_for_creation" = "Miestnosť sa už vytvára. Počkajte, prosím."; +"room_creation_make_public_prompt_msg" = "Ste si istí, že chcete túto konverzáciu zverejniť? Ktokoľvek si môže prečítať vaše správy a pripojiť sa ku konverzácii."; +"social_login_button_title_sign_in" = "Prihlásiť sa s %@"; +"auth_reset_password_error_is_required" = "Nie je nastavený žiadny server identity: pridajte ho v možnostiach servera a obnovte heslo."; +"security_settings_crosssigning_info_trusted" = "Krížové podpisovanie je povolené. Na základe krížového podpisovania môžete dôverovať iným používateľom a ostatným reláciám, ale nemôžete krížovo podpisovať z tejto relácie, pretože nemá súkromné kľúče na krížové podpisovanie. Dokončite zabezpečenie tejto relácie."; +"manage_session_sign_out" = "Odhlásiť sa z tejto relácie"; +"security_settings_crosssigning_info_ok" = "Krížové podpisovanie je pripravené na použitie."; +"settings_discovery_error_message" = "Vyskytla sa chyba. Prosím, skúste to znova."; +"settings_crypto_blacklist_unverified_devices" = "Šifrovať len overeným reláciám"; +"settings_password_updated" = "Vaše heslo bolo aktualizované"; +"settings_notifications_disabled_alert_message" = "Ak chcete povoliť upozornenia, prejdite do nastavení zariadenia."; +"settings_enable_push_notif" = "Oznámenia na tomto zariadení"; +"auth_autodiscover_invalid_response" = "Neplatná odpoveď pri zisťovaní domovského servera"; +"auth_accept_policies" = "Prosím prečítajte si a odsúhlaste zmluvné podmienky tohoto domovského servera:"; +"enable" = "Povoliť"; +// String for App Store +"store_short_description" = "Bezpečné decentralizované konverzácie/VoIP"; +"key_backup_setup_passphrase_confirm_passphrase_invalid" = "fráza sa nezhoduje"; +"key_backup_setup_intro_manual_export_action" = "Exportovať kľúče manuálne"; +"key_backup_setup_skip_alert_title" = "Ste si istí?"; + + +// Cancel + +"secure_key_backup_setup_cancel_alert_title" = "Ste si istí?"; +"deactivate_account_informations_part3" = "\n\nDeaktivácia vášho konta "; + +// Room key request dialog +"e2e_room_key_request_title" = "Žiadosť o šifrovací kľúč"; +"room_widget_permission_user_id_permission" = "Vaše ID používateľa"; +"room_widget_permission_avatar_url_permission" = "URL adresa vášho obrázku"; +"room_widget_permission_display_name_permission" = "Vaše zobrazované meno"; +"widget_menu_open_outside" = "Otvoriť v prehliadači"; +"bug_report_background_mode" = "Pokračovať v pozadí"; +"e2e_key_backup_wrong_version_button_wasme" = "Bol(a) som to ja"; +"call_no_stun_server_error_use_fallback_button" = "Skúste použiť %s"; +"call_incoming_video" = "Prichádzajúci video hovor…"; +"event_formatter_group_call_incoming" = "%@ v %@"; +"event_formatter_call_active_video" = "Aktívny video hovor"; +"event_formatter_call_active_voice" = "Aktívny hovor"; +"event_formatter_call_incoming_video" = "Prichádzajúci video hovor"; +"event_formatter_call_incoming_voice" = "Prichádzajúci hovor"; +"image_picker_action_library" = "Vybrať z knižnice"; + +// Read Receipts +"read_receipts_list" = "Zoznam potvrdení o prečítaní"; + +// Group rooms +"group_rooms_filter_rooms" = "Filtrovať komunitné miestnosti"; +"group_participants_filter_members" = "Filtrovať členov komunity"; +"room_details_copy_room_url" = "Kopírovať URL adresu miestnosti"; +"room_details_copy_room_address" = "Kopírovať adresu miestnosti"; +"room_details_copy_room_id" = "Kopírovať ID miestnosti"; +"room_details_addresses_invalid_address_prompt_title" = "Nesprávny formát aliasu"; +"room_details_new_address" = "Pridať novú adresu"; +"identity_server_settings_alert_disconnect_title" = "Odpojiť server totožností"; +"identity_server_settings_alert_change_title" = "Zmeniť server totožností"; +"security_settings_export_keys_manually" = "Exportovať kľúče manuálne"; +"security_settings_secure_backup_restore" = "Obnoviť zo zálohy"; +"settings_discovery_three_pid_details_cancel_email_validation_action" = "Zrušiť overenie e-mailu"; +"settings_discovery_three_pid_details_title_phone_number" = "Spravovať telefónne číslo"; +"settings_key_backup_button_restore" = "Obnoviť zo zálohy"; +"settings_deactivate_my_account" = "Deaktivovať účet"; +"settings_add_3pid_password_title_msidsn" = "Pridať telefónne číslo"; +"settings_add_3pid_password_title_email" = "Pridať emailovú adresu"; +"settings_term_conditions" = "Podmienky používania"; +"settings_olm_version" = "Olm verzia %@"; +"settings_contacts_enable_sync" = "Nájdite svoje kontakty"; +"settings_show_url_previews" = "Zobraziť náhľad webovej stránky"; +"settings_ui_theme_picker_title" = "Vybrať vzhľad"; +"settings_new_keyword" = "Pridať nové kľúčové slovo"; +"settings_messages_containing_display_name" = "Moje zobrazované meno"; +"settings_encrypted_group_messages" = "Šifrované skupinové správy"; +"settings_encrypted_direct_messages" = "Šifrované priame správy"; +"settings_mentions_and_keywords" = "Zmienky a kľúčové slová"; +"settings_show_decrypted_content" = "Zobraziť dešifrovaný obsah"; +"settings_add_phone_number" = "Pridať telefónne číslo"; +"settings_add_email_address" = "Pridať emailovú adresu"; +"settings_sign_out_confirmation" = "Ste si istí?"; +"settings_config_home_server" = "Domovský server je %@"; +"account_logout_all" = "Odhlásiť sa zo všetkých účtov"; +"room_title_one_active_member" = "%@/%@ aktívny člen"; +"room_title_multiple_active_members" = "%@/%@ aktívnych členov"; +"room_multiple_typing_notification" = "%@ a ďalší"; +"room_event_failed_to_send" = "Nepodarilo sa odoslať"; +"room_event_action_delete_confirmation_title" = "Vymazať neodoslanú správu"; +"room_event_action_view_decrypted_source" = "Zobraziť dešifrovaný zdroj"; +"room_delete_unsent_messages" = "Vymazať neodoslané správy"; +"room_resend_unsent_messages" = "Znovu odoslať neodoslané správy"; +"room_unsent_messages_cancel_title" = "Vymazať neodoslané správy"; +"room_message_reply_to_short_placeholder" = "Odoslať odpoveď…"; +"room_message_short_placeholder" = "Odoslať správu…"; +"room_one_user_is_typing" = "%s píše…"; +"room_new_messages_notification" = "%d nových správ"; +"room_new_message_notification" = "%d nová správa"; +"room_jump_to_first_unread" = "Preskočiť na neprečítanú"; +"find_your_contacts_button_title" = "Nájdite svoje kontakty"; +"social_login_button_title_continue" = "Pokračovať s %@"; +"auth_softlogout_clear_data_button" = "Vymazať všetky údaje"; +"auth_softlogout_clear_data" = "Vymazať osobné údaje"; +"auth_softlogout_signed_out" = "Ste odhlásený"; +"send_to" = "Odoslať do %@"; +"poll_timeline_one_vote" = "1 hlas"; +"room_event_action_end_poll" = "Ukončiť anketu"; +"room_event_action_remove_poll" = "Odstrániť anketu"; +"poll_edit_form_add_option" = "Pridať možnosť"; +"poll_edit_form_option_number" = "Možnosť %lu"; +"poll_edit_form_create_options" = "Vytvoriť možnosti"; +"poll_edit_form_input_placeholder" = "Napíšte niečo"; + +// Mark: - Polls + +"poll_edit_form_create_poll" = "Vytvoriť anketu"; +"version_check_modal_action_title_supported" = "Rozumiem"; +"voice_message_lock_screen_placeholder" = "Hlasová správa"; +"voice_message_remaining_recording_time" = "%1$s ostáva"; +"side_menu_app_version" = "Verzia %s"; +"side_menu_action_invite_friends" = "Pozvať priateľov"; + +// Mark: - Side menu + +"side_menu_reveal_action_accessibility_label" = "Ľavý panel"; +"space_public_join_rule" = "Verejný priestor"; +"space_private_join_rule" = "Súkromný priestor"; +"spaces_coming_soon_title" = "Už čoskoro"; +"spaces_explore_rooms" = "Preskúmať miestnosti"; +"leave_space_title" = "Opustiť %@"; +"room_intro_cell_information_room_with_topic_sentence2" = "Téma: %@"; + +// Mark: - Room creation introduction cell + +"room_intro_cell_add_participants_action" = "Pridať ľudí"; +"call_transfer_dialpad" = "Číselník"; + +// MARK: - Dial Pad +"dialpad_title" = "Číselník"; +"room_info_list_several_members" = "%@ členovia"; + +// MARK: - Room Info + +"room_info_list_one_member" = "1 člen"; +"create_room_section_header_address" = "Adresa miestnosti"; +"create_room_type_public" = "Verejná miestnosť"; +"create_room_type_private" = "Súkromná miestnosť"; +"create_room_section_header_type" = "Typ miestnosti"; +"create_room_enable_encryption" = "Povoliť šifrovanie"; +"create_room_section_header_encryption" = "Šifrovanie miestnosti"; +"create_room_section_header_name" = "Názov miestnosti"; + +// MARK: - Create Room + +"create_room_title" = "Nová miestnosť"; +"searchable_directory_x_network" = "%@ Sieť"; +"biometrics_desetup_disable_button_title_x" = "Vypnúť %@"; +"biometrics_desetup_title_x" = "Vypnúť %@"; +"biometrics_setup_enable_button_title_x" = "Povoliť %@"; +"biometrics_setup_title_x" = "Povoliť %@"; +"biometrics_settings_enable_x" = "Povoliť %@"; +"pin_protection_settings_change_pin" = "Zmeniť PIN"; +"pin_protection_settings_enable_pin" = "Zapnúť PIN"; + +// MARK: - PIN Protection + +"pin_protection_choose_pin_welcome_after_login" = "Vitajte späť."; +"major_update_done_action" = "Rozumiem"; +"major_update_learn_more_action" = "Zistiť viac"; + +// MARK: - Secrets reset + +"secrets_reset_title" = "Obnoviť všetko"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Potvrdiť frázu"; +"secrets_recovery_with_key_recover_action" = "Použiť kľúč"; + +// Recover with key + +"secrets_recovery_with_key_title" = "Bezpečnostný kľúč"; +"secrets_recovery_with_passphrase_recover_action" = "Použiť frázu"; + +// Recover with passphrase + +"secrets_recovery_with_passphrase_title" = "Bezpečnostná fráza"; +"secrets_recovery_reset_action_part_2" = "Obnoviť všetko"; +"user_verification_session_details_verify_action_other_user" = "Manuálne overiť"; +"user_verification_session_details_verify_action_current_user" = "Interaktívne overiť"; +"user_verification_session_details_untrusted_title" = "Nedôveryhodné"; +"user_verification_sessions_list_session_untrusted" = "Nedôveryhodné"; + +// MARK: - User verification + +// Start + +"user_verification_start_verify_action" = "Spustiť overenie"; + +// Scanned +"key_verification_scan_confirmation_scanned_title" = "Už to skoro bude!"; +"key_verification_verify_qr_code_scan_other_code_success_title" = "Kód overený!"; +"key_verification_verify_qr_code_cannot_scan_action" = "Nemôžete naskenovať?"; +"key_verification_tile_request_status_accepted" = "Prijali ste"; +"key_verification_tile_request_status_cancelled" = "%@ bolo zrušené"; +"key_verification_tile_request_status_cancelled_by_me" = "Zrušili ste overenie"; +"key_verification_tile_request_status_data_loading" = "Načítavanie údajov…"; +"key_verification_tile_request_outgoing_title" = "Overenie odoslané"; + +// Tiles + +"key_verification_tile_request_incoming_title" = "Žiadosť o overenie"; + +// MARK: File upload +"file_upload_error_title" = "Nahrávanie súboru"; +"device_verification_emoji_light bulb" = "Žiarovka"; +"device_verification_verified_got_it_button" = "Rozumiem"; +"key_verification_manually_verify_device_key_title" = "Kľúč relácie"; +"key_verification_manually_verify_device_id_title" = "ID relácie"; +"key_verification_manually_verify_device_name_title" = "Názov relácie"; +"key_verification_verify_sas_validate_action" = "Zhodujú sa"; + +// MARK: Verify + +"key_verification_verify_sas_title_emoji" = "Porovnať emoji"; +"device_verification_self_verify_start_verify_action" = "Spustiť overovanie"; + +// Recover from passphrase + +"key_backup_recover_from_passphrase_info" = "Použite svoju bezpečnostnú frázu na odomknutie histórie zabezpečených správ"; + +// Recover from private key +"key_backup_recover_from_private_key_info" = "Obnovenie zálohy…"; +"key_backup_recover_invalid_recovery_key" = "Zálohu sa nepodarilo dešifrovať pomocou tohto kľúča: overte prosím, či ste zadali správny bezpečnostný kľúč."; +"key_backup_recover_invalid_recovery_key_title" = "Nezhoda bezpečnostných kľúčov"; +"key_backup_recover_invalid_passphrase" = "Zálohu sa nepodarilo dešifrovať pomocou tejto frázy: overte prosím, či ste zadali správnu bezpečnostnú frázu."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "( Pokročilé) Nastavenie pomocou bezpečnostného kľúča"; +"secure_key_backup_setup_cancel_alert_message" = "Ak to teraz zrušíte, môžete prísť o zašifrované správy a údaje, ak stratíte prístup k svojim prihlasovacím údajom.\n\nBezpečné zálohovanie a správu kľúčov môžete nastaviť aj v Nastaveniach."; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Zadajte tajnú frázu, ktorú poznáte len vy, a vygenerujte kľúč na zálohovanie."; +"secure_key_backup_setup_intro_use_security_key_info" = "Vygenerujte bezpečnostný kľúč a uložte ho na bezpečné miesto, napríklad do správcu hesiel alebo trezora."; +"deactivate_account_forget_messages_information_part3" = ": toto spôsobí, že budúci používatelia uvidia neúplný prehľad konverzácií)"; +"deactivate_account_forget_messages_information_part1" = "Prosím, zabudnite na všetky správy, ktoré som poslal, keď bude moje konto deaktivované ("; +"deactivate_account_informations_part5" = "Ak chcete, aby sme vaše správy zabudli, označte nižšie uvedené políčko\n\nViditeľnosť správ v Matrixe je podobná ako v prípade e-mailu. Naše zabudnutie vašich správ znamená, že správy, ktoré ste poslali, nebudú zdieľané s novými alebo neregistrovanými používateľmi, ale registrovaní používatelia, ktorí už majú prístup k týmto správam, budú mať stále prístup k ich kópii."; +"deactivate_account_informations_part1" = "Týmto sa vaše konto stane trvalo nepoužiteľným. Nebudete sa môcť prihlásiť a nikto nebude môcť opätovne zaregistrovať rovnaké ID používateľa. To spôsobí, že váš účet opustí všetky miestnosti, ktorých sa zúčastňuje, a odstráni údaje o vašom účte zo servera totožností. "; +"service_terms_modal_description_integration_manager" = "Toto vám umožní používať botov, premostenia, widgety a balíčky nálepiek."; +"service_terms_modal_description_identity_server" = "Vďaka tomu vás niekto nájde, ak má vaše telefónne číslo alebo e-mail uložený v kontaktoch telefónu."; +"widget_menu_revoke_permission" = "Zrušiť prístup pre mňa"; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Pridať nejaké teraz?"; +"widget_creation_failure" = "Vytvorenie widgetu zlyhalo"; +"photo_library_access_not_granted" = "%@ nemá povolenie používať knižnicu fotografií, zmeňte to prosím nastaveniach ochrany súkromia"; +"event_formatter_jitsi_widget_removed_by_you" = "Odstránili ste konferenciu VoIP"; +"event_formatter_jitsi_widget_added_by_you" = "Pridali ste konferenciu VoIP"; +"event_formatter_call_back" = "Zavolať späť"; +"event_formatter_rerequest_keys_part2" = " z vašich ostatných relácií."; +"event_formatter_rerequest_keys_part1_link" = "Opätovne vyžiadať šifrovacie kľúče"; +"event_formatter_jitsi_widget_removed" = "VoIP konferenciu odstránil/a %@"; +"event_formatter_jitsi_widget_added" = "Konferenciu VoIP pridal/a %@"; +"room_notifs_settings_encrypted_room_notice" = "Upozorňujeme, že oznámenia o zmienkach a kľúčových slovách nie sú v mobilných zariadeniach k dispozícii v zašifrovaných miestnostiach."; +"room_details_fail_to_enable_encryption" = "Nepodarilo sa zapnúť šifrovanie v tejto miestnosti"; +"room_details_fail_to_update_room_directory_visibility" = "Nepodarilo sa aktualizovať viditeľnosť adresára miestnosti"; +"identity_server_settings_alert_error_terms_not_accepted" = "Ak ho chcete nastaviť %@ ako server identity, musíte prijať jeho podmienky."; +"identity_server_settings_alert_disconnect_still_sharing_3pid" = "Svoje osobné údaje stále zdieľate na serveri totožností %@.\n\nOdporúčame vám, aby ste pred odpojením odstránili svoje e-mailové adresy a telefónne čísla zo servera totožností."; +"identity_server_settings_disconnect_info" = "Odpojenie od servera totožností bude znamenať, že vás nebudú môcť objaviť iní používatelia a že nebudete môcť pozývať ostatných prostredníctvom e-mailu alebo telefónu."; +"identity_server_settings_description" = "V súčasnosti používate %@ na objavovanie existujúcich kontaktov, ktoré poznáte, a na to, aby vás mohli objaviť."; +"security_settings_crypto_sessions_description_2" = "Ak nespoznávate prihlásenie, zmeňte si heslo a vykonajte obnovenie funkcie Zabezpečené zálohovanie."; +"settings_identity_server_no_is_description" = "Momentálne nepoužívate žiadny server totožností. Ak chcete objaviť existujúce kontakty, ktoré poznáte, a byť nimi objaviteľní, pridajte jeden vyššie."; +"settings_analytics_and_crash_data" = "Odoslať údaje o páde a analytické údaje"; +"settings_labs_create_conference_with_jitsi" = "Vytvárajte konferenčné hovory pomocou aplikácie jitsi"; +"settings_callkit_info" = "Prijímajte prichádzajúce hovory na uzamknutej obrazovke. Prezrite si svoje %@ hovory v histórii hovorov systému. Ak je povolená služba iCloud, táto história hovorov bude zdieľaná so spoločnosťou Apple."; +"settings_notify_me_for" = "Informovať ma o"; +"poll_timeline_votes_count" = "%lu hlasov"; +"poll_edit_form_post_failure_subtitle" = "Prosím, skúste to znova"; +"version_check_modal_action_title_deprecated" = "Zistite ako"; +"version_check_modal_subtitle_deprecated" = "Pracovali sme na vylepšení %@, aby bolo používanie rýchlejšie a dokonalejšie. Bohužiaľ, vaša súčasná verzia systému iOS nie je kompatibilná s niektorými z týchto opráv a nebude ďalej podporovaná.\nOdporúčame vám aktualizovať operačný systém, aby ste mohli používať %@ naplno."; +"version_check_modal_title_deprecated" = "Už nepodporujeme iOS %@"; +"version_check_modal_subtitle_supported" = "Pracovali sme na vylepšení %@, aby bolo používanie rýchlejšie a dokonalejšie. Bohužiaľ, vaša súčasná verzia systému iOS nie je kompatibilná s niektorými z týchto opráv a nebude ďalej podporovaná.\nOdporúčame vám aktualizovať operačný systém, aby ste mohli používať %@ naplno."; +"version_check_modal_title_supported" = "Ukončujeme podporu pre iOS %@"; +"version_check_banner_subtitle_deprecated" = "V systéme iOS %@ už nepodporujeme %@. Ak chcete naďalej používať %@ naplno, odporúčame vám aktualizovať verziu systému iOS."; +"version_check_banner_title_deprecated" = "Už nepodporujeme iOS %@"; +"version_check_banner_subtitle_supported" = "Čoskoro ukončíme podporu pre %@ v systéme iOS %@. Ak chcete naďalej využívať plný potenciál %@, odporúčame vám aktualizovať verziu systému iOS."; + +// Mark: - Version check + +"version_check_banner_title_supported" = "Ukončujeme podporu pre iOS %@"; +"voice_message_stop_locked_mode_recording" = "Ťuknutím na nahrávku ju zastavíte alebo si ju vypočujete"; + +// Mark: - Voice Messages + +"voice_message_release_to_send" = "Podržaním nahrávate, uvoľnením odošlete"; +"user_avatar_view_accessibility_hint" = "Zmeniť obrázok používateľa"; +"space_avatar_view_accessibility_hint" = "Zmeniť obrázok priestoru"; +"space_home_show_all_rooms" = "Zobraziť všetky miestnosti"; +"space_participants_action_ban" = "Vylúčiť z tohto priestoru"; +"space_participants_action_remove" = "Odstrániť z tohto priestoru"; +"spaces_coming_soon_detail" = "Táto funkcia tu ešte nebola zapracovaná, ale je na ceste k jej zapracovaniu. Zatiaľ to môžete urobiť pomocou %@ na vašom počítači."; +"spaces_invites_coming_soon_title" = "Pozvánky už čoskoro"; +"spaces_add_rooms_coming_soon_title" = "Pridávanie miestností už čoskoro"; +"spaces_no_member_found_detail" = "Hľadáte niekoho, kto nie je v %@? Zatiaľ ich môžete pozvať na webe alebo na počítači."; +"spaces_no_room_found_detail" = "Niektoré výsledky môžu byť skryté, pretože sú súkromné a na pripojenie k nim potrebujete pozvánku."; +"spaces_no_result_found_title" = "Nenašli sa žiadne výsledky"; +"spaces_empty_space_detail" = "Niektoré miestnosti môžu byť skryté, pretože sú súkromné a potrebujete pozvánku."; +"leave_space_only_action" = "Neopustiť žiadne miestnosti"; +"leave_space_message_admin_warning" = "Ste administrátorom tohto priestoru, pred odchodom sa uistite, že ste právo administrátora preniesli na iného člena."; +"leave_space_message" = "Ste si istí, že chcete opustiť %@? Chcete opustiť aj všetky miestnosti a priestory tohto priestoru?"; +"space_beta_announce_information" = "Priestory sú novým spôsobom zoskupovania miestností a ľudí. Zatiaľ nie sú v systéme iOS, ale už ich môžete používať na webe a počítači."; +"space_beta_announce_title" = "Priestory budú čoskoro"; +"space_feature_unavailable_information" = "Priestory sú novým spôsobom zoskupovania miestností a ľudí.\n\nBudú tu čoskoro. Zatiaľ platí, že ak sa k nim pripojíte na inej platforme, budete mať prístup ku všetkým miestnostiam, ku ktorým sa pripojíte tu."; +"space_feature_unavailable_subtitle" = "Spaces ešte nie sú v systéme iOS, ale už ich môžete používať na webe a počítači"; + +// Mark: - Spaces + +"space_feature_unavailable_title" = "Priestory tu ešte nie sú"; +"room_intro_cell_information_dm_sentence1_part1" = "Toto je začiatok vašej priamej správy s "; +"room_intro_cell_information_room_sentence1_part1" = "Toto je začiatok "; +"home_empty_view_information" = "Univerzálna aplikácia na bezpečný konverzáciu pre tímy, priateľov a organizácie. Ťuknutím na tlačidlo + nižšie pridáte ľudí a miestnosti."; +"create_room_section_footer_type" = "Do súkromnej miestnosti sa ľudia môžu pripojiť len s pozvánkou do miestnosti."; +"create_room_section_footer_encryption" = "Šifrovanie sa potom nedá vypnúť."; +"biometrics_cant_unlocked_alert_message_login" = "Prihlásiť sa znova"; +"biometrics_cant_unlocked_alert_message_x" = "Ak chcete odomknúť, použite %@ alebo sa prihláste a zapnite %@ znova"; +"biometrics_cant_unlocked_alert_title" = "Nie je možné odomknúť aplikáciu"; +"biometrics_usage_reason" = "Na prístup k aplikácii je potrebné overenie"; +"biometrics_setup_subtitle" = "Ušetrite si čas"; +"pin_protection_kick_user_alert_message" = "Príliš veľa chýb, boli ste odhlásení"; +"pin_protection_explanatory" = "Nastavenie kódu PIN umožňuje chrániť údaje, ako sú správy a kontakty, takže prístup k nim získate len vy zadaním kódu PIN na začiatku aplikácie."; +"pin_protection_not_allowed_pin" = "Z bezpečnostných dôvodov nie je tento kód PIN k dispozícii. Prosím, skúste iný PIN"; +"pin_protection_settings_enabled_forced" = "Zapnutý kód PIN"; +"pin_protection_settings_section_footer" = "Ak chcete obnoviť kód PIN, musíte sa znovu prihlásiť a vytvoriť nový kód PIN."; +"pin_protection_settings_section_header_with_biometrics" = "PIN a %@"; +"pin_protection_mismatch_too_many_times_error_message" = "Ak si nemôžete spomenúť na svoj kód PIN, ťuknite na tlačidlo Zabudnutý kód PIN."; +"pin_protection_mismatch_error_message" = "Prosím, skúste to znova"; +"pin_protection_mismatch_error_title" = "PIN kódy sa nezhodujú"; +"pin_protection_reset_alert_message" = "Ak chcete obnoviť kód PIN, musíte sa znovu prihlásiť a vytvoriť nový kód PIN"; +"secrets_reset_warning_message" = "Reštartujete bez histórie, bez správ, dôveryhodných zariadení a dôveryhodných používateľov."; +"secrets_reset_warning_title" = "Ak všetko obnovíte do pôvodného stavu"; +"secrets_reset_information" = "Urobte to len vtedy, ak nemáte žiadne iné zariadenie, pomocou ktorého by ste mohli toto zariadenie overiť."; +"secrets_recovery_with_key_information_default" = "Prístup k histórii zabezpečených správ a k totožnosti krížového podpisu na overenie iných relácií získate zadaním bezpečnostného kľúča."; +"user_verification_session_details_additional_information_untrusted_other_user" = "Pokiaľ tento používateľ nezačne tejto relácii dôverovať, správy odoslané do nej a z nej sú označené varovaním. Môžete ju tiež overiť ručne."; + +// MARK: Manually Verify Device + +"key_verification_manually_verify_device_title" = "Ručné overenie pomocou textu"; +"device_verification_self_verify_wait_recover_secrets_checking_availability" = "Kontrola ďalších možností overovania ..."; +"device_verification_self_verify_wait_recover_secrets_additional_information" = "Ak nemáte prístup k existujúcej relácii"; +"device_verification_error_cannot_load_device" = "Nie je možné načítať informácie o relácii."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Ak si pred odhlásením nezálohujete kľúče, stratíte prístup k zašifrovaným správam."; +"find_your_contacts_message" = "Nechajte %@ zobraziť svoje kontakty, aby ste mohli rýchlo začať komunikovať s tými, ktorých najlepšie poznáte."; +"callbar_only_multiple_paused" = "%@ pozastavené hovory"; +"identity_server_settings_alert_change" = "Odpojiť sa od servera totožnosti %1$@ a namiesto toho sa pripojiť k %2$@?"; +"identity_server_settings_alert_no_terms" = "Vami vybratý server totožností nemá podmienky používania služieb. Pokračujte len v prípade, že dôverujete vlastníkovi servera."; +"identity_server_settings_alert_no_terms_title" = "Server totožností nemá žiadne podmienky poskytovania služieb"; +"identity_server_settings_no_is_description" = "Momentálne nepoužívate server totožností. Ak chcete nájsť a byť objaviteľní existujúcimi kontaktmi, pridajte jeden vyššie."; +"security_settings_crosssigning_complete_security" = "Dokončiť zabezpečenie"; +"security_settings_crosssigning_info_exists" = "Váš účet má totožnosť krížového podpisu, ale táto relácia mu ešte nedôveruje. Dokončite zabezpečenie tejto relácie."; +"security_settings_secure_backup_description" = "Zálohujte si šifrovacie kľúče s údajmi o účte pre prípad, že stratíte prístup k reláciám. Vaše kľúče budú zabezpečené jedinečným bezpečnostným kľúčom."; +"settings_identity_server_description" = "Pomocou vyššie nastaveného servera totožnosti môžete nájsť existujúce kontakty, ktoré poznáte, a aj oni vás môžu nájsť."; +"settings_discovery_three_pid_details_information_phone_number" = "Spravujte predvoľby pre toto telefónne číslo, ktoré môžu ostatní používatelia použiť na vaše objavenie a pozvanie do miestností. Pridajte alebo odstráňte telefónne čísla v časti Účty."; +"settings_discovery_three_pids_management_information_part1" = "Spravujte, ktoré e-mailové adresy alebo telefónne čísla môžu ostatní používatelia použiť na vaše objavenie a pozvanie do miestností. E-mailové adresy alebo telefónne čísla môžete pridávať alebo odstraňovať z tohto zoznamu v "; +"settings_discovery_terms_not_signed" = "Musíte súhlasiť s podmienkami služby servera totožnosti (%@), aby vás mohli nájsť podľa e-mailovej adresy alebo telefónneho čísla."; +"settings_key_backup_button_connect" = "Pripojiť túto reláciu k zálohovaniu kľúčov"; +"settings_fail_to_update_password" = "Nepodarilo sa aktualizovať heslo"; +"settings_labs_enable_ringing_for_group_calls" = "Zvonenie pre skupinové hovory"; +"widget_integration_positive_power_level" = "Úroveň oprávnenia musí byť kladné celé číslo."; +"settings_integrations_allow_description" = "Použite správcu integrácií (%@) na spravovanie botov, premostení, widgetov a balíčkov s nálepkami. \n\nSprávcovia integrácie dostávajú konfiguračné údaje a môžu vo vašom mene upravovať widgety, posielať pozvánky do miestnosti a nastavovať úrovne oprávnení."; +"settings_mentions_and_keywords_encryption_notice" = "V mobilných zariadeniach nebudete dostávať upozornenia na zmienky a kľúčové slová v zašifrovaných miestnostiach."; +"settings_your_keywords" = "Vaše kľúčové slová"; +"settings_messages_by_a_bot" = "Správy od bota"; +"settings_call_invitations" = "Pozvánky na hovory"; +"settings_room_invitations" = "Pozvánky do miestnosti"; +"settings_global_settings_info" = "Všeobecné nastavenia oznámení sú k dispozícii vo webovom klientovi %@"; +"settings_confirm_media_size_description" = "Keď je táto funkcia zapnutá, budete vyzvaní, aby ste potvrdili, v akej veľkosti sa budú obrázky a videá odosielať."; +"settings_three_pids_management_information_part1" = "Tu môžete spravovať e-mailové adresy alebo telefónne čísla, ktoré môžete použiť na prihlásenie alebo obnovenie účtu. Ovládajte, kto vás môže nájsť v "; +"settings_fail_to_update_profile" = "Nepodarilo sa aktualizovať profil"; +"settings_email_address_placeholder" = "Zadajte svoju e-mailovú adresu"; +"settings_remove_phone_prompt_msg" = "Určite chcete odstrániť telefónne číslo %@?"; +"settings_remove_email_prompt_msg" = "Určite chcete odstrániť e-mailovú adresu %@?"; +"settings_sign_out_e2e_warn" = "Prídete o svoje end-to-end šifrovacie kľúče. To znamená, že v tomto zariadení už nebudete môcť čítať staré správy v zašifrovaných miestnostiach."; +"settings_labs" = "EXPERIMENTÁLNE"; +"room_ongoing_conference_call_with_close" = "Prebiehajúci konferenčný hovor. Pripojte sa ako %@ alebo %@. %@ to."; +"room_ongoing_conference_call" = "Prebiehajúci konferenčný hovor. Pripojte sa ako %@ alebo %@."; +"room_unsent_messages_unknown_devices_notification" = "Správu sa nepodarilo odoslať z dôvodu prítomnosti neznámych relácií."; +"room_participants_action_security_status_complete_security" = "Dokončiť zabezpečenie"; +"room_participants_invite_malformed_id" = "Chybné ID. Mala by to byť emailová adresa alebo Matrix ID ako napríklad \"@lokalnacast:domena\""; +"directory_search_fail" = "Nepodarilo sa načítať údaje"; +"directory_search_results_title" = "Prehľadávať výsledky adresára"; +"room_creation_dm_error" = "Nemohli sme vytvoriť vašu PS. Skontrolujte používateľov, ktorých chcete pozvať, a skúste to znova."; +"auth_softlogout_clear_data_sign_out_msg" = "Ste si istí, že chcete vymazať všetky údaje aktuálne uložené v tomto zariadení? Znovu sa prihláste, aby ste získali prístup k údajom a správam svojho účtu."; +"auth_softlogout_clear_data_message_2" = "Vyčistite to, ak ste skončili s používaním tohto zariadenia alebo sa chcete prihlásiť do iného účtu."; +"auth_softlogout_clear_data_message_1" = "Varovanie: Vaše osobné údaje (vrátane šifrovacích kľúčov) sú stále uložené na tomto zariadení."; +"auth_softlogout_recover_encryption_keys" = "Prihláste sa, aby ste obnovili šifrovacie kľúče uložené výlučne v tomto zariadení. Potrebujete ich na čítanie všetkých svojich zabezpečených správ na akomkoľvek zariadení."; +"auth_softlogout_reason" = "Váš správca domovského servera (%1$@) vás odhlásil z vášho účtu %2$@ (%3$@)."; +"auth_add_email_and_phone_warning" = "Registrácia pomocou e-mailu a telefónneho čísla naraz zatiaľ nie je podporovaná, kým nebude existovať api. Do úvahy sa bude brať iba telefónne číslo. Svoj e-mail môžete pridať do svojho profilu v nastaveniach."; +"auth_reset_password_success_message" = "Vaše heslo bolo obnovené.\n\nBoli ste odhlásení zo všetkých relácií a už nebudete dostávať push oznámenia. Ak chcete opätovne povoliť oznámenia, znovu sa prihláste na každom zariadení."; +"auth_untrusted_id_server" = "Server totožností nie je dôveryhodný"; +"auth_phone_is_required" = "Nie je nastavený žiadny server totožností, takže nemôžete pridať e-telefónne číslo, aby ste mohli v budúcnosti obnoviť svoje heslo."; +"auth_email_is_required" = "Nie je nastavený žiadny server totožností, takže nemôžete pridať e-mailovú adresu, aby ste mohli v budúcnosti obnoviť svoje heslo."; +"auth_add_email_phone_message_2" = "Nastavte si e-mail na obnovenie účtu. Neskôr môžete voliteľne použiť e-mail alebo telefón, aby vás mohli nájsť ľudia, ktorí vás poznajú."; +"cross_signing_setup_banner_subtitle" = "Jednoduchšie overenie ostatných zariadení"; +"room_details_advanced_e2e_encryption_blacklist_unverified_devices" = "Šifrovať len overeným reláciám"; +"spaces_empty_space_title" = "Tento priestor nemá žiadne miestnosti (zatiaľ)"; +"leave_space_and_all_rooms_action" = "Opustiť všetky miestnosti a priestory"; +"space_beta_announce_subtitle" = "Nová verzia komunít"; +"room_intro_cell_information_multiple_dm_sentence2" = "V tejto konverzácii ste len vy, pokiaľ niekto z vás niekoho nepozve, aby sa pripojil."; +"room_intro_cell_information_dm_sentence2" = "V tejto konverzácii ste len vy dvaja, nikto iný sa nemôže pripojiť."; +"room_intro_cell_information_room_without_topic_sentence2_part2" = " aby ľudia vedeli, o čom je táto miestnosť."; +"room_intro_cell_information_room_without_topic_sentence2_part1" = "Pridať tému"; +"room_avatar_view_accessibility_hint" = "Zmeniť obrázok miestnosti"; +"invite_friends_share_text" = "Hej, ozvi sa mi na %@: %@"; + +// MARK: - Invite friends + +"invite_friends_action" = "Pozvať priateľov do %@"; +"favourites_empty_view_information" = "Obľúbiť si môžete niekoľkými spôsobmi - najrýchlejšie je jednoducho stlačiť a podržať. Ťuknite na hviezdičku a automaticky sa tu zobrazia na bezpečné uloženie."; + +// MARK: - Favourites + +"favourites_empty_view_title" = "Obľúbené miestnosti a ľudia"; +"create_room_show_in_directory" = "Zobraziť miestnosť v adresári"; +"create_room_section_header_topic" = "Téma miestnosti (voliteľné)"; +"searchable_directory_search_placeholder" = "Meno alebo ID"; + +// MARK: - Searchable Directory View Controller + +"searchable_directory_create_new_room" = "Vytvoriť novú miestnosť"; +"biometrics_mode_face_id" = "Face ID"; + +// MARK: - Biometrics Protection + +"biometrics_mode_touch_id" = "Touch ID"; +"pin_protection_reset_alert_title" = "Obnoviť PIN"; +"pin_protection_forgot_pin" = "Zabudli ste PIN kód"; +"pin_protection_enter_pin" = "Zadajte svoj kód PIN"; +"pin_protection_confirm_pin_to_change" = "Potvrďte kód PIN, na zmenu kódu PIN"; +"pin_protection_confirm_pin_to_disable" = "Potvrďte kód PIN, ak chcete deaktivovať kód PIN"; +"pin_protection_confirm_pin" = "Potvrďte svoj kód PIN"; +"pin_protection_choose_pin" = "Vytvoriť bezpečnostný kód PIN"; +"major_update_information" = "S radosťou oznamujeme, že sme zmenili názov! Vaša aplikácia je aktualizovaná a ste prihlásení do svojho účtu."; + +// MARK: - Major update + +"major_update_title" = "Riot je teraz %@"; + +// MARK: - Cross-signing + +// Banner + +"cross_signing_setup_banner_title" = "Nastaviť šifrovanie"; +"secrets_reset_authentication_message" = "Zadajte heslo svojho účtu pre potvrdenie"; +"secrets_setup_recovery_passphrase_summary_information" = "Zapamätajte si bezpečnostnú frázu. Môžete ju použiť na odomknutie zašifrovaných správ a údajov."; +"secrets_setup_recovery_passphrase_summary_title" = "Uložte si bezpečnostnú frázu"; +"secrets_setup_recovery_passphrase_confirm_information" = "Opätovne zadajte bezpečnostnú frázu a potvrďte ju."; +"secrets_setup_recovery_passphrase_additional_information" = "Nepoužívajte heslo k svojmu účtu."; +"secrets_setup_recovery_passphrase_information" = "Zadajte bezpečnostnú frázu, ktorú poznáte len vy a ktorá sa používa na zabezpečenie tajomstiev na vašom serveri."; +"secrets_recovery_with_key_recovery_key_title" = "Zadajte"; +"secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Ak chcete pokračovať, zadajte bezpečnostný kľúč."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Ak chcete pokračovať, zadajte bezpečnostnú frázu."; +"secrets_recovery_with_key_information_verify_device" = "Na overenie tohto zariadenia použite bezpečnostná kľúč."; +"secrets_recovery_with_passphrase_passphrase_title" = "Zadajte"; +"secrets_recovery_with_passphrase_information_default" = "Prístup k histórii zabezpečených správ a k totožnosti krížového podpisu na overenie iných relácií získate zadaním bezpečnostnej frázy."; + +// MARK: - Secrets recovery + +"secrets_recovery_reset_action_part_1" = "Zabudli ste alebo ste stratili všetky možnosti obnovy? "; +"user_verification_session_details_verify_action_current_user_manually" = "Ručné overenie pomocou textu"; +"user_verification_session_details_additional_information_untrusted_current_user" = "Ak ste sa do tejto relácie neprihlásili, vaše konto môže byť ohrozené."; +"key_verification_tile_request_status_expired" = "Vypršala platnosť"; + + +// Generic errors +"error_invite_3pid_with_no_identity_server" = "V nastaveniach pridajte server totožnosti, ak chcete pozývať e-mailom."; +"emoji_picker_people_category" = "Smajlíky a ľudia"; +"file_upload_error_unsupported_file_type_message" = "Nepodporovaný typ súboru."; +"key_verification_self_verify_unverified_sessions_alert_message" = "Overte všetky vaše relácie, aby ste si boli istý, že sú vaše správy a účet bezpečné."; +"sign_out_non_existing_key_backup_alert_title" = "Ak sa teraz odhlásite, prídete o zašifrované správy"; +"device_verification_emoji_thumbs up" = "Palec hore"; + +// Device + +"device_verification_verify_wait_partner" = "Čakanie na potvrdenie od partnera…"; +"key_verification_manually_verify_device_additional_information" = "Ak sa nezhodujú, môže byť ohrozená bezpečnosť vašej komunikácie."; +"key_verification_manually_verify_device_instruction" = "Potvrďte to porovnaním nasledujúcich údajov s nastaveniami používateľa v inej relácii:"; +"key_verification_verify_sas_additional_information" = "V záujme maximálnej bezpečnosti použite iný dôveryhodný komunikačný prostriedok alebo to urobte osobne."; +"key_verification_verify_sas_cancel_action" = "Nezhodujú sa"; +"key_verification_verify_sas_title_number" = "Porovnať čísla"; +"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Použiť bezpečnostnú frázu alebo kľúč"; +"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Použiť bezpečnostný kľúč"; +"device_verification_self_verify_wait_additional_information" = "Toto funguje s %@ a inými klientmi Matrix podporujúcimi krížové podpisovanie."; +"device_verification_self_verify_wait_information" = "Overte túto reláciu z niektorej z vašich ostatných relácií, čím jej udelíte prístup k zašifrovaným správam.\n\nNa ostatných zariadeniach použite najnovší %@:"; +"device_verification_self_verify_wait_new_sign_in_title" = "Overiť toto prihlásenie"; + +// MARK: Self verification wait + +"device_verification_self_verify_wait_title" = "Dokončiť zabezpečenie"; +"key_verification_self_verify_unverified_sessions_alert_validate_action" = "Overiť"; + +// Unverified sessions + +"key_verification_self_verify_unverified_sessions_alert_title" = "Zobraziť, kde ste prihlásený"; +"key_verification_self_verify_current_session_alert_message" = "Ostatní používatelia jej nemusia dôverovať."; + +// Current session + +"key_verification_self_verify_current_session_alert_title" = "Overiť túto reláciu"; +"device_verification_self_verify_start_information" = "Túto reláciu použite na overenie novej relácie, čím jej udelíte prístup k zašifrovaným správam."; +"device_verification_self_verify_alert_message" = "Overte nové prihlásenie prístupom k vášmu účtu: %@"; + +// MARK: Self verification start + +// New login +"device_verification_self_verify_alert_title" = "Nové prihlásenie. Boli ste to vy?"; +"device_verification_start_use_legacy_action" = "Použiť starší spôsob overenia"; +"device_verification_start_verify_button" = "Začať overenie"; +"device_verification_start_use_legacy" = "Nič sa neobjavuje? Nie všetci klienti ešte podporujú interaktívne overovanie. Použite staršie overovanie."; +"device_verification_start_wait_partner" = "Čaká sa na prijatie partnerom…"; + +// MARK: Start +"device_verification_start_title" = "Overiť porovnaním krátkeho textu"; +"device_verification_incoming_description_2" = "Overenie tejto relácie ju označí ako dôveryhodnú a zároveň označí vašu reláciu ako dôveryhodnú pre partnera."; +"device_verification_incoming_description_1" = "Overte túto reláciu a označte ju ako dôveryhodnú. Dôveryhodnosť relácií partnerov vám poskytuje pokoj na duši pri používaní end-to-end šifrovaných správ."; + +// Mark: Incoming +"device_verification_incoming_title" = "Prichádzajúca žiadosť o overenie"; +"device_verification_cancelled_by_me" = "Overovanie bolo zrušené. Dôvod: %@"; +"device_verification_cancelled" = "Druhá strana zrušila overenie."; +"device_verification_security_advice_number" = "Porovnajte čísla a uistite sa, že sú v rovnakom poradí."; +"device_verification_security_advice_emoji" = "Porovnajte jedinečné emoji a uistite sa, že sú zobrazené v rovnakom poradí."; +"key_verification_this_session_title" = "Overiť túto reláciu"; +"key_verification_new_session_title" = "Overte svoju novú reláciu"; + +// MARK: - Device Verification +"key_verification_other_session_title" = "Overiť reláciu"; +"sign_out_key_backup_in_progress_alert_cancel_action" = "Počkám"; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Nezáleží mi na zašifrovaných správach"; +"sign_out_key_backup_in_progress_alert_title" = "Prebieha zálohovanie šifrovacích kľúčov. Ak sa teraz odhlásite, prídete o zašifrované správy."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Odhlásiť sa"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Stratíte svoje zašifrované správy"; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Nezáleží mi na zašifrovaných správach"; +"sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Začnite používať Bezpečné zálohovanie"; +"sign_out_existing_key_backup_alert_sign_out_action" = "Odhlásiť sa"; + +// MARK: Sign out warning + +"sign_out_existing_key_backup_alert_title" = "Naozaj sa chcete odhlásiť?"; + +// Success + +"key_backup_recover_success_info" = "Záloha obnovená!"; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Stratili ste kľúč obnovenia, v nastaveniach získate nový."; +"key_backup_recover_from_recovery_key_recover_action" = "Sprístupniť históriu"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Zadajte bezpečnostný kľúč"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "použiť váš bezpečnostný kľúč"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Neviete svoju bezpečnostnú frázu? Môžete "; +"key_backup_recover_from_passphrase_recover_action" = "Sprístupniť históriu"; +"key_backup_recover_from_passphrase_passphrase_placeholder" = "Zadajte frázu"; + +// Success from recovery key +"key_backup_setup_success_from_recovery_key_info" = "Vaše kľúče sa zálohujú.\n\nVytvorte si kópiu tohto bezpečnostného kľúča a uschovajte ho na bezpečnom mieste."; +"key_backup_setup_passphrase_passphrase_invalid" = "Skúste pridať slovo"; +"key_backup_setup_passphrase_info" = "Šifrovanú kópiu vašich kľúčov uložíme na našom serveri. Chráňte svoju zálohu frázou, aby bola bezpečná.\n\nV záujme maximálneho zabezpečenia by sa mala líšiť od hesla k vášmu účtu."; +"key_backup_setup_intro_info" = "Správy v šifrovaných miestnostiach sú zabezpečené end-to-end šifrovaním. Kľúče na čítanie týchto správ máte len vy a príjemca (príjemcovia).\n\nKľúče si bezpečne zálohujte, aby ste ich nestratili."; +"secure_key_backup_setup_existing_backup_error_delete_it" = "Vymazať"; +"secure_key_backup_setup_existing_backup_error_unlock_it" = "Odomknúť"; +"secure_key_backup_setup_existing_backup_error_title" = "Záloha pre správy už existuje"; +"service_terms_modal_information_description_integration_manager" = "Správca integrácie umožňuje pridávať funkcie od tretích strán."; +"service_terms_modal_information_description_identity_server" = "Server totožností vám pomôže nájsť kontakty tak, že vyhľadá ich telefónne číslo alebo e-mailovú adresu a zistí, či už majú účet."; +"service_terms_modal_table_header_integration_manager" = "PODMIENKY SPRÁVCU INTEGRÁCIE"; +"service_terms_modal_table_header_identity_server" = "PODMIENKY SERVERA TOTOŽNOSTÍ"; +"service_terms_modal_footer" = "Toto môžete kedykoľvek vypnúť v nastaveniach."; + +// Service terms +"service_terms_modal_title_message" = "Ak chcete pokračovať, prijmite nižšie uvedené podmienky"; +"gdpr_consent_not_given_alert_review_now_action" = "Prečítať teraz"; + +// GDPR +"gdpr_consent_not_given_alert_message" = "Ak chcete pokračovať v používaní domovského servera %@, musíte si prečítať podmienky a súhlasiť s nimi."; +"e2e_room_key_request_share_without_verifying" = "Zdieľať bez overenia"; +"e2e_room_key_request_message" = "Vaša neoverená relácia '%@' požaduje šifrovacie kľúče."; +"e2e_room_key_request_message_new_device" = "Pridali ste novú reláciu \"%@\", ktorá požaduje šifrovacie kľúče."; +"share_extension_low_quality_video_message" = "Ak chcete poslať v lepšej kvalite, pošlite vo formáte %@ alebo pošlite v nízkej kvalite nižšie."; +"share_extension_low_quality_video_title" = "Video bude odoslané v nízkej kvalite"; +"room_widget_permission_information_title" = "Používaním zdieľate údaje s %@:\n"; +"room_widget_permission_webview_information_title" = "Používaním prijímate cookies od a zdieľate údaje %@:\n"; +"room_widget_permission_creator_info_title" = "Tento widget pridal:"; +"widget_integration_manager_disabled" = "V nastaveniach je potrebné povoliť správcu integrácie"; +"widget_integration_room_not_visible" = "Miestnosť %s nie je viditeľná."; +"widget_integration_missing_user_id" = "V požiadavke chýba user_id."; +"widget_integration_missing_room_id" = "V požiadavke chýba room_id."; +"widget_integration_no_permission_in_room" = "V tejto miestnosti na to nemáte povolenie."; +"widget_integration_must_be_in_room" = "Nenachádzate sa v tejto miestnosti."; +"widget_integration_room_not_recognised" = "Nie je možné rozpoznať takúto miestnosť."; +"widget_integration_failed_to_send_request" = "Nepodarilo sa odoslať požiadavku."; +"widget_integration_unable_to_create" = "Nie je možné vytvoriť widget."; +"widget_menu_remove" = "Odstrániť pre všetkých"; +"widget_no_power_to_manage" = "Musíte mať povolenie spravovať widgety v tejto miestnosti"; + +// Widget +"widget_no_integrations_server_configured" = "Nie je nastavený žiadny server integrácií"; +"bug_report_progress_zipping" = "Získavajú sa záznamy"; +"bug_report_send_logs" = "Odoslať záznamy"; +"bug_crash_report_description" = "Opíšte prosím, čo ste robili pred pádom aplikácie:"; +"bug_crash_report_title" = "Správa o poruche"; +"e2e_key_backup_wrong_version" = "Bola zistená nová záloha zabezpečenej správy.\n\nAk ste to neboli vy, nastavte novú bezpečnostnú frázu v Nastaveniach."; + +// Key backup wrong version +"e2e_key_backup_wrong_version_title" = "Nová záloha kľúča"; + +// Crypto +"e2e_enabling_on_app_update" = "%@ teraz podporuje end-to-end šifrovanie, ale na jeho zapnutie sa musíte znova prihlásiť.\n\nMôžete to urobiť teraz alebo neskôr v nastaveniach aplikácie."; +"analytics_prompt_stop" = "Zastaviť zdieľanie"; +"analytics_prompt_yes" = "Áno, je to v poriadku"; +"analytics_prompt_message_upgrade" = "Predtým ste nám udelili súhlas so zdieľaním anonymných údajov o používaní. Teraz, aby sme pomohli pochopiť, ako ľudia používajú viacero zariadení, vygenerujeme náhodný identifikátor zdieľaný vašimi zariadeniami."; +"analytics_prompt_message_new_user" = "Pomôžte nám identifikovať problémy a zlepšiť %@ zdieľaním anonymných údajov o používaní. Aby sme pochopili, ako ľudia používajú viacero zariadení, vygenerujeme náhodný identifikátor zdieľaný vašimi zariadeniami."; + +// Analytics +"analytics_prompt_title" = "Pomôžte zlepšiť %@"; +"call_already_displayed" = "Telefonát už prebieha."; +"network_offline_prompt" = "Zdá sa, že nie ste pripojený na internet."; +"event_formatter_widget_removed_by_you" = "Odstránili ste widget: %@"; + +// Events formatter with you +"event_formatter_widget_added_by_you" = "Pridali ste widget: %@"; +"event_formatter_call_missed_video" = "Zmeškaný video hovor"; +"event_formatter_call_missed_voice" = "Zmeškaný hlasový hovor"; +"event_formatter_call_has_ended_with_time" = "Hovor ukončený - %@"; +"event_formatter_widget_removed" = "%@ widget odstránil %@"; +"event_formatter_widget_added" = "%@ widget pridal %@"; +"directory_server_type_homeserver" = "Zadajte domovský server, z ktorého chcete zobraziť zoznam verejných miestností"; +"directory_server_all_native_rooms" = "Všetky natívne miestnosti Matrix"; +"directory_server_all_rooms" = "Všetky miestnosti na serveri %s"; +"directory_server_picker_title" = "Vybrať adresár"; + +// Media picker +"media_picker_title" = "Knižnica médií"; +"group_participants_invite_malformed_id" = "Chybné ID. Malo by to byť Matrix ID ako napríklad “@lokalnacast:domena“"; +"group_participants_invite_another_user" = "Vyhľadať / pozvať podľa ID používateľa alebo mena"; +"group_participants_invite_prompt_msg" = "Ste si istí, že chcete pozvať %@ do tejto skupiny?"; +"group_participants_remove_prompt_msg" = "Ste si istí, že chcete odstrániť %@ z tejto skupiny?"; +"group_participants_leave_prompt_msg" = "Ste si istí, že chcete opustiť skupinu?"; +"group_invitation_format" = "%@ vás pozval do tejto komunity"; +"room_notifs_settings_manage_notifications" = "Oznámenia môžete spravovať v %@"; +"room_notifs_settings_mentions_and_keywords" = "Iba zmienky a kľúčové slová"; + +// Room Notification Settings +"room_notifs_settings_notify_me_for" = "Upozorniť ma na"; +"room_details_unset_main_address" = "Zrušiť nastavenie ako hlavnej adresy"; +"room_details_set_main_address" = "Nastaviť ako hlavnú adresu"; +"room_details_save_changes_prompt" = "Chcete uložiť zmeny?"; +"room_details_fail_to_update_room_communities" = "Nepodarilo sa aktualizovať príbuzné komunity"; +"room_details_fail_to_update_room_canonical_alias" = "Nepodarilo sa aktualizovať hlavnú adresu"; +"room_details_fail_to_remove_room_aliases" = "Nepodarilo sa odstrániť adresy miestností"; +"room_details_fail_to_add_room_aliases" = "Nepodarilo sa pridať nové adresy miestností"; +"room_details_fail_to_update_history_visibility" = "Nepodarilo sa aktualizovať viditeľnosť histórie"; +"room_details_fail_to_update_room_guest_access" = "Nepodarilo sa aktualizovať prístup hosťa do miestnosti"; +"room_details_fail_to_update_avatar" = "Nepodarilo sa aktualizovať fotografiu miestnosti"; +"room_details_fail_to_update_topic" = "Nepodarilo sa aktualizovať tému"; +"room_details_fail_to_update_room_name" = "Nepodarilo sa aktualizovať názov miestnosti"; +"room_details_advanced_e2e_encryption_disabled_for_dm" = "Šifrovanie tu nie je zapnuté."; +"room_details_advanced_e2e_encryption_disabled" = "V tejto miestnosti nie je zapnuté šifrovanie."; +"room_details_advanced_e2e_encryption_enabled_for_dm" = "Je tu zapnuté šifrovanie"; +"room_details_advanced_e2e_encryption_enabled" = "V tejto miestnosti je zapnuté šifrovanie"; +"room_details_advanced_enable_e2e_encryption" = "Povoliť šifrovanie (Pozor: nie je možné ho znova vypnúť!)"; +"room_details_flair_invalid_id_prompt_msg" = "%@ nie je platný identifikátor komunity"; +"room_details_new_flair_placeholder" = "Pridať nové ID komunity (napr. +foo%@)"; +"room_details_addresses_invalid_address_prompt_msg" = "%@ nie je platný formát pre alias"; +"room_details_new_address_placeholder" = "Pridať novú adresu (napr. #foo%@)"; +"room_details_no_local_addresses_for_dm" = "Táto nemá žiadne lokálne adresy"; +"room_details_no_local_addresses" = "Táto miestnosť nemá žiadne lokálne adresy"; +"room_details_history_section_prompt_msg" = "Zmeny týkajúce sa toho, kto môže čítať históriu, sa budú vzťahovať len na budúce správy v tejto miestnosti. Viditeľnosť existujúcej histórie zostane nezmenená."; +"room_details_history_section_prompt_title" = "Upozornenie o ochrane súkromia"; +"room_details_history_section_members_only_since_joined" = "Len členovia (odkedy vstúpili)"; +"room_details_history_section_members_only_since_invited" = "Len členovia (odkedy boli pozvaní)"; +"room_details_history_section_members_only" = "Len členovia (odkedy je táto voľba aktivovaná)"; +"room_details_history_section" = "Kto môže čítať históriu?"; +"room_details_access_section_directory_toggle_for_dm" = "Uverejniť v adresári miestností"; +"room_details_access_section_directory_toggle" = "Uverejniť túto miestnosť v adresári miestností"; +"room_details_access_section_no_address_warning" = "Ak chcete vytvoriť odkaz do miestnosti, musíte najprv nastaviť jej adresu"; +"room_details_access_section_anyone_for_dm" = "Ktokoľvek, kto pozná odkaz, vrátane hostí"; +"room_details_access_section_anyone_apart_from_guest" = "Ktokoľvek, kto pozná odkaz na miestnosť, okrem hostí"; +"room_details_access_section_anyone_apart_from_guest_for_dm" = "Ktokoľvek, kto pozná odkaz, okrem hostí"; +"room_details_access_section_anyone" = "Ktokoľvek, kto pozná odkaz na miestnosť, vrátane hostí"; +"room_details_access_section_invited_only" = "Iba ľudia, ktorí boli pozvaní"; +"room_details_access_section_for_dm" = "Kto má k tomuto prístup?"; +"room_details_access_section" = "Kto má prístup do tejto miestnosti?"; +"identity_server_settings_alert_error_invalid_identity_server" = "%@ nie je platný server totožností."; +"identity_server_settings_alert_disconnect" = "Odpojiť sa od servera totožností %@?"; +"identity_server_settings_place_holder" = "Zadajte server totožností"; + +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "Táto aplikácia nepodporuje mechanizmus overovania na vašom domovskom serveri."; +"security_settings_user_password_description" = "Potvrďte svoju totožnosť zadaním hesla k účtu"; +"security_settings_coming_soon" = "Prepáčte. Táto akcia zatiaľ nie je dostupná v systéme %@ iOS. Na jej nastavenie použite iného klienta Matrix. Systém %@ iOS to bude používať."; +"security_settings_complete_security_alert_title" = "Dokončiť zabezpečenie"; +"security_settings_complete_security_alert_message" = "Najprv by ste mali dokončiť zabezpečenie aktuálnej relácie."; +"security_settings_blacklist_unverified_devices_description" = "Overte všetky relácie používateľov, aby ste ich mohli označiť za dôveryhodné a odosielať im správy."; +"security_settings_secure_backup_info_valid" = "Táto relácia zálohuje vaše kľúče."; +"settings_show_NSFW_public_rooms" = "Zobraziť verejné miestnosti nevhodné do práce"; +"settings_identity_server_no_is" = "Nebol nastavený server totožností"; +"settings_discovery_three_pid_details_enter_sms_code_action" = "Zadajte aktivačný kód SMS"; +"settings_discovery_three_pid_details_information_email" = "Spravujte predvoľby pre túto e-mailovú adresu, ktorú môžu ostatní používatelia použiť na vaše objavenie a pozvanie do miestností. Pridávajte alebo odstraňujte e-mailové adresy v časti Účty."; +"settings_discovery_three_pid_details_title_email" = "Spravovať e-mail"; +"settings_discovery_accept_terms" = "Akceptovať podmienky servera totožnosti"; +"settings_discovery_no_identity_server" = "V súčasnosti nepoužívate server totožností. Ak chcete, aby vás existujúce kontakty mohli nájsť, pridajte si jeden."; +"settings_devices_description" = "Verejný názov relácie je viditeľný pre ľudí, s ktorými komunikujete"; +"settings_key_backup_delete_confirmation_prompt_msg" = "Ste si istý? Ak nie sú vaše kľúče správne zálohované, prídete o svoje zašifrované správy."; +"settings_key_backup_button_create" = "Začnite používať zálohovanie kľúčov"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Záloha má neplatný podpis z %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Záloha má neplatný podpis z %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "Záloha má podpis z %@"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Zálohovanie má platný podpis z %@"; +"settings_key_backup_info_trust_signature_valid" = "Zálohovanie má platný podpis z tejto relácie"; +"settings_key_backup_info_trust_signature_unknown" = "Záloha má podpis z relácie s ID: %@"; +"settings_key_backup_info_progress_done" = "Všetky kľúče sú zálohované"; +"settings_key_backup_info_progress" = "Zálohovanie %@ kľúčov…"; +"settings_key_backup_info_not_valid" = "Táto relácia nezálohuje vaše kľúče, ale máte existujúcu zálohu, ktorú môžete obnoviť a pridať do budúcnosti."; +"settings_key_backup_info_valid" = "Táto relácia zálohuje vaše kľúče."; +"settings_key_backup_info_version" = "Verzia zálohy kľúča: %@"; +"settings_key_backup_info_signout_warning" = "Zálohujte si šifrovacie kľúče pred odhlásením, aby ste o ne neprišli."; +"settings_key_backup_info_none" = "Vaše kľúče sa z tejto relácie nezálohujú."; +"settings_key_backup_info" = "Šifrované správy sú zabezpečené end-to-end šifrovaním. Kľúče na čítanie týchto správ máte len vy a príjemca (príjemcovia)."; +"settings_add_3pid_password_message" = "Aby ste mohli pokračovať, prosím zadajte svoje heslo"; +"settings_labs_message_reaction" = "Reagujte na správy pomocou emoji"; +"settings_labs_e2e_encryption_prompt_message" = "Ak chcete dokončiť nastavenie šifrovania, musíte sa znova prihlásiť."; +"settings_contacts_enable_sync_description" = "Na prepojenie s vašimi kontaktmi sa použije váš server totožností a pomôže im vás nájsť."; +"settings_show_url_previews_description" = "Náhľady sa zobrazia len v nezašifrovaných miestnostiach."; + +// Recover from recovery key + +"key_backup_recover_from_recovery_key_info" = "Použite svoju bezpečnostný kľúč na odomknutie histórie zabezpečených správ"; +"widget_sticker_picker_no_stickerpacks_alert" = "Momentálne nemáte aktívne žiadne balíčky s nálepkami."; +"widget_integrations_server_failed_to_connect" = "Nepodarilo sa pripojiť k integračnému serveru"; +"no_voip" = "%@ vám volá, ale %@ zatiaľ nepodporuje hovory.\nToto oznámenie môžete ignorovať a prijať hovor z iného zariadenia alebo ho môžete odmietnuť."; +"call_actions_unhold" = "Pokračovať"; +"call_jitsi_error" = "Nepodarilo sa pripojiť ku konferenčnému hovoru."; +"rage_shake_prompt" = "Zdá sa, že rozčúlene trasiete telefónom. Chceli by ste odoslať hlásenie o chybe?"; +"room_details_addresses_disable_main_address_prompt_msg" = "Nebudete mať zadanú žiadnu hlavnú adresu. Predvolená hlavná adresa pre túto miestnosť sa vyberie náhodne"; +"settings_enable_rageshake" = "Zúrivo potraste pre nahlásenie chyby"; +"settings_ui_theme_picker_message_match_system_theme" = "\"Auto\" zodpovedá systémovej téme vášho zariadenia"; +"settings_pin_rooms_with_unread" = "Pripnúť miestnosti s neprečítanými správami"; +"settings_pin_rooms_with_missed_notif" = "Pripnúť miestnosti so zmeškanými oznámeniami"; +"settings_confirm_media_size" = "Potvrdiť veľkosť pri odosielaní"; +"ok" = "OK"; +"poll_edit_form_question_or_topic" = "Otázka alebo téma"; +"poll_timeline_vote_not_registered_title" = "Hlas nebol zaznamenaný"; +"poll_timeline_vote_not_registered_subtitle" = "Je nám ľúto, váš hlas nebol zaznamenaný. Skúste to prosím znova"; +"poll_timeline_not_closed_title" = "Nepodarilo sa ukončiť anketu"; + +// MARK: - Location sharing + +"location_sharing_title" = "Poloha"; +"location_sharing_close_action" = "Zavrieť"; +"location_sharing_share_action" = "Zdieľať"; +"location_sharing_loading_map_error_title" = "%@ sa nepodarilo načítať mapu. Skúste to prosím neskôr."; +"location_sharing_locating_user_error_title" = "%@ sa nepodarilo získať prístup k vašej polohe. Skúste to prosím neskôr."; +"location_sharing_invalid_authorization_error_title" = "%@ nemá povolenie na prístup k vašej polohe. Prístup môžete povoliť v ponuke Nastavenia > Poloha"; +"location_sharing_invalid_authorization_not_now" = "Teraz nie"; +"location_sharing_invalid_authorization_settings" = "Nastavenia"; +"location_sharing_open_apple_maps" = "Otvoriť v službe Apple Mapy"; +"location_sharing_open_google_maps" = "Otvoriť v službe Mapy Google"; +"location_sharing_settings_header" = "Zdieľanie polohy"; +"location_sharing_settings_toggle_title" = "Povoliť zdieľanie polohy"; + +// Onboarding +"onboarding_splash_register_button_title" = "Vytvoriť účet"; +"onboarding_splash_login_button_title" = "Už mám účet"; +"onboarding_splash_page_1_title" = "Vlastnite svoje konverzácie."; +"onboarding_splash_page_1_message" = "Bezpečná a nezávislá komunikácia, ktorá vám poskytuje rovnakú úroveň súkromia ako rozhovor zoči-voči vo vašom vlastnom dome."; +"onboarding_splash_page_2_title" = "Vy máte všetko pod kontrolou."; +"onboarding_splash_page_2_message" = "Vyberte si, kde budú vaše rozhovory uložené, a získajte tak kontrolu a nezávislosť. Pripojené cez Matrix."; +"onboarding_splash_page_3_title" = "Zabezpečené zasielanie správ."; +"onboarding_splash_page_3_message" = "End-to-end šifrovanie a bez potreby telefónneho čísla. Žiadne reklamy ani zneužívanie údajov."; +"onboarding_splash_page_4_title_no_pun" = "Zasielanie správ pre váš tím."; +"onboarding_splash_page_4_message" = "Element je skvelý aj na pracovisku. Dôverujú mu najbezpečnejšie organizácie na svete."; +"settings_enable_room_message_bubbles" = "Správy v bublinách"; diff --git a/Riot/Assets/sq.lproj/InfoPlist.strings b/Riot/Assets/sq.lproj/InfoPlist.strings index ee2f2697a..bfeef7694 100644 --- a/Riot/Assets/sq.lproj/InfoPlist.strings +++ b/Riot/Assets/sq.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSContactsUsageDescription" = "Element-i do të shfaqë kontaktet tuaja, që kështu të mund t’i ftoni për të biseduar."; "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ë."; diff --git a/Riot/Assets/sq.lproj/Localizable.strings b/Riot/Assets/sq.lproj/Localizable.strings index 6bec05eba..ad7a81203 100644 --- a/Riot/Assets/sq.lproj/Localizable.strings +++ b/Riot/Assets/sq.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Njoftim"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ tregoi vendndodhjen e vet"; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 75f1fc60e..9c8151004 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -222,7 +222,7 @@ "settings_ignored_users" = "PËRDORUES TË SHPËRFILLUR"; "settings_contacts" = "KONTAKTE PAJISJEJE"; "settings_advanced" = "TË MËTEJSHME"; -"settings_other" = "TË TJERA"; +"settings_other" = "Tjetër"; "settings_devices" = "SESIONE"; "settings_cryptography" = "KRIPTOGRAFI"; "settings_sign_out" = "Dilni"; @@ -1471,7 +1471,7 @@ "space_private_join_rule" = "Hapësirë private"; "space_participants_action_ban" = "Dëboje prej kësaj hapësire"; "space_participants_action_remove" = "Hiqe prej kësaj hapësire"; -"spaces_coming_soon_detail" = "Kjo veçori s’është sendërtuar këtu, por po bëhet. Tani për tani, mund ta bëni këtë me Element-in në kompjuterin tuaj."; +"spaces_coming_soon_detail" = "Kjo veçori s’është sendërtuar këtu, por po bëhet. Tani për tani, mund ta bëni këtë me %@-in në kompjuterin tuaj."; "spaces_invites_coming_soon_title" = "Ftesat vijnë së shpejti"; "spaces_add_rooms_coming_soon_title" = "Shtimi i dhomave vjen së shpejti"; "spaces_coming_soon_title" = "S’afërmi"; @@ -1519,3 +1519,92 @@ "contacts_address_book_permission_denied_alert_message" = "Që të aktivizoni kontakte, kaloni te rregullimet e pajisjes tua."; "contacts_address_book_permission_denied_alert_title" = "Kontaktet u çaktivizuan"; "space_home_show_all_rooms" = "Shfaqi krejt dhomat"; +"poll_timeline_not_closed_action" = "OK"; +"poll_timeline_not_closed_subtitle" = "Ju lutemi, riprovoni"; +"poll_timeline_not_closed_title" = "S’u arrit të përfundohej pyetësori"; +"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_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"; +"poll_timeline_total_votes" = "%lu vota të hedhura"; +"poll_timeline_total_one_vote" = "1 votë e hedhur"; +"poll_timeline_total_no_votes" = "S’u votua gjë"; +"poll_timeline_votes_count" = "%lu vota"; +"poll_timeline_one_vote" = "1 votë"; +"poll_edit_form_post_failure_action" = "OK"; +"poll_edit_form_post_failure_subtitle" = "Ju lutemi, riprovoni"; +"poll_edit_form_post_failure_title" = "S’u arrit të postohej anketimi"; +"poll_edit_form_add_option" = "Shtoni mundësi"; +"poll_edit_form_option_number" = "Mundësia %lu"; +"poll_edit_form_create_options" = "Krijo mundësi"; +"poll_edit_form_input_placeholder" = "Shkruani diçka!"; +"poll_edit_form_question_or_topic" = "Pyetje ose temë"; +"poll_edit_form_poll_question_or_topic" = "Pyetje ose temë pyetësori"; + +// Mark: - Polls + +"poll_edit_form_create_poll" = "Krijoni anketim"; +"share_extension_send_now" = "Dërgoje tani"; +"share_extension_low_quality_video_message" = "Dërgojeni në %@. për cilësi më të mirë, ose dërgojeni në cilësi të ulët si më poshtë."; +"share_extension_low_quality_video_title" = "Videoja do të dërgohet në cilësi të ulët"; +"analytics_prompt_stop" = "Resht së ndari"; +"analytics_prompt_yes" = "Po, s’ka problem"; +"analytics_prompt_not_now" = "Jo tani"; +"analytics_prompt_point_3" = "Këtë mund të çaktivizoni në çfarëdo kohe, që nga rregullimet"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Nuk u japin hollësi palëve të treta"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Nuk regjistrojmë ose profilizojmë ndonjë të dhënë llogarie"; +"analytics_prompt_terms_link_upgrade" = "këtu"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Lexoni krejt kushtet tona %@. Në rregull?"; +"analytics_prompt_terms_link_new_user" = "këtu"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Mund të lexoni krejt kushtet tona %@."; +"analytics_prompt_message_upgrade" = "Keni pranuar më herët të ndani me ne të dhëna anonime përdorimi. Tani, që të na ndihmoni të kuptojmë se si njerëzit përdorin pajisje të shumta, do të prodhojmë një identifikues kuturu, të përbashkët për pajisjet tuaja."; +"analytics_prompt_message_new_user" = "Ndihmonani të identifikojmë probleme dhe të përmirësojmë %@-in, duke ndarë me ne të dhëna anonime përdorimi. Për të kuptuar se si i përdorin njerëzit disa pajisje njëherësh, do të prodhojmë një identifikues kuturu, të përbashkët për pajisjet tuaja."; + +// Analytics +"analytics_prompt_title" = "Ndihmoni të përmirësohet %@"; +"settings_discovery_accept_terms" = "Pranoni Kushte Shërbyesi Identitetesh"; +"settings_analytics_and_crash_data" = "Dërgoni të dhëna vithisjesh dhe analitike"; +"settings_labs_enabled_polls" = "Pyetësorë"; +"settings_about" = "MBI"; +"room_event_action_forward" = "Përpara"; +"room_event_action_end_poll" = "Përfundoje pyetësorin"; +"room_event_action_remove_poll" = "Hiqe pyetësorin"; +"accessibility_button_label" = "kopsë"; +"open" = "Hapur"; +"enable" = "Aktivizoje"; +"location_sharing_settings_toggle_title" = "Aktivizoni tregim vendndodhjeje"; +"location_sharing_settings_header" = "Tregim vendndodhjeje"; +"location_sharing_open_google_maps" = "Hape në Google Maps"; +"location_sharing_open_apple_maps" = "Hape në Apple Maps"; +"location_sharing_invalid_authorization_settings" = "Rregullime"; +"location_sharing_invalid_authorization_not_now" = "Jo tani"; +"location_sharing_locating_user_error_title" = "%@ s’njohu dot vendndodhjen tuaj. Ju lutemi, riprovoni më vonë."; +"location_sharing_invalid_authorization_error_title" = "%@ s’ka leje të njohë vendndodhjen tuaj. Njohjen mund ta lejoni që nga Rregullime > Vendndodhje"; +"location_sharing_loading_map_error_title" = "%@ s’ngarkoi dot hartën. Ju lutemi, riprovoni më vonë."; +"location_sharing_share_action" = "Ndajeni me të tjerë"; +"location_sharing_close_action" = "Mbylle"; + +// MARK: - Location sharing + +"location_sharing_title" = "Vendndodhje"; +"ok" = "OK"; +"settings_enable_room_message_bubbles" = "Flluska mesazhesh"; +"onboarding_splash_page_4_message" = "Element-i është gjithashtu i goditur për në punë. Është i besuar nga entet më të sigurta të botës."; +"onboarding_splash_page_4_title_no_pun" = "Shkëmbim mesazhesh për ekipin tuaj."; +"onboarding_splash_page_3_message" = "I fshehtëzuar skaj më skaj dhe pa u dashur numër telefoni. Pa reklama, apo shfrytëzim të dhënash."; +"onboarding_splash_page_3_title" = "Shkëmbim i sigurt mesazhesh."; +"onboarding_splash_page_2_message" = "Zgjidhni ku mbahen bisedat tuaja, duke ju dhënë kontroll dhe pavarësi. Të lidhur përmes Matrix-i."; +"onboarding_splash_page_2_title" = "Kontrollin e keni ju."; +"onboarding_splash_page_1_message" = "Komunikim i sigurt dhe i pavarur, që ju jep të njëjtën shkallë privatësie si biseda kokë më kokë, në shtëpinë tuaj."; +"onboarding_splash_page_1_title" = "Jini zot i bisedave tuaja."; +"onboarding_splash_login_button_title" = "Kam tashmë një llogari"; + +// Onboarding +"onboarding_splash_register_button_title" = "Krijoni llogari"; diff --git a/Riot/Assets/sv.lproj/InfoPlist.strings b/Riot/Assets/sv.lproj/InfoPlist.strings index 45ccee26b..4d03e8e0c 100644 --- a/Riot/Assets/sv.lproj/InfoPlist.strings +++ b/Riot/Assets/sv.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSMicrophoneUsageDescription" = "Element behöver åtkomst till din mikrofon för att kunna ringa och ta emot samtal samt spela in video och röstmeddelanden."; "NSContactsUsageDescription" = "Element kommer att visa dina kontakter så du kan bjuda in dem att chatta."; "NSFaceIDUsageDescription" = "Face ID används för att komma åt appen."; +"NSLocationWhenInUseUsageDescription" = "När du delar din plats med folk så behöver Element åtkomst för att visa dem en karta."; diff --git a/Riot/Assets/sv.lproj/Localizable.strings b/Riot/Assets/sv.lproj/Localizable.strings index 4883fb894..9c4df7f81 100644 --- a/Riot/Assets/sv.lproj/Localizable.strings +++ b/Riot/Assets/sv.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Avisering"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ delade sin plats"; diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index b615810b2..8d973a9a6 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1897,6 +1897,59 @@ Library. SOFTWARE.

+
  • + PostHog iOS (https://github.com/PostHog/posthog-ios) +

    + The MIT License (MIT) +

    + Copyright (c) 2020 PostHog (part of Hiberly Inc) +

    + Copyright (c) 2016 Segment.io, Inc. +

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

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

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

    +
  • +
  • + UICollectionViewRightAlignedLayout (https://github.com/mokagio/UICollectionViewRightAlignedLayout) +

    + The MIT License (MIT) +

    + Copyright (c) 2014 Giovanni Lodi +

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

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

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

    +
  • diff --git a/Riot/Assets/uk.lproj/InfoPlist.strings b/Riot/Assets/uk.lproj/InfoPlist.strings index 1923cbcbc..35a02253f 100644 --- a/Riot/Assets/uk.lproj/InfoPlist.strings +++ b/Riot/Assets/uk.lproj/InfoPlist.strings @@ -5,3 +5,4 @@ "NSContactsUsageDescription" = "Element покаже ваші контакти, щоб ви могли запросити їх до бесіди."; "NSCalendarsUsageDescription" = "Переглядайте свої заплановані зустрічі в додатку."; "NSFaceIDUsageDescription" = "Face ID використовується для доступу до вашого додатку."; +"NSLocationWhenInUseUsageDescription" = "Коли ви надсилаєте комусь дані про своє місцеперебування, Element потребує дозволу, щоб показати їм карту."; diff --git a/Riot/Assets/uk.lproj/Localizable.strings b/Riot/Assets/uk.lproj/Localizable.strings index 01f01d7ac..0ad0b3aa3 100644 --- a/Riot/Assets/uk.lproj/Localizable.strings +++ b/Riot/Assets/uk.lproj/Localizable.strings @@ -116,3 +116,6 @@ /** General **/ "NOTIFICATION" = "Сповіщення"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ надсилає дані про своє місцеперебування"; diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index abf77edfd..e85515b74 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -163,7 +163,7 @@ "room_participants_leave_prompt_msg_for_dm" = "Ви впевнені, що хочете вийти?"; "room_participants_leave_prompt_title_for_dm" = "Вийти"; "client_android_name" = "Element Android"; -"store_promotional_text" = "Застосунок для бесід та співпраці, що зберігає приватність у відкритій мережі. Децентралізований, щоб надати вам контроль над даними. Без обробки даних, без бекдорів, без доступу для третіх сторін."; +"store_promotional_text" = "Застосунок для спілкування та співпраці, що зберігає приватність у відкритій мережі. Децентралізований, щоб надати вам контроль над даними. Без обробки даних, без бекдорів, без доступу для третіх сторін."; "settings_three_pids_management_information_part3" = "."; "settings_three_pids_management_information_part1" = "Керуйте звідси адресами е-пошти чи номерами телефонів, які можна застосовувати для входу або відновлення облікового запису. Контролюйте хто і як може вас знайти "; "contacts_address_book_no_identity_server" = "Сервер ідентифікації не налаштований"; @@ -388,7 +388,7 @@ "settings_identity_server_settings" = "СЕРВЕР ІДЕНТИФІКАЦІЇ"; "settings_calls_settings" = "ВИКЛИКИ"; "settings_notifications" = "СПОВІЩЕННЯ"; -"settings_user_settings" = "НАЛАШТУВАННЯ КОРИСТУВАЧА"; +"settings_user_settings" = "КОРИСТУВАЦЬКІ НАЛАШТУВАННЯ"; "event_formatter_call_connecting" = "З'єднання…"; "settings_config_identity_server" = "Сервер ідентифікації %@"; "settings_config_home_server" = "Домашній сервер %@"; @@ -408,7 +408,7 @@ // Bug report "bug_report_title" = "Звіт про ваду"; -"e2e_key_backup_wrong_version_button_wasme" = "Це був я"; +"e2e_key_backup_wrong_version_button_wasme" = "Це я"; "e2e_key_backup_wrong_version_button_settings" = "Налаштування"; "settings_privacy_policy" = "Політика приватності"; "settings_term_conditions" = "Умови та положення"; @@ -433,7 +433,7 @@ "room_widget_permission_theme_permission" = "Ваша тема"; "room_widget_permission_user_id_permission" = "Ваш ID користувача"; "room_widget_permission_avatar_url_permission" = "URL-адреса вашого аватара"; -"room_widget_permission_display_name_permission" = "Ваше показуване ім'я"; +"room_widget_permission_display_name_permission" = "Ваше показуване імʼя"; "room_widget_permission_creator_info_title" = "Цей віджет додано:"; // Room widget permissions @@ -591,7 +591,7 @@ "user_avatar_view_accessibility_label" = "аватар"; "room_intro_cell_information_dm_sentence1_part3" = ". "; -"room_intro_cell_information_room_without_topic_sentence2_part1" = "Додати тему"; +"room_intro_cell_information_room_without_topic_sentence2_part1" = "Додайте тему,"; "room_intro_cell_information_room_with_topic_sentence2" = "Тема: %@"; "room_intro_cell_information_room_sentence1_part3" = ". "; "room_intro_cell_information_room_sentence1_part1" = "Це початок "; @@ -1002,7 +1002,7 @@ // Room key request dialog "e2e_room_key_request_title" = "Запит ключів шифрування"; "share_extension_send_now" = "Надіслати зараз"; -"service_terms_modal_accept_button" = "Прийняти"; +"service_terms_modal_accept_button" = "Погодитися"; "room_details_flair_invalid_id_prompt_msg" = "%@ — неправильний ідентифікатор спільноти"; "room_details_flair_invalid_id_prompt_title" = "Неправильний формат"; "room_details_new_flair_placeholder" = "Додати новий ID спільноти (напр., +foo%@)"; @@ -1022,3 +1022,712 @@ "room_details_history_section_members_only" = "Лише учасники (від часу вибору цієї опції)"; "room_details_history_section_anyone" = "Будь-хто"; "room_details_history_section" = "Хто може переглядати історію?"; +"identity_server_settings_alert_disconnect" = "Від'єднатися від сервера ідентифікації %@?"; +"identity_server_settings_alert_disconnect_title" = "Від'єднатися від сервера ідентифікації"; +"identity_server_settings_alert_change" = "Від'єднатися від сервера ідентифікації %1$@ й натомість під'єднатися до %2$@?"; +"identity_server_settings_alert_change_title" = "Змінити сервер ідентифікації"; +"identity_server_settings_alert_no_terms" = "Обраний вами сервер ідентифікації не має жодних умов використання. Продовжуйте лише якщо довіряєте власнику сервісу."; +"identity_server_settings_alert_no_terms_title" = "Сервер ідентифікації не має умов використання"; +"identity_server_settings_disconnect_info" = "Після від'єднання від сервера ідентифікації вас більше не зможуть знаходити інші користувачі, а ви не зможете запрошувати інших за е-поштою чи телефоном."; +"identity_server_settings_no_is_description" = "Зараз ви не використовуєте сервер ідентифікації. Щоб знаходити наявні контакти й вони знаходили вас, додайте сервер угорі."; +"identity_server_settings_description" = "Ви користуєтесь %@, щоб знаходити людей за наявними контактами й вони знаходили вас."; + +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "Цей застосунок не підтримує способу входу, доступного на вашому домашньому сервері."; +"security_settings_user_password_description" = "Підтвердьте свою особу, ввівши пароль свого облікового запису"; +"security_settings_coming_soon" = "Вибачте, ця дія ще не доступна в %@ iOS. Якщо налаштуєте це іншим клієнтом Matrix, %@ iOS зможе також це використати."; +"security_settings_complete_security_alert_message" = "Спершу слід доповнити захист вашого поточного сеансу."; +"security_settings_blacklist_unverified_devices_description" = "Звірте всі сеанси користувача, щоб позначити його довіреним і надіслати йому повідомлення."; +"security_settings_crosssigning_bootstrap" = "Налаштувати"; +"security_settings_crosssigning_info_ok" = "Перехресне підписування готове до використання."; +"security_settings_crosssigning_info_trusted" = "Перехресне підписування увімкнено. Ви можете робити інших користувачів і свої інші сеанси довіреними на підставі перехресного підпису, але ви не можете перехресно підписувати цим сеансом, бо в нього ще нема закритих ключів перехресного підписування. Доповніть захист цього сеансу."; +"security_settings_crosssigning_info_exists" = "Ваш обліковий запис має ідентичність перехресного підписування, але вона ще не довірена цим сеансом. Доповніть захист цього сеансу."; +"security_settings_secure_backup_description" = "Зробіть резервну копію своїх ключів шифрування й даних облікового запису на випадок втрати доступу до своїх сеансів. Ваші ключі будуть захищені унікальним ключем безпеки."; +"security_settings_crypto_sessions_description_2" = "Якщо не впізнаєте вхід, скиньте пароль і налаштування безпечного резервного копіювання."; +"settings_identity_server_no_is_description" = "Зараз ви не використовуєте сервер ідентифікації. Щоб знаходити наявні контакти й вони знаходили вас, додайте сервер угорі."; +"settings_identity_server_description" = "Налаштований угорі сервер ідентифікації дає вам змогу знаходити людей за наявними контактами, а їм знаходити вас."; +"settings_discovery_three_pid_details_information_phone_number" = "Керуйте параметрами для цього номера телефону, за яким інші користувачі можуть вас знаходити й запрошувати до кімнат. Додавайте й видаляйте номери в Облікових записах."; +"settings_discovery_three_pid_details_information_email" = "Керуйте параметрами для цієї адреси е-пошти, за якою інші користувачі можуть вас знаходити й запрошувати до кімнат. Додавайте й видаляйте адреси в Облікових записах."; +"settings_discovery_three_pids_management_information_part1" = "Керуйте, за якими адресами е-пошти й номерами телефону інші користувачі зможуть вас знаходити й запрошувати до кімнат. Щоб додати адреси й номери в цей список чи вилучити наявні, перейдіть у "; +"settings_discovery_no_identity_server" = "Зараз ви не використовуєте сервер ідентифікації. Щоб наявні контакти могли вас знаходити, додайте такий сервер."; +"settings_key_backup_info_not_valid" = "Цей сеанс не створює резервної копії ваших ключів, але у вас уже є резервна копія, яку ви можете відновити й додати у майбутньому."; +"settings_key_backup_info_version" = "Версія резервного копіювання ключів: %@"; +"settings_labs_message_reaction" = "Реагувати на повідомлення за допомогою емодзі"; +"settings_contacts_enable_sync_description" = "Це використовуватиме ваш сервер ідентифікації, щоб ви знаходили свої контакти, а вони вас."; +"settings_integrations_allow_description" = "Використовуйте менеджер інтеграцій (%@), щоб керувати ботами, мостами, віджетами й пакунками наліпок.\n\nМенеджери інтеграцій отримують ваші параметри й можуть змінювати віджети, надсилати запрошення до кімнат і надавати повноваження від вашого імені."; +"settings_calls_stun_server_fallback_description" = "Дозволити допоміжний сервер викликів %@, коли ваш домашній сервер не надає свого (ваша IP-адреса ставатиме відомою при виклику)."; +"settings_callkit_info" = "Отримувати вхідні виклики, не розблоковуючи екран. Перегляньте свої виклики (%@) в історії викликів системи. Якщо iCloud увімкнено, ця історія викликів надсилатиметься Apple."; +"settings_confirm_media_size_description" = "Коли це ввімкнено, при надсиланні зображень чи відео вам пропонуватиметься підтвердити їхній розмір."; +"settings_three_pids_management_information_part2" = "Знаходження"; +"settings_config_user_id" = "Ви ввійшли як %@"; +"unknown_devices_alert" = "Кімната містить сеанси, які досі не пройшли звірку.\nТобто нема гарантії, що ці сеанси належать користувачам, від імені яких вони створені.\nРадимо звірити кожен сеанс, перш ніж продовжити; але за потреби можете повторити надсилання повідомлення без звірки."; +"room_action_camera" = "Зробити світлину або відео"; +"room_ongoing_conference_call_with_close" = "Відбувається конференц-виклик. Приєднатись як %1$s чи %2$s. %@ його."; +"room_member_power_level_short_custom" = "Інше"; +"room_member_power_level_custom_in" = "Інше (%@) у %@"; +"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Поки жоден сервер ідентифікації не налаштований, ви не можете почати бесіду з кимось за адресою е-пошти."; +"find_your_contacts_message" = "Дозвольте %@ показувати ваші контакти, щоб ви могли швидко почати бесіду з тими, кого знаєте найкраще."; +"find_your_contacts_title" = "Почніть із переліку своїх контактів"; +"store_full_description" = "Element — застосунок для листування й співпраці нового покоління:\n\n1. Надає вам контроль над збереженням вашої приватності\n2. Дає змогу спілкуватися з будь-ким у мережі Matrix і навіть за її межами, інтегруючись із такими застосунками, як Slack\n3. Оберігає вас від реклами, збору даних, бекдорів і прив'язаності до провайдера\n4. Захищає вас наскрізним шифруванням і звіркою інших перехресним підписуванням\n\nElement суттєво відрізняється від інших застосунків для листування й співпраці тим, що децентралізований і має відкритий код.\n\nElement дає змогу самостійно встановити сервер або обрати з-поміж загальнодоступних, щоб ви зберігали приватність своїх даних і розмов, власність і контроль над ними. Він надає вам доступ до відкритої мережі; тож ви можете спілкуватися з користувачами інших застосунків, не лише Element. А ще він добре захищений.\n\nElement здатен на це все завдяки своїй основі Matrix — стандарту відкритого, децентралізованого спілкування.\n\nElement надає вам контроль, даючи змогу обрати, в кого зберігаються ваші розмови. У застосунку Element ви можете обрати між такими шляхами:\n\n1. Зареєструвати безплатний обліковий запис на загальнодоступному сервері matrix.org\n2. Самостійно розмістити свій обліковий запис, встановивши сервер на власному обладнанні\n3. Отримати обліковий запис на виділеному сервері, просто передплативши хостинг-платформу Element Matrix Services\n\nЧому Element?\n\nВОЛОДІЙТЕ СВОЇМИ ДАНИМИ: Ви обираєте, де зберігати свої дані й повідомлення. Ви володієте й керуєте ними, не якась МЕГАКОРПОРАЦІЯ, що аналізує ваші дані й передає їх стороннім особам.\n\nВІДКРИТЕ ЛИСТУВАННЯ Й СПІВПРАЦЯ: Можете розмовляти з будь-ким іншим у мережі Matrix незалежно від того, використовують вони Element, інший застосунок Matrix чи навіть сторонню систему листування на зразок Slack, IRC чи XMPP.\n\nНАДБЕЗПЕКА: Справжнє наскрізне шифрування (лише учасники розмови можуть розшифрувати повідомлення) й звірка пристроїв учасників розмови перехресним підписуванням.\n\nДОСКОНАЛЕ СПІЛКУВАННЯ: Листуйтеся, робіть голосові й відеовиклики, діліться файлами, транслюйте екран, підʼєднуйте різноманітні інтеграції, ботів і віджети. Розбудовуйте кімнати, спільноти, будьте на звʼязку й досягайте цілей.\n\nСКРІЗЬ, ДЕ ВИ: Будьте на зв'язку, де б ви не були, завдяки повній синхронізації історії повідомлень між усіма вашим пристроями та онлайн-клієнтом https://app.element.io."; +"security_settings_crosssigning_info_not_bootstrapped" = "Перехресне підписування ще не налаштовано."; +"security_settings_crosssigning" = "ПЕРЕХРЕСНЕ ПІДПИСУВАННЯ"; +"security_settings_backup" = "РЕЗЕРВНЕ КОПІЮВАННЯ ПОВІДОМЛЕНЬ"; +"security_settings_secure_backup_restore" = "Відновити з резервної копії"; +"key_verification_manually_verify_device_key_title" = "Ключ сеансу"; +"key_verification_manually_verify_device_id_title" = "ID сеансу"; +"identity_server_settings_alert_error_invalid_identity_server" = "%@ не дійсний сервер ідентифікації."; +"identity_server_settings_alert_error_terms_not_accepted" = "Ви повинні погодитися з умовами %@, щоб налаштувати сервер ідентифікації."; +"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Усе одно відʼєднати"; +"identity_server_settings_alert_disconnect_button" = "Відʼєднати"; +"identity_server_settings_disconnect" = "Відʼєднати"; +"identity_server_settings_change" = "Змінити"; +"identity_server_settings_add" = "Додати"; +"identity_server_settings_place_holder" = "Введіть сервер ідентифікації"; + +// Identity server settings +"identity_server_settings_title" = "Сервер ідентифікації"; +"manage_session_sign_out" = "Вийти з цього сеансу"; +"manage_session_not_trusted" = "Не довірені"; +"manage_session_trusted" = "Довірені вами"; +"key_verification_manually_verify_device_name_title" = "Назва сеансу"; +"manage_session_name" = "Назва сеансу"; +"manage_session_info" = "ВІДОМОСТІ ПРО СЕАНС"; + +// Manage session +"manage_session_title" = "Керувати сеансом"; +"security_settings_complete_security_alert_title" = "Завершити налаштування безпеки"; +"user_verification_session_details_verify_action_current_user" = "Інтерактивна перевірка"; +"secrets_recovery_reset_action_part_2" = "Скинути все"; + +// MARK: - Secrets reset + +"secrets_reset_title" = "Скинути все"; +"cross_signing_setup_banner_subtitle" = "Спростіть перевірку інших своїх пристроїв"; + +// MARK: - Cross-signing + +// Banner + +"cross_signing_setup_banner_title" = "Налаштування шифрування"; +"secrets_reset_authentication_message" = "Введіть пароль свого облікового запису, щоб продовжити"; +"security_settings_blacklist_unverified_devices" = "Ніколи не надсилати повідомлення не довіреним сеансам"; +"security_settings_advanced" = "ДОДАТКОВО"; +"security_settings_export_keys_manually" = "Експорт ключів вручну"; +"security_settings_cryptography" = "КРИПТОГРАФІЯ"; +"security_settings_crosssigning_complete_security" = "Завершити налаштування безпеки"; +"security_settings_crosssigning_reset" = "Скинути"; +"secrets_reset_reset_action" = "Скинути"; +"security_settings_secure_backup_reset" = "Скинути"; +"settings_show_NSFW_public_rooms" = "Показувати загальнодоступні кімнати з делікатним вмістом"; +"settings_identity_server_no_is" = "Сервер ідентифікації не налаштований"; +"settings_discovery_three_pid_details_enter_sms_code_action" = "Введіть код активації з СМС"; +"settings_discovery_three_pid_details_cancel_email_validation_action" = "Скасувати перевірку е-пошти"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Резервна копія має недійсний підпис %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Резервна копія має недійсний підпис %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "Резервна копія має підпис %@"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Резервна копія має дійсний підпис %@"; +"settings_send_crash_report" = "Надсилати анонімні дані про збої та користування"; +"settings_show_url_previews_description" = "Попередній перегляд виконується лише у кімнатах без шифрування."; +"settings_show_url_previews" = "Попередній перегляд вебсайтів"; +"settings_ui_theme_picker_message_match_system_theme" = "«Авто» застосовує тему вашого пристрою"; +"settings_ui_theme_picker_message_invert_colours" = "«Авто» застосовує налаштування вашого пристрою «Інвертувати кольори»"; +"settings_messages_containing_display_name" = "Моє показуване імʼя"; +"settings_discovery_three_pid_details_title_phone_number" = "Керувати номером телефону"; +"settings_discovery_three_pid_details_title_email" = "Керувати е-поштою"; +"settings_discovery_error_message" = "Сталася помилка. Повторіть спробу."; +"settings_discovery_three_pids_management_information_part2" = "Користувацькі налаштування"; +"settings_discovery_accept_terms" = "Погодитися з умовами використання сервера ідентифікації"; +"settings_discovery_terms_not_signed" = "Погодьтеся з умовами використання сервера ідентифікації (%@), щоб дозволити вашу виявність за електронною адресою та номером телефону."; +"settings_key_backup_delete_confirmation_prompt_msg" = "Ви впевнені? Ви втратите ваші зашифровані повідомлення якщо копія ключів не була створена коректно."; +"settings_key_backup_delete_confirmation_prompt_title" = "Видалити резервну копію"; +"settings_key_backup_button_connect" = "Налаштувати цьому сеансу резервне копіювання ключів"; +"settings_key_backup_button_delete" = "Видалити резервну копію"; +"settings_key_backup_button_restore" = "Відновити з резервної копії"; +"settings_key_backup_button_create" = "Почати використовувати резервне копіювання ключів"; +"settings_key_backup_info_trust_signature_valid" = "Резервна копія має чинний підпис цього сеансу"; +"settings_key_backup_info_trust_signature_unknown" = "Резервна копія містить підпис невідомого сеансу з ID: %@"; +"settings_key_backup_info_progress_done" = "Резервні копії всіх ключів створено"; +"settings_key_backup_info_progress" = "Резервне копіювання %@ ключів…"; +"security_settings_secure_backup_info_valid" = "Цей сеанс створює резервну копію ваших ключів."; +"settings_key_backup_info_valid" = "Цей сеанс створює резервну копію ваших ключів."; +"settings_key_backup_info_signout_warning" = "Створіть резервну копію ключів перед виходом, щоб не втратити їх."; +"settings_key_backup_info_none" = "Для цього сеансу не створюється резервна копія ваших ключів."; +"security_settings_secure_backup_info_checking" = "Перевірка…"; +"settings_key_backup_info_checking" = "Перевірка…"; +"settings_key_backup_info" = "Зашифровані повідомлення захищені наскрізним шифруванням. Лише ви та отримувачі повідомлень мають ключі для їх читання."; +"settings_deactivate_my_account" = "Деактивувати обліковий запис"; +"settings_enable_rageshake" = "Струснути пристрій, щоб повідомити про ваду"; +"settings_third_party_notices" = "Примітки третіх сторін"; +"settings_labs_enable_ringing_for_group_calls" = "Дзвінок групових викликів"; +"settings_labs_enabled_polls" = "Опитування"; +"settings_labs_create_conference_with_jitsi" = "Створити конференц-виклик за допомогою jitsi"; +"settings_labs_e2e_encryption_prompt_message" = "Щоб завершити налаштування шифрування вам потрібно повторно увійти."; +"settings_labs_e2e_encryption" = "Наскрізне шифрування"; +"settings_unignore_user" = "Показати всі повідомлення від %@?"; +"settings_ui_theme_picker_title" = "Вибрати тему"; +"settings_ui_theme_black" = "Чорна"; +"settings_ui_theme_dark" = "Темна"; +"settings_ui_theme_light" = "Світла"; +"settings_ui_theme_auto" = "Авто"; +"settings_ui_theme" = "Тема"; +"settings_ui_language" = "Мова"; +"settings_integrations_allow_button" = "Керування інтеграціями"; +"settings_calls_stun_server_fallback_button" = "Дозволити допоміжний сервер викликів"; +"settings_enable_callkit" = "Інтегрований виклик"; +"settings_new_keyword" = "Додати нове ключове слово"; +"settings_your_keywords" = "Ваші ключові слова"; +"settings_room_upgrades" = "Поліпшення кімнати"; +"settings_messages_by_a_bot" = "Повідомлення бота"; +"settings_call_invitations" = "Запрошення до виклику"; +"settings_room_invitations" = "Запрошення до кімнати"; +"settings_messages_containing_keywords" = "Ключові слова"; +"settings_messages_containing_user_name" = "Моє користувацьке імʼя"; +"settings_messages_containing_at_room" = "@room"; +"settings_encrypted_group_messages" = "Зашифровані групові повідомлення"; +"settings_group_messages" = "Групові повідомлення"; +"settings_other" = "Інше"; +"settings_mentions_and_keywords" = "Згадки та ключові слова"; +"settings_pin_rooms_with_unread" = "Закріплювати кімнати з новими повідомленнями"; +"settings_confirm_media_size" = "Підтверджувати розмір під час надсилання"; +"settings_discovery_settings" = "ВИЯВНІСТЬ"; +"room_preview_unlinked_email_warning" = "Запрошення надіслано на адресу %@, не повʼязану з цим обліковим записом. Ви можете увійти за допомогою іншого облікового запису або додати е-пошту до цього."; +"unknown_devices_verify" = "Перевірка…"; +"room_message_edits_history_title" = "Редагування повідомлення"; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Цей домашній сервер досяг свого місячного обмеження активних користувачів, тож "; +"room_resource_usage_limit_reached_message_1_default" = "Цей домашній сервер досягнув одного зі своїх лімітів ресурсів, тож "; +"room_conference_call_no_power" = "Для керування конференц-викликами у цій кімнаті потрібен дозвіл"; +"room_ongoing_conference_call" = "Відбувається конференц-виклик. Приєднатись як %1$s чи %2$s."; +"room_participants_security_information_room_encrypted_for_dm" = "Повідомлення тут захищені наскрізним шифруванням.\n\nВаші повідомлення захищені замками, тож лише ви та отримувач маєте унікальні ключі для їхнього відмикання."; +"room_participants_security_information_room_encrypted" = "Повідомлення тут захищені наскрізним шифруванням.\n\nВаші повідомлення захищені замками, тож лише ви та отримувачі мають унікальні ключі для їхнього відмикання."; +"room_participants_action_security_status_complete_security" = "Завершити налаштування безпеки"; +"room_participants_idle" = "Неактивний"; +"settings_labs" = "ЛАБОРАТОРІЯ"; +"settings_about" = "ПРО"; +"settings_advanced" = "ДОДАТКОВО"; +"settings_phone_contacts" = "КОНТАКТИ ТЕЛЕФОНА"; +"settings_links" = "ПОСИЛАННЯ"; +"settings_sending_media" = "НАДСИЛАННЯ ЗОБРАЖЕНЬ І ВІДЕО"; +"room_preview_try_join_an_unknown_room" = "Ви намагаєтесь отримати доступ до %@. Бажаєте приєднатися, щоб взяти участь в обговоренні?"; +"room_preview_subtitle" = "Це попередній перегляд кімнати. Ви в режимі лише читання."; + +// Room Preview +"room_preview_invitation_format" = "%s запрошує вас приєднатися до цієї кімнати"; + +// Unknown devices +"unknown_devices_alert_title" = "Кімната містить невідомі сеанси"; +"external_link_confirmation_message" = "Посилання %@ спрямовує вас на інший сайт: %@.\n\nВи впевнені, що бажаєте продовжити?"; +"external_link_confirmation_title" = "Перевірте це посилання"; +"room_accessibility_hangup" = "Покласти слухавку"; +"room_resource_usage_limit_reached_message_contact_3" = " , щоб збільшити ліміт."; +"room_resource_usage_limit_reached_message_2" = "деякі користувачі не зможуть увійти."; +"room_resource_limit_exceeded_message_contact_3" = " , щоб продовжити користуватися цією службою."; +"room_resource_limit_exceeded_message_contact_2_link" = "зв’яжіться з адміністратором вашого сервера"; +"room_predecessor_link" = "Торкніться тут, щоб переглянути давніші повідомлення."; +"room_predecessor_information" = "Ця кімната — продовження спілкування в іншій кімнаті."; +"room_replacement_link" = "Спілкування продовжується тут."; +"room_replacement_information" = "Цю кімнату замінено й вона більше не активна."; +"room_event_action_end_poll" = "Завершити опитування"; +"room_event_action_remove_poll" = "Вилучити опитування"; +"user_verification_session_details_information_trusted_current_user" = "Цей сеанс довірений для захищеного листування, бо ви звірили його:"; +"user_verification_session_details_information_trusted_other_user_part1" = "Цей сеанс довірений для захищеного листування, бо "; +"user_verification_session_details_information_trusted_other_user_part2" = " звіряє його:"; +"user_verification_session_details_information_untrusted_other_user" = " входить у новому сеансі:"; +"user_verification_session_details_additional_information_untrusted_other_user" = "Надіслані цьому сеансу й цим сеансом повідомлення позначатимуться застереженнями, поки цей користувач йому не довірить. Або ви можете власноруч звірити сеанс."; +"user_verification_session_details_additional_information_untrusted_current_user" = "Якщо ви не входили в цей сеанс, ваш обліковий запис може бути під загрозою."; +"user_verification_session_details_verify_action_other_user" = "Звірити власноруч"; +"key_verification_bootstrap_not_setup_message" = "Спершу налаштуйте перехресне підписування."; +"key_verification_verify_qr_code_information" = "Відскануйте код, щоб безпечно звірити одне одного."; +"key_verification_verify_qr_code_scan_code_action" = "Сканувати їхній код"; +"key_verification_verify_qr_code_cannot_scan_action" = "Не вдалося сканувати?"; +"key_verification_verify_qr_code_other_scan_my_code_title" = "Чи зміг інший користувач відсканувати QR-код?"; +"key_verification_verify_qr_code_scan_other_code_success_title" = "Код підтверджено!"; +"key_verification_verify_qr_code_scan_other_code_success_message" = "QR-код успішно підтверджено."; + +// MARK: Self verification start + +// New login +"device_verification_self_verify_alert_title" = "Новий вхід. Це були ви?"; +"device_verification_self_verify_alert_message" = "Звірте новий вхід до вашого облікового запису: %@"; +"device_verification_self_verify_start_information" = "Звірте цим сеансом свій новий. Це надасть йому доступ до зашифрованих повідомлень."; +"device_verification_self_verify_start_waiting" = "Очікування…"; + +// MARK: Self verification wait + +"device_verification_self_verify_wait_title" = "Доповніть безпеку"; +"device_verification_self_verify_wait_information" = "Звірте цей сеанс одним зі своїх інших, щоб надати йому доступ до зашифрованих повідомлень.\n\nВикористайте найостанніший %@ на своїх інших пристроях:"; + +// Recover from private key +"key_backup_recover_from_private_key_info" = "Відновлення резервної копії…"; + +// MARK: Scan confirmation + +// Scanning +"key_verification_scan_confirmation_scanning_title" = "Майже все! Чекаємо на підтвердження…"; +"key_verification_scan_confirmation_scanning_user_waiting_other" = "Очікування на %@…"; +"key_verification_scan_confirmation_scanning_device_waiting_other" = "Очікування іншого пристрою…"; + +// Scanned +"key_verification_scan_confirmation_scanned_title" = "Майже все!"; +"key_verification_scan_confirmation_scanned_user_information" = "Чи показує %@ такий же щит?"; +"key_verification_scan_confirmation_scanned_device_information" = "Чи показує інший пристрій такий же щит?"; +"device_verification_self_verify_wait_new_sign_in_title" = "Звірте цей вхід"; +"device_verification_self_verify_wait_additional_information" = "Це працює з %@ та іншими клієнтами Matrix, здатними на перехресне підписування."; + +// MARK: - Device Verification +"key_verification_other_session_title" = "Звірте сеанс"; +"key_verification_new_session_title" = "Звірте свій новий сеанс"; +"key_verification_this_session_title" = "Звірити цей сеанс"; +"device_verification_security_advice_emoji" = "Порівняйте унікальні емоджі, переконавшись, що їх показано в однаковому порядку."; +"device_verification_security_advice_number" = "Порівняйте числа, переконавшись, що їх показано в однаковому порядку."; + +// MARK: Verify + +"key_verification_verify_sas_title_emoji" = "Порівняйте емодзі"; +"key_verification_verify_sas_title_number" = "Порівняйте числа"; +"key_verification_verify_sas_cancel_action" = "Вони не збігаються"; +"key_verification_verify_sas_validate_action" = "Вони збігаються"; +"key_verification_verify_sas_additional_information" = "Для більшої безпеки зв'яжіться іншим довіреним засобом чи особисто."; +"key_verification_verify_qr_code_emoji_information" = "Звірити порівнянням унікальних емодзі."; +"key_verification_verify_qr_code_start_emoji_action" = "Звірити за допомогою емодзі"; +"key_verification_verified_other_session_information" = "Тепер ви можете читати захищені повідомлення в своєму іншому сеансі, а інші користувачі знатимуть, що можуть йому довіряти."; +"key_verification_verified_new_session_information" = "Тепер ви можете читати захищені повідомлення на своєму новому пристрої, а інші користувачі знатимуть, що можуть йому довіряти."; +"key_verification_verified_this_session_information" = "Тепер ви можете читати захищені повідомлення на цьому пристрої, а інші користувачі знатимуть, що можуть йому довіряти."; + +// User + +"key_verification_verified_user_information" = "Листування з цим користувачем наскрізно зашифроване й непрочитне для сторонніх."; + +// Current session + +"key_verification_self_verify_current_session_alert_title" = "Звірити цей сеанс"; +"key_verification_self_verify_current_session_alert_message" = "Інші користувачі можуть йому не довіряти."; +"key_verification_verify_qr_code_information_other_device" = "Відскануйте код знизу для звірки:"; + +// MARK: Manually Verify Device + +"key_verification_manually_verify_device_title" = "Звірити власноруч за допомогою тексту"; +"key_verification_manually_verify_device_instruction" = "Підтвердьте шляхом порівняння наступного рядка з рядком у користувацьких налаштуваннях вашого іншого сеансу:"; +"key_verification_manually_verify_device_additional_information" = "Якщо вони відрізняються, безпека вашого зв'язку може бути під загрозою."; +"user_verification_session_details_verify_action_current_user_manually" = "Звірити власноруч за допомогою тексту"; +"device_verification_self_verify_wait_recover_secrets_without_passphrase" = "Використати ключ безпеки"; +"device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Використати фразу чи ключ безпеки"; +"device_verification_self_verify_wait_recover_secrets_additional_information" = "Якщо не маєте доступу до наявного сеансу"; + +// Recover with passphrase + +"secrets_recovery_with_passphrase_title" = "Фраза безпеки"; +"secrets_recovery_with_passphrase_information_default" = "Отримайте доступ до своєї захищеної історії повідомлень та ідентичності перехресного підписування для звірки інших сеансів, увівши свою фразу безпеки."; +"secrets_recovery_with_passphrase_information_verify_device" = "Підтвердьте цей пристрій своєю фразою безпеки."; +"secrets_recovery_with_passphrase_passphrase_placeholder" = "Введіть фразу безпеки"; +"secrets_recovery_with_passphrase_recover_action" = "Використати фразу"; +"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "Забули свою фразу безпеки? Можете "; +"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "використати ключ безпеки"; +"secrets_recovery_with_passphrase_invalid_passphrase_title" = "Не вдалося зайти до таємного сховища"; +"secrets_recovery_with_passphrase_invalid_passphrase_message" = "Переконайтеся, що вводите правильну фразу безпеки."; + +// Recover with key + +"secrets_recovery_with_key_title" = "Ключ безпеки"; +"secrets_recovery_with_key_information_default" = "Отримайте доступ до своєї захищеної історії повідомлень та ідентичності перехресного підписування для звірки інших сеансів, увівши свій ключ безпеки."; +"secrets_recovery_with_key_information_verify_device" = "Підтвердьте цей пристрій своїм ключем безпеки."; +"secrets_recovery_with_key_recovery_key_placeholder" = "Введіть ключ безпеки"; +"secrets_recovery_with_key_recover_action" = "Використати ключ"; +"secrets_recovery_with_key_invalid_recovery_key_title" = "Не вдалося зайти до таємного сховища"; +"secrets_recovery_with_key_invalid_recovery_key_message" = "Переконайтеся, що вводите правильний ключ безпеки."; +"secure_key_backup_setup_intro_use_security_key_title" = "Використати ключ безпеки"; +"secure_key_backup_setup_intro_use_security_key_info" = "Згенерувати ключ безпеки для зберігання в надійному місці, наприклад у менеджері паролів чи сейфі."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Встановити фразу безпеки"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Ввести таємну фразу, відому лише вам, і згенерувати ключ резервного копіювання."; + + +// Cancel + +"secure_key_backup_setup_cancel_alert_title" = "Ви впевнені?"; +"secure_key_backup_setup_cancel_alert_message" = "Якщо скасуєте це й загубите пристрій, то втратите зашифровані повідомлення й дані.\n\nВвімкнути захищене резервне копіювання й керувати своїми ключами можна в налаштуваннях."; + +// MARK: - Secrets set up + +// Recovery Key + +"secrets_setup_recovery_key_title" = "Збережіть ключ безпеки"; +"secrets_setup_recovery_key_information" = "Зберігайте ключ безпеки в надійному місці. Ним можна буде розблокувати ваші зашифровані повідомлення й дані."; +"secrets_setup_recovery_key_storage_alert_title" = "Зберігайте його у надійному місці"; + +// Recovery passphrase + +"secrets_setup_recovery_passphrase_title" = "Встановіть фразу безпеки"; +"secrets_setup_recovery_passphrase_information" = "Введіть відому лише вам фразу безпеки для захисту таємниць на вашому сервері."; +"secrets_setup_recovery_passphrase_additional_information" = "Не застосовуйте пароль облікового запису повторно."; +"secrets_setup_recovery_passphrase_confirm_information" = "Для підтвердження введіть таємну фразу ще раз."; +"secure_key_backup_setup_existing_backup_error_title" = "Резервна копія повідомлень уже існує"; +"secure_key_backup_setup_existing_backup_error_info" = "Розблокуйте її, щоб продовжити попереднє захищене резервне копіювання, або видаліть її, щоб почати захищене резервне копіювання повідомлень заново."; +"secure_key_backup_setup_existing_backup_error_unlock_it" = "Розблокувати"; +"secure_key_backup_setup_existing_backup_error_delete_it" = "Видалити"; +"sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Налаштувати резервне копіювання ключів"; +"major_update_information" = "Ми раді повідомити, що змінили назву! Ваш застосунок оновлено й ви ввійшли у свій обліковий запис."; +"pin_protection_reset_alert_message" = "Щоб скинути PIN-код, увійдіть заново та створіть новий"; +"pin_protection_explanatory" = "Встановлення PIN-коду захищає ваші дані, як-от повідомлення чи контакти, обов'язковим запитом вашого PIN-коду при відкритті застосунку."; +"pin_protection_not_allowed_pin" = "Цей PIN-код недостатньо безпечний. Спробуйте інший"; +"pin_protection_settings_section_footer" = "Щоб скинути PIN-код, потрібно ввійти заново та створити новий."; +"pin_protection_mismatch_too_many_times_error_message" = "Не пригадуєте свій PIN-код? Торкніться кнопки \"Забули PIN-код\"."; +"biometrics_usage_reason" = "Застосунок потребує підтвердження особи"; +"biometrics_cant_unlocked_alert_message_x" = "Розблокуйте за допомогою %@ або ввійдіть заново й увімкніть %@ ще раз"; +"biometrics_cant_unlocked_alert_message_login" = "Увійти заново"; +"device_verification_self_verify_wait_recover_secrets_checking_availability" = "Перевірка інших можливостей звірки ..."; +"create_room_section_footer_type" = "Вхід до закритої кімнати — лише за запрошенням."; +"pin_protection_kick_user_alert_message" = "Забагато помилок, ваш сеанс припинено"; + +// MARK: - Secrets recovery + +"secrets_recovery_reset_action_part_1" = "Забули чи втратили всі способи відновлення? "; +"secrets_reset_information" = "Робіть це лише якщо у вас немає іншого пристрою для звірки."; +"secrets_reset_warning_title" = "Якщо ви скинете все"; +"secrets_reset_warning_message" = "Ви розпочнете знову, але без історії повідомлень, без довірених пристроїв та користувачів."; +"secrets_setup_recovery_passphrase_summary_title" = "Збережіть ключ безпеки"; +"secrets_setup_recovery_passphrase_summary_information" = "Запам'ятайте свою фразу безпеки. Нею можна буде розблокувати ваші зашифровані повідомлення й дані."; +"home_empty_view_information" = "Досконалий застосунок для захищеного спілкування команд, друзів і організацій. Торкніться кнопки + унизу, щоб додати людей і кімнати."; +"bug_report_background_mode" = "Продовжити у фоновому режимі"; +"room_intro_cell_information_room_without_topic_sentence2_part2" = " щоб люди знали, про що ця кімната."; +"room_intro_cell_information_dm_sentence2" = "У цій розмові лише ви двоє, більше ніхто не може приєднатися."; +"room_intro_cell_information_multiple_dm_sentence2" = "У цій розмові лише ви, поки хтось із вас не запросить іще когось приєднатися."; +"space_feature_unavailable_subtitle" = "Простори ще не готові на iOS, але доступні у вебпереглядачі та на комп'ютері"; +"space_feature_unavailable_information" = "Простори — новий спосіб групувати кімнати й людей.\n\nСкоро ми їх допрацюємо. Якщо приєднаєтеся до простору на іншій платформі, всі його кімнати стануть доступні й тут."; + +// Success from secure backup +"key_backup_setup_success_from_secure_backup_info" = "Створюється резервна копія ключів."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_phrase" = "Введіть фразу безпеки для продовження."; +"secrets_recovery_with_key_information_unlock_secure_backup_with_key" = "Скористайтеся ключем безпеки для продовження."; +"key_verification_verify_qr_code_scan_code_other_device_action" = "Сканувати цим пристроєм"; +"voice_message_stop_locked_mode_recording" = "Торкніться запису, щоб зупинити або прослухати"; +"version_check_banner_subtitle_supported" = "Ми скоро припинимо підтримку %@ на iOS %@. Щоб усі можливості %@ залишалися вам доступні, радимо оновити вашу версію iOS."; +"version_check_banner_subtitle_deprecated" = "Ми більше не підтримуємо %@ на iOS %@. Щоб усі можливості %@ залишалися вам доступні, радимо оновити вашу версію iOS."; +"version_check_modal_subtitle_supported" = "Ми вдосконалюємо швидкодію та дизайн %@. На жаль, ваша версія iOS сумісна не з усіма нашими виправленнями й скоро втратить підтримку.\nРадимо оновити операційну систему, щоб усі можливості %@ були вам доступні."; +"version_check_modal_subtitle_deprecated" = "Ми вдосконалюємо швидкодію та дизайн %@. На жаль, ваша версія iOS сумісна не з усіма нашими виправленнями й більше не підтримується.\nРадимо оновити операційну систему, щоб усі можливості %@ були вам доступні."; +"leave_space_message" = "Точно вийти з %@? Вийти також із усіх кімнат і просторів цього простору?"; +"leave_space_message_admin_warning" = "Ви адмініструєте цей простір. Перед виходом переконайтесь, що передали права адміністрування іншому учаснику."; +"leave_space_only_action" = "Не виходити з жодної кімнати"; +"leave_space_and_all_rooms_action" = "Вийти з усіх кімнат і просторів"; +"spaces_explore_rooms" = "Дослідити кімнати"; +"spaces_empty_space_detail" = "Деяких кімнат може бути не видно, бо вони закриті й потребують запрошення."; +"spaces_suggested_room" = "Пропоновано"; +"room_details_access_section_anyone_for_dm" = "Будь-хто з посиланням, зокрема гості"; +"spaces_no_room_found_detail" = "Деяких результатів може бути не видно, бо вони закриті й потребують запрошення."; +"spaces_no_member_found_detail" = "Шукаєте когось, хто ще не в %@? Запросіть їх вебпереглядачем або комп'ютером."; +"spaces_invites_coming_soon_title" = "Запрошення ще в розробці"; +"spaces_coming_soon_detail" = "Ця можливість ще не втілена, але скоро буде. Поки що можете зробити це в %@ на комп'ютері."; +"space_participants_action_remove" = "Вилучити з цього простору"; +"space_participants_action_ban" = "Заблокувати в цьому просторі"; + +// Service terms +"service_terms_modal_title_message" = "Щоб продовжити, погодьтеся з запропонованими умови й положеннями"; +"service_terms_modal_description_identity_server" = "Інші зможуть знаходити вас, якщо в контактах їхнього телефону записаний ваш номер чи е-пошта."; +"service_terms_modal_description_integration_manager" = "Це дасть змогу використовувати ботів, мости, віджети й пакунки наліпок."; +"service_terms_modal_information_description_identity_server" = "Сервер ідентифікації допомагає знаходити облікові записи за номером телефону чи адресою е-пошти."; +"service_terms_modal_information_description_integration_manager" = "Менеджер інтеграцій дає змогу додавати сторонній функціонал."; +"share_extension_low_quality_video_message" = "Надішліть в %@ у кращій якості, або нижче в низькій."; + +// Mark: - Polls + +"poll_edit_form_create_poll" = "Створити опитування"; +"poll_edit_form_poll_question_or_topic" = "Питання опитування або тема"; +"poll_edit_form_question_or_topic" = "Питання чи тема"; +"poll_edit_form_input_placeholder" = "Напишіть щось"; +"poll_edit_form_create_options" = "Створіть варіанти"; +"poll_edit_form_option_number" = "Варіант %lu"; +"poll_edit_form_add_option" = "Додати варіант"; +"poll_edit_form_post_failure_title" = "Не вдалося надіслати опитування"; +"poll_edit_form_post_failure_subtitle" = "Повторіть спробу"; +"poll_edit_form_post_failure_action" = "Гаразд"; +"poll_timeline_one_vote" = "1 голос"; +"poll_timeline_votes_count" = "%lu голосів"; +"poll_timeline_total_no_votes" = "Жодного голосу"; +"poll_timeline_total_one_vote" = "1 голос надіслано"; +"poll_timeline_total_votes" = "%lu голосів надіслано"; +"poll_timeline_total_one_vote_not_voted" = "1 голос надіслано. Проголосуйте, щоб побачити результати"; +"poll_timeline_total_votes_not_voted" = "%lu голосів надіслано. Проголосуйте, щоб побачити результати"; +"poll_timeline_total_final_results_one_vote" = "Остаточні результати на підставі 1 голосу"; +"poll_timeline_total_final_results" = "Остаточні результати на підставі %lu голосів"; +"poll_timeline_vote_not_registered_title" = "Голос не зареєстровано"; +"poll_timeline_vote_not_registered_subtitle" = "Не вдалося зареєструвати ваш голос, просимо повторити спробу"; +"poll_timeline_vote_not_registered_action" = "Гаразд"; +"poll_timeline_not_closed_title" = "Не вдалося завершити опитування"; +"poll_timeline_not_closed_subtitle" = "Повторіть спробу"; +"poll_timeline_not_closed_action" = "Гаразд"; +"e2e_need_log_in_again" = "Вам потрібно ввійти заново, щоб згенерувати ключі наскрізного шифрування для цього сеансу й надіслати відкритий ключ домашньому серверу.\nЦе одноразова дія; перепрошуємо за незручності."; + +// Crypto +"e2e_enabling_on_app_update" = "%@ тепер підтримує наскрізне шифрування. Перезайдіть, щоб його ввімкнути.\n\nМожете зробити це зараз або пізніше в налаштуваннях застосунку."; + +// Key backup wrong version +"e2e_key_backup_wrong_version_title" = "Нова резервна копія ключів"; +"e2e_key_backup_wrong_version" = "Знайдено нову резервну копію ключів до захищених повідомлень.\n\nЯкщо це були не ви, встановіть нову фразу безпеки в налаштуваннях."; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "Ми не записуємо й не аналізуємо жодних даних облікового запису"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "Ми не передаємо даних стороннім особам"; +"analytics_prompt_point_3" = "Можете вимкнути це коли завгодно в налаштуваннях"; +"analytics_prompt_not_now" = "Відкласти"; +"analytics_prompt_yes" = "Так, усе гаразд"; +"analytics_prompt_stop" = "Більше не надсилати"; +"analytics_prompt_terms_link_upgrade" = "тут"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Прочитайте всі наші умови %@. Згодні з ними?"; +"analytics_prompt_terms_link_new_user" = "тут"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Можете прочитати всі наші умови %@."; +"analytics_prompt_message_upgrade" = "Раніше ви погодилися надсилати нам анонімні дані про використання. Тепер, щоб розуміти, як люди використовують кілька пристроїв, ми створимо спільний для ваших пристроїв випадковий ідентифікатор."; +"analytics_prompt_message_new_user" = "Допомагайте нам визначати проблеми й удосконалювати %@, надсилаючи анонімні дані про використання. Щоб розуміти, як люди використовують кілька пристроїв, ми створимо спільний для ваших пристроїв випадковий ідентифікатор."; + +// Analytics +"analytics_prompt_title" = "Допоможіть удосконалити %@"; +"widget_integration_room_not_visible" = "Кімната %@ недоступна."; +"widget_integration_missing_user_id" = "В запиті бракує user_id."; +"widget_integration_missing_room_id" = "В запиті бракує room_id."; +"widget_integration_no_permission_in_room" = "У вас нема такого дозволу в цій кімнаті."; +"widget_integration_must_be_in_room" = "Вас нема в цій кімнаті."; +"widget_integration_positive_power_level" = "Рівень повноважень має бути цілим додатним числом."; +"widget_integration_room_not_recognised" = "Кімнату не знайдено."; +"widget_integration_failed_to_send_request" = "Не вдалося надіслати запит."; +"widget_integration_unable_to_create" = "Не вдалося створити віджет."; + +// Widget Integration Manager +"widget_integration_need_to_be_able_to_invite" = "Для цього вам потрібен дозвіл запрошувати користувачів."; +"widget_creation_failure" = "Помилка створення віджету"; +"widget_no_power_to_manage" = "Для керування віджетами у цій кімнаті потрібен дозвіл"; +"bug_report_progress_uploading" = "Надсилання звіту"; +"bug_report_progress_zipping" = "Збір журналів"; +"bug_report_send_screenshot" = "Надіслати знімок екрана"; +"room_details_access_section_for_dm" = "Хто може мати доступ?"; +"room_details_access_section" = "Хто має доступ до кімнати?"; +"room_details_low_priority_tag" = "Неважливе"; +"room_details_favourite_tag" = "Улюблене"; +"identity_server_settings_alert_disconnect_still_sharing_3pid" = "Сервер ідентифікації %@ досі поширює ваші особисті дані.\n\nРадимо вилучити адреси е-пошти й номери телефонів із сервера ідентифікації, перш ніж від'єднатися."; +"settings_analytics_and_crash_data" = "Надсилати дані про збої та аналітику"; +"accessibility_button_label" = "кнопка"; +"enable" = "Увімкнути"; +"call_no_stun_server_error_use_fallback_button" = "Спробувати %@"; +"call_no_stun_server_error_message_2" = "Також ви можете спробувати публічний сервер %@, але це буде менш надійно й сервер бачитиме вашу IP-адресу. Ви можете керувати цим у налаштуваннях"; +"call_no_stun_server_error_message_1" = "Запропонуйте адміністратору вашого домашнього сервера %@ налаштувати сервер TURN для надійної роботи викликів."; +"call_no_stun_server_error_title" = "Не вдалося зателефонувати через неправильно налаштований сервер"; +"call_jitsi_error" = "Не вдалося приєднатися до конференції."; +"call_already_displayed" = "Виклик уже триває."; +"photo_library_access_not_granted" = "%@ не має доступу до медіатеки, просимо змінити налаштування приватності"; +"camera_unavailable" = "Камера недоступна на вашому пристрої"; +"camera_access_not_granted" = "%@ не має дозволу використовувати камеру, просимо змінити налаштування приватності"; +"rage_shake_prompt" = "Схоже, ви розчаровано струсили телефон. Бажаєте надіслати звіт про ваду?"; +"bug_report_prompt" = "При останньому запуску застосунок закрився з помилкою. Бажаєте надіслати звіт про помилку?"; +"homeserver_connection_lost" = "Не вдалося з'єднатися з домашнім сервером."; +"event_formatter_jitsi_widget_removed_by_you" = "Ви вилучаєте голосову конференцію"; +"event_formatter_jitsi_widget_added_by_you" = "Ви додаєте голосову конференцію"; +"event_formatter_rerequest_keys_part2" = " ваших інших сеансів."; +"event_formatter_rerequest_keys_part1_link" = "Повторити запит ключів шифрування"; +"event_formatter_jitsi_widget_removed" = "%@ вилучає голосову конференцію"; +"event_formatter_jitsi_widget_added" = "%@ додає голосову конференцію"; +"event_formatter_widget_removed" = "%@ віджет видалено %@"; +"event_formatter_widget_added" = "%@ віджет додано %@"; + +// Events formatter +"event_formatter_member_updates" = "%tu змін членства"; +"directory_server_type_homeserver" = "Загальнодоступні кімнати якого домашнього сервера перелічити?"; +"receipt_status_read" = "Прочитано: "; + +// Read Receipts +"read_receipts_list" = "Список міток прочитання"; +"room_details_addresses_disable_main_address_prompt_msg" = "Ви вилучаєте головну адресу. Усталена головна адреса для цієї кімнати буде обрана випадково"; +"room_details_access_section_no_address_warning" = "Щоб на кімнату посилалися, вона повинна мати адресу"; +"room_details_access_section_anyone" = "Будь-хто з посиланням, зокрема гості"; +"room_details_access_section_anyone_apart_from_guest_for_dm" = "Будь-хто з посиланням, окрім гостей"; +"room_details_access_section_anyone_apart_from_guest" = "Будь-хто з посиланням, окрім гостей"; +"deactivate_account_informations_part5" = "Якщо ви хочете, щоб ми вилучили ваші повідомлення, встановіть прапорець унизу\n\nВидимість повідомлень у Matrix схожа на електронну пошту. Тобто ваші повідомлення не зможуть переглянути нові або незареєстровані користувачі, але зареєстровані користувачі, які вже мають доступ до цих повідомлень і надалі матимуть доступ до їхньої копії на своїх пристроях."; +"deactivate_account_informations_part4_emphasize" = "типово не вилучає надіслані вами повідомлення. "; +"deactivate_account_informations_part2_emphasize" = "Ця дія незворотна."; +"deactivate_account_informations_part1" = "Вашим обліковим записом більше ніколи не можна буде користуватися. Ви не зможете увійти, і ніхто не зможе повторно зареєструвати той самий ID користувача. Ваш обліковий запис буде видалено з усіх кімнат, а всі його дані вилучено з вашого сервера ідентифікації. "; +"e2e_room_key_request_message" = "Ваш не звірений сеанс «%@» запитує ключі шифрування."; +"e2e_room_key_request_message_new_device" = "Ви додали новий сеанс «%@», котрий запитує ключі шифрування."; +"room_widget_permission_information_title" = "Віджет може надсилати дані до %@:\n"; +"room_widget_permission_webview_information_title" = "Віджет може встановлювати куки та надсилати дані до %@:\n"; +"share_extension_failed_to_encrypt" = "Не вдалося надіслати. Перевірте налаштування шифрування для цієї кімнати в головному застосунку"; + +// Share extension +"share_extension_auth_prompt" = "Увійдіть до основного застосунку, щоб поділитися вмістом"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Позначте, щоб погодитися на %@"; +"gdpr_consent_not_given_alert_review_now_action" = "Переглянути зараз"; + +// GDPR +"gdpr_consent_not_given_alert_message" = "Перегляньте та погодьтеся з умовами користування, щоб продовжувати використовувати сервер %@."; +"e2e_room_key_request_ignore_request" = "Знехтувати запит"; +"e2e_room_key_request_share_without_verifying" = "Надіслати без перевірки"; +"e2e_room_key_request_start_verification" = "Починається перевірка…"; +"deactivate_account_forget_messages_information_part3" = ": внаслідок цього майбутні користувачі не можуть не розуміти змісту розмов)"; +"deactivate_account_forget_messages_information_part1" = "Видаліть усі надіслані мною повідомлення після деактивації мого облікового запису ("; +"rerequest_keys_alert_message" = "Запустіть %@ на іншому пристрої, який зможе розшифрувати повідомлення та надіслати ключі цьому сеансу."; +"widget_integration_manager_disabled" = "Необхідно увімкнути менеджер інтеграцій у налаштуваннях"; +"widget_menu_remove" = "Вилучити для всіх"; +"widget_menu_revoke_permission" = "Відкликати мій доступ"; +"widget_menu_open_outside" = "Відкрити у переглядачі"; +"widget_menu_refresh" = "Оновити"; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Додати зараз?"; +"widget_sticker_picker_no_stickerpacks_alert" = "На разі жоден пакунок наліпок не увімкнено."; +"widget_integrations_server_failed_to_connect" = "Не вдалося зʼєднатися з сервером інтеграцій"; + +// Widget +"widget_no_integrations_server_configured" = "Сервер інтеграцій не налаштовано"; +"bug_report_description" = "Опишіть ваду. Що ви робили? Виконання якої дії очікували? Що сталося натомість?"; +"bug_crash_report_title" = "Звіт про збій"; +"bug_crash_report_description" = "Будь ласка, опишіть свої дії перед збоєм:"; +"bug_report_logs_description" = "Задля діагностики, журнали клієнта будуть надіслані разом зі звітом про ваду. Якщо бажаєте надіслати лише текст угорі, зніміть галочку:"; +"bug_report_send_logs" = "Надіслати журнали"; +"group_participants_invite_another_user" = "Знайти чи запросити за ID користувача чи іменем"; +"group_participants_invite_malformed_id" = "Хибний ID. Треба Matrix ID вигляду «@localpart:domain»"; + + +// MARK: Key backup setup + +"key_backup_setup_title" = "Резервне копіювання ключів"; +"key_backup_setup_skip_alert_message" = "Ви втратите захищені повідомлення, якщо вийдете чи загубите пристрій."; + +// Intro + +"key_backup_setup_intro_title" = "Ніколи не втрачайте зашифровані повідомлення"; +"key_backup_setup_intro_info" = "Повідомлення в зашифрованих кімнатах захищені наскрізним шифруванням. Тільки ви та одержувачі маєте ключі для читання цих повідомлень.\n\nСтворіть захищену резервну копію ключів, щоб їх не втратити."; +"key_backup_setup_passphrase_info" = "Ми збережемо зашифровану копію ваших ключів на нашому сервері. Захистіть свою резервну копію парольною фразою.\n\nДля максимальної безпеки фраза повинна відрізнятися від пароля вашого облікового запису."; +"key_backup_setup_passphrase_set_passphrase_action" = "Встановити фразу"; + +// MARK: Key backup recover + +"key_backup_recover_title" = "Захищені повідомлення"; + +// Recover from passphrase + +"key_backup_recover_from_passphrase_info" = "Використайте фразу безпеки, щоб розблокувати історію зашифрованих повідомлень"; + +// Recover from recovery key + +"key_backup_recover_from_recovery_key_info" = "Використайте ключ безпеки, щоб розблокувати історію зашифрованих повідомлень"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Забули свою фразу безпеки? Можете "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "використати ключ безпеки"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Введіть ключ безпеки"; +"key_backup_recover_from_recovery_key_recover_action" = "Розблокувати історію"; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Втратили ключ безпеки? Встановіть новий у налаштуваннях."; + +// Success + +"key_backup_recover_success_info" = "Резервну копію відновлено!"; +"key_backup_recover_done_action" = "Готово"; +"key_backup_setup_intro_setup_action_without_existing_backup" = "Налаштувати резервне копіювання ключів"; + +// Passphrase + +"key_backup_setup_passphrase_title" = "Захистіть резервну копію фразою безпеки"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Або захистіть резервну копію ключем безпеки, який зберігатимете в надійному місці."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Додатково) Налаштувати ключ безпеки"; + +// Success + +"key_backup_setup_success_title" = "Успіх!"; + +// Success from passphrase +"key_backup_setup_success_from_passphrase_info" = "Триває резервне копіювання ваших ключів.\n\nКлюч безпеки підстраховує вас: можете використати його для відновлення доступу до ваших зашифрованих повідомлень, якщо забудете парольну фразу.\n\nТримайте відновлювальний ключ у якомусь дуже надійному місці, наприклад у менеджері паролів (або сейфі)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Зберегти ключ безпеки"; + +// Success from recovery key +"key_backup_setup_success_from_recovery_key_info" = "Триває резервне копіювання ваших ключів.\n\nСкопіюйте цей ключ безпеки й надійно його зберігайте."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Ключ безпеки"; +"key_backup_setup_success_from_recovery_key_make_copy_action" = "Зробити копію"; +"key_backup_setup_success_from_recovery_key_made_copy_action" = "Вже маю копію"; +"key_backup_recover_invalid_passphrase_title" = "Хибна фраза безпеки"; +"key_backup_recover_invalid_passphrase" = "Не вдається розшифрувати резервну копію цією фразою: переконайтеся, що вводите правильну фразу безпеки."; +"key_backup_recover_invalid_recovery_key_title" = "Хибний ключ безпеки"; +"key_backup_recover_invalid_recovery_key" = "Не вдалося розшифрувати резервну копію цим ключем: переконайтеся, що вводите правильний ключ безпеки."; + +// MARK: Sign out warning + +"sign_out_existing_key_backup_alert_title" = "Точно вийти?"; +"sign_out_existing_key_backup_alert_sign_out_action" = "Вийти"; +"sign_out_non_existing_key_backup_alert_title" = "Якщо вийти зараз, ви втратите свої зашифровані повідомлення"; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Мені не потрібні мої зашифровані повідомлення"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Ви втратите доступ до ваших зашифрованих повідомлень"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Ви втратите доступ до своїх зашифрованих повідомлень, якщо не зробите резервну копію ключів перед виходом з системи."; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Вийти"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Резервна копія"; +"sign_out_key_backup_in_progress_alert_title" = "Триває резервне копіювання ключів. Якщо вийти зараз, ви втратите доступ до своїх зашифрованих повідомлень."; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Мені не потрібні мої зашифровані повідомлення"; +"sign_out_key_backup_in_progress_alert_cancel_action" = "Почекаю"; +"key_backup_setup_intro_manual_export_info" = "(Розширені)"; +"key_backup_setup_intro_manual_export_action" = "Експорт ключів вручну"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Налаштувати цьому пристрою резервне копіювання ключів"; +"device_verification_cancelled" = "Інша сторона скасувала звірку."; +"device_verification_cancelled_by_me" = "Звірку скасовано. Причина: %@"; +"device_verification_error_cannot_load_device" = "Не вдалося отримати дані сеансу."; + +// Mark: Incoming +"device_verification_incoming_title" = "Надійшов запит на звірку"; +"device_verification_incoming_description_1" = "Звірте цей сеанс, щоб позначити його довіреним. Довірені сеанси партнерів дають змогу впевненіше користуватися наскрізним шифруванням повідомлень."; +"device_verification_incoming_description_2" = "Звірка цього сеансу позначить його довіреним для вас, а ваш довіреним для партнера."; + +// MARK: Start +"device_verification_start_title" = "Звірити, порівнявши короткий текстовий рядок"; +"device_verification_start_wait_partner" = "Очікування підтвердження партнером…"; +"device_verification_start_use_legacy" = "Нічого не з'являється? Ще не всі клієнти підтримують інтерактивну звірку. Звірте по-старому."; +"device_verification_start_verify_button" = "Почати звірку"; +"device_verification_start_use_legacy_action" = "Звірити по-старому"; +"key_verification_tile_request_status_expired" = "Термін сплив"; +"key_verification_user_title" = "Звірте їх"; +"user_verification_start_information_part2" = " порівнявши одноразовий код на обох ваших пристроях."; +"user_verification_start_information_part1" = "Звірте безпечніше, "; + +// MARK: - User verification + +// Start + +"user_verification_start_verify_action" = "Почати звірку"; +"user_verification_start_waiting_partner" = "Очікування на %@…"; +"user_verification_start_additional_information" = "Для безпеки зробіть це особисто або скористайтеся іншим способом зв'язку."; + +// Sessions list + +"user_verification_sessions_list_user_trust_level_trusted_title" = "Довірений"; +"user_verification_sessions_list_information" = "Листування з цим користувачем у цій кімнаті наскрізно зашифроване й непрочитне для сторонніх."; +"user_verification_session_details_information_untrusted_current_user" = "Звірте цей сеанс, щоб позначити його довіреним і надати йому доступ до зашифрованих повідомлень:"; +"secrets_setup_recovery_key_storage_alert_message" = "✓ Надрукуйте його й зберігайте в надійному місці\n✓ Збережіть його на USB-ключ або носій резервного копіювання\n✓ Скопіюйте його до вашого особистого хмарного сховища"; +"call_transfer_error_message" = "Не вдалося переадресувати виклик"; +"favourites_empty_view_information" = "Ви можете додати до улюблених кількома способами, найшвидший — це просто натиснути й утримувати. Торкніться зірки, і вони автоматично зʼявляться тут для безпечного зберігання."; + +// MARK: - Favourites + +"favourites_empty_view_title" = "Улюблені кімнати й люди"; +"group_participants_filter_members" = "Фільтр учасників спільноти"; +"group_participants_invite_prompt_msg" = "Ви впевнені, що хочете запросити %@ до цієї групи?"; +"room_details_fail_to_update_room_direct" = "Не вдалося оновити прямий прапор цієї кімнати"; +"room_details_flair_section" = "Показувати значки для спільнот"; +"settings_flair" = "Показувати значок, де це дозволено"; +"room_warning_about_encryption" = "Наскрізне шифрування ще на етапі бета-тестування й може бути ненадійним.\n\nПоки що не варто довіряти йому захист даних.\n\nПристрої ще не зможуть розшифрувати історію до того, як з них приєдналися до кімнати.\n\nЗашифровані повідомлення не буде показано у клієнтах, які ще не використовують шифрування."; +"location_sharing_settings_toggle_title" = "Увімкнути надсилання місцеперебування"; +"location_sharing_settings_header" = "Надсилання місцеперебування"; +"location_sharing_open_google_maps" = "Відкрити в Картах Google"; +"location_sharing_open_apple_maps" = "Відкрити в Apple Maps"; +"location_sharing_invalid_authorization_settings" = "Налаштування"; +"location_sharing_invalid_authorization_not_now" = "Не зараз"; +"location_sharing_invalid_authorization_error_title" = "%@ не має дозволу на доступ до вашого місцеперебування. Ви можете увімкнути доступ у Налаштування > Місцеперебування"; +"location_sharing_locating_user_error_title" = "%@ не може отримати доступ до вашого місцеперебування. Повторіть спробу пізніше."; +"location_sharing_loading_map_error_title" = "%@ не може завантажити карту. Повторіть спробу пізніше."; +"location_sharing_share_action" = "Поділиться"; +"location_sharing_close_action" = "Закрити"; + +// MARK: - Location sharing + +"location_sharing_title" = "Місцеперебування"; +"ok" = "Гаразд"; +"settings_enable_room_message_bubbles" = "Бульбашки повідомлень"; +"onboarding_splash_page_4_message" = "Елемент також чудово підходить для роботи. Йому довіряють найбезпечніші організації світу."; +"onboarding_splash_page_4_title_no_pun" = "Спілкування зі своєю командою."; +"onboarding_splash_page_3_message" = "Наскрізне шифрування та відсутність потреби у телефонному номері. Без реклами чи аналізу даних."; +"onboarding_splash_page_3_title" = "Безпечне спілкування."; +"onboarding_splash_page_2_message" = "Оберіть, де зберігати ваші бесіди, що дасть вам контроль і незалежність. Спілкуйтеся через Matrix."; +"onboarding_splash_page_2_title" = "Ви контролюєте все."; +"onboarding_splash_page_1_message" = "Безпечне та незалежне спілкування, яке дає вам такий же рівень приватності як особиста розмова у вашому власному домі."; +"onboarding_splash_page_1_title" = "Будьте господарем свого спілкування."; +"onboarding_splash_login_button_title" = "У мене вже є обліковий запис"; + +// Onboarding +"onboarding_splash_register_button_title" = "Створити обліковий запис"; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index f54deca1f..12863f8fa 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -1548,7 +1548,7 @@ "space_home_show_all_rooms" = "显示所有聊天室"; "room_event_action_forward" = "转发"; "poll_edit_form_add_option" = "添加选项"; -"poll_edit_form_option_number" = "选项 %d"; +"poll_edit_form_option_number" = "选项 %lu"; "poll_edit_form_create_options" = "创建选项"; "poll_edit_form_input_placeholder" = "写些东西"; "poll_edit_form_question_or_topic" = "问题或话题"; @@ -1562,3 +1562,46 @@ "share_extension_low_quality_video_title" = "将以低画质发送视频"; "settings_discovery_accept_terms" = "接受身份服务器条款"; "settings_about" = "关于"; +"poll_timeline_not_closed_action" = "好"; +"poll_timeline_not_closed_subtitle" = "请再试一次"; +"poll_timeline_not_closed_title" = "结束投票失败"; +"poll_timeline_vote_not_registered_action" = "好"; +"poll_timeline_vote_not_registered_subtitle" = "抱歉,您的投票未登记,请重试"; +"poll_timeline_vote_not_registered_title" = "投票未登记"; +"poll_timeline_total_final_results" = "基于 %lu 票的最终结果"; +"poll_timeline_total_final_results_one_vote" = "基于 1 票的最终结果"; +"poll_timeline_total_votes_not_voted" = "已有 %lu 票。投票查看结果"; +"poll_timeline_total_one_vote_not_voted" = "已有 1 票。 投票查看结果"; +"poll_timeline_total_votes" = "%lu 票"; +"poll_timeline_total_one_vote" = "1 票"; +"poll_timeline_total_no_votes" = "尚无投票"; +"poll_timeline_votes_count" = "%lu 票"; +"poll_timeline_one_vote" = "1 票"; +"poll_edit_form_post_failure_action" = "好"; +"poll_edit_form_post_failure_subtitle" = "请再试一次"; +"poll_edit_form_post_failure_title" = "发布投票失败"; +"settings_labs_enabled_polls" = "投票"; +"room_event_action_end_poll" = "结束投票"; +"room_event_action_remove_poll" = "删除投票"; +"analytics_prompt_stop" = "停止共享"; +"analytics_prompt_yes" = "是的,没关系"; +"analytics_prompt_not_now" = "不是现在"; +"analytics_prompt_point_3" = "您可以随时在设置中关闭此功能"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_2" = "我们与第三方共享信息"; +/* Note: The word "don't" is formatted in bold */ +"analytics_prompt_point_1" = "我们 记录任何账户数据或绘制任何账户数据的画像"; +"analytics_prompt_terms_link_upgrade" = "此处"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "阅读我们所有的条款 %@。 那样行吗?"; +"analytics_prompt_terms_link_new_user" = "此处"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "你可以阅读我们所有的条款 %@。"; +"analytics_prompt_message_upgrade" = "您之前同意与我们共享匿名使用数据。 现在,为了帮助了解人们如何使用多个设备,我们将生成一个随机标识符,由您的设备共享。"; +"analytics_prompt_message_new_user" = "通过共享匿名使用数据帮助我们发现问题并改进 Element。 为了了解人们如何使用多个设备,我们将生成一个随机标识符,由您的设备共享。"; + +// Analytics +"analytics_prompt_title" = "帮助改进 %@"; +"settings_analytics_and_crash_data" = "发送崩溃和分析数据"; +"accessibility_button_label" = "按钮"; +"enable" = "启用"; diff --git a/Riot/Categories/MXEvent.swift b/Riot/Categories/MXEvent.swift new file mode 100644 index 000000000..5ba0c1c7d --- /dev/null +++ b/Riot/Categories/MXEvent.swift @@ -0,0 +1,29 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK + +extension MXEvent { + + /// Get MXMessageType if any + var messageType: MXMessageType? { + guard let messageTypeString = self.content["msgtype"] as? String else { + return nil + } + return MXMessageType(identifier: messageTypeString) + } +} diff --git a/Riot/Categories/MXKRoomBubbleCellData+Riot.swift b/Riot/Categories/MXKRoomBubbleCellData+Riot.swift new file mode 100644 index 000000000..bfb7d1846 --- /dev/null +++ b/Riot/Categories/MXKRoomBubbleCellData+Riot.swift @@ -0,0 +1,30 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension MXKRoomBubbleCellData { + + // Indicate true if the cell data is collapsable and collapsed + var isCollapsableAndCollapsed: Bool { + return self.collapsable && self.collapsed + } + + var cellDataTag: RoomBubbleCellDataTag { + return RoomBubbleCellDataTag(rawValue: self.tag) ?? .message + } + +} diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h index 24a0ff136..cc20ce9d0 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h @@ -67,6 +67,17 @@ extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePr */ - (void)addTimestampLabelForComponent:(NSUInteger)componentIndex; +/** + Add timestamp label for a component in receiver. + + Note: The label added here is automatically removed when [didEndDisplay] is called. + + @param componentIndex index of the component in bubble message data + @param displayOnLeft Indicate true to display label on left and false to display on right + */ +- (void)addTimestampLabelForComponent:(NSUInteger)componentIndex + displayOnLeft:(BOOL)displayOnLeft; + /** Highlight a component in receiver. diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index 2d2a0dbfc..98fde6a11 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -36,6 +36,29 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = @implementation MXKRoomBubbleTableViewCell (Riot) - (void)addTimestampLabelForComponent:(NSUInteger)componentIndex +{ + BOOL isFirstDisplayedComponent = (componentIndex == 0); + BOOL isLastMessageMostRecentComponent = NO; + + RoomBubbleCellData *roomBubbleCellData; + + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + roomBubbleCellData = (RoomBubbleCellData*)bubbleData; + isFirstDisplayedComponent = (componentIndex == roomBubbleCellData.oldestComponentIndex); + isLastMessageMostRecentComponent = roomBubbleCellData.containsLastMessage && (componentIndex == roomBubbleCellData.mostRecentComponentIndex); + } + + // Display timestamp on the left for selected component when it cannot overlap other UI elements like user's avatar + BOOL displayLabelOnLeft = roomBubbleCellData.displayTimestampForSelectedComponentOnLeftWhenPossible + && !isLastMessageMostRecentComponent + && (!isFirstDisplayedComponent || roomBubbleCellData.shouldHideSenderInformation); + + [self addTimestampLabelForComponent:componentIndex displayOnLeft:displayLabelOnLeft]; +} + +- (void)addTimestampLabelForComponent:(NSUInteger)componentIndex + displayOnLeft:(BOOL)displayLabelOnLeft { MXKRoomBubbleComponent *component; @@ -49,7 +72,6 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = if (component && component.date) { BOOL isFirstDisplayedComponent = (componentIndex == 0); - BOOL isLastMessageMostRecentComponent = NO; RoomBubbleCellData *roomBubbleCellData; @@ -57,14 +79,8 @@ NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = { roomBubbleCellData = (RoomBubbleCellData*)bubbleData; isFirstDisplayedComponent = (componentIndex == roomBubbleCellData.oldestComponentIndex); - isLastMessageMostRecentComponent = roomBubbleCellData.containsLastMessage && (componentIndex == roomBubbleCellData.mostRecentComponentIndex); } - // Display timestamp on the left for selected component when it cannot overlap other UI elements like user's avatar - BOOL displayLabelOnLeft = roomBubbleCellData.displayTimestampForSelectedComponentOnLeftWhenPossible - && !isLastMessageMostRecentComponent - && ( !isFirstDisplayedComponent || roomBubbleCellData.shouldHideSenderInformation); - [self addTimestampLabelForComponentIndex:componentIndex isFirstDisplayedComponent:isFirstDisplayedComponent viewTag:componentIndex diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m index faf32bd01..97a74e5b2 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -26,15 +26,15 @@ NSUInteger missedDiscussionsCount = 0; // Sum all the rooms with missed notifications. - for (MXRoomSummary *roomSummary in self.roomsSummaries) + for (MXRoom *room in self.rooms) { - NSUInteger notificationCount = roomSummary.notificationCount; + NSUInteger notificationCount = room.summary.notificationCount; // Ignore the regular notification count if the room is in 'mentions only" mode at the Riot level. - if (roomSummary.room.isMentionsOnly) + if (room.isMentionsOnly) { // Only the highlighted missed messages must be considered here. - notificationCount = roomSummary.highlightCount; + notificationCount = room.summary.highlightCount; } if (notificationCount) diff --git a/Riot/Categories/UIImage.swift b/Riot/Categories/UIImage.swift index 4b5a9703a..2faec2be1 100644 --- a/Riot/Categories/UIImage.swift +++ b/Riot/Categories/UIImage.swift @@ -59,6 +59,7 @@ extension UIImage { // Based on https://stackoverflow.com/a/31314494 @objc func vc_resized(with targetSize: CGSize) -> UIImage? { + let originalRenderingMode = self.renderingMode let size = self.size let widthRatio = targetSize.width/size.width @@ -79,7 +80,7 @@ extension UIImage { let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return newImage + return newImage?.withRenderingMode(originalRenderingMode) } @objc func vc_notRenderedImage() -> UIImage { diff --git a/Riot/Categories/UIView+Toast.swift b/Riot/Categories/UIView+Toast.swift new file mode 100644 index 000000000..e395578e3 --- /dev/null +++ b/Riot/Categories/UIView+Toast.swift @@ -0,0 +1,311 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +// MARK: - ToastPosition + +/// Vertical position for a toast +@objc +enum ToastPosition: Int { + /// Toast will be placed at the top of the screen, with a margin to the safe area insets of the superview. Max height is also limited with safe area insets. + case top + /// Toast will be placed at the middle of the screen vertically. Max height is also limited with safe area insets. + case middle + /// Toast will be placed at the bottom of the screen, with a margin to the safe area insets of the superview. Max height is also limited with safe area insets. + case bottom +} + +// MARK: - UIView Extension + +extension UIView { + + private enum Constants { + static let defaultDuration: TimeInterval = 2.0 + static let defaultPosition: ToastPosition = .bottom + } + + private static var operationQueue: OperationQueue = { + let queue = OperationQueue.vc_createSerialOperationQueue(name: "ToastQueue") + queue.qualityOfService = .userInteractive + queue.underlyingQueue = .main + return queue + }() + + /// Show a toast message with the given properties. + /// - Parameters: + /// - message: Message to be displayed + /// - image: Icon to be displayed. Placed left to the message. Will be tinted. + /// - duration: Duration of the toast messsage + /// - position: Vertical position of the toast message in the view. Toast view spans the receiver view horizontally, taking into account the safe area insets. + /// - additionalMargin: By default, a toast placed according to safe area insets, with a margin. + /// For `top` and `bottom` positions, adds toast an additional margin from the top and bottom respectively. + /// Has no effect for `middle` position. + @objc + func vc_toast(message: String?, + image: UIImage? = nil, + duration: TimeInterval = Constants.defaultDuration, + position: ToastPosition = Constants.defaultPosition, + additionalMargin: CGFloat = 0.0) { + let view = ToastView(withMessage: message, image: image) + vc_toast(view: view, duration: duration, position: position, additionalMargin: additionalMargin) + } + + /// Show a toast view with the given properties. + /// - Parameters: + /// - view: View to be displayed as a toast + /// - duration: Duration of the toast messsage + /// - position: Vertical position of the toast message in the view. Toast view spans the receiver view horizontally, taking into account the safe area insets. + /// - additionalMargin: By default, a toast placed according to safe area insets, with a margin. + /// For `top` and `bottom` positions, adds toast an additional margin from the top and bottom respectively. + /// Has no effect for `middle` position. + @objc + func vc_toast(view: UIView, + duration: TimeInterval = Constants.defaultDuration, + position: ToastPosition = Constants.defaultPosition, + additionalMargin: CGFloat = 0.0) { + let operation = ToastOperation(containerView: self, + toastView: view, + duration: duration, + position: position, + additionalMargin: additionalMargin, + completion: nil) + Self.operationQueue.addOperation(operation) + } + +} + +// MARK: - ToastOperation + +/// Async toast UI operation. Will run on the main thread. +private class ToastOperation: AsyncOperation { + + private enum Constants { + static let margin: UIEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + static let animationDuration: TimeInterval = 0.15 + static let timeBetweenToasts: TimeInterval = 0.5 + } + + private var containerView: UIView + private var toastView: UIView + private var duration: TimeInterval + private var position: ToastPosition + private var additionalMargin: CGFloat + private var completion: (() -> Void)? + private var timer: Timer? + + init(containerView: UIView, + toastView: UIView, + duration: TimeInterval, + position: ToastPosition, + additionalMargin: CGFloat, + completion: (() -> Void)? = nil) { + self.containerView = containerView + self.toastView = toastView + self.duration = duration + self.position = position + self.additionalMargin = additionalMargin + self.completion = completion + } + + override func main() { + showToast { + self.invalidateTimer() + let timer = Timer(timeInterval: self.duration, + target: self, + selector: #selector(self.timerFired(_:)), + userInfo: nil, + repeats: false) + RunLoop.main.add(timer, forMode: .common) + self.timer = timer + } + } + + @objc + private func timerFired(_ timer: Timer) { + invalidateTimer() + hideToast() + } + + private func showToast(_ completion: @escaping () -> Void) { + toastView.alpha = 0.0 + containerView.addSubview(toastView) + toastView.translatesAutoresizingMaskIntoConstraints = false + switch position { + case .top: + NSLayoutConstraint.activate([ + toastView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor, + constant: Constants.margin.left), + toastView.topAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.topAnchor, + constant: Constants.margin.top + additionalMargin), + toastView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor, + constant: -Constants.margin.right), + toastView.bottomAnchor.constraint(lessThanOrEqualTo: containerView.safeAreaLayoutGuide.bottomAnchor, + constant: -Constants.margin.bottom) + ]) + case .middle: + NSLayoutConstraint.activate([ + toastView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor, + constant: Constants.margin.left), + toastView.topAnchor.constraint(greaterThanOrEqualTo: containerView.safeAreaLayoutGuide.topAnchor, + constant: Constants.margin.top), + toastView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor, + constant: -Constants.margin.right), + toastView.bottomAnchor.constraint(lessThanOrEqualTo: containerView.safeAreaLayoutGuide.bottomAnchor, + constant: -Constants.margin.bottom), + toastView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor) + ]) + case .bottom: + NSLayoutConstraint.activate([ + toastView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor, + constant: Constants.margin.left), + toastView.topAnchor.constraint(greaterThanOrEqualTo: containerView.safeAreaLayoutGuide.topAnchor, + constant: Constants.margin.top), + toastView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor, + constant: -Constants.margin.right), + toastView.bottomAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.bottomAnchor, + constant: -Constants.margin.bottom - additionalMargin) + ]) + } + + UIView.animate(withDuration: Constants.animationDuration, + delay: 0.0, + options: [.curveEaseOut, .allowUserInteraction], + animations: { + self.toastView.alpha = 1.0 + }, completion: { _ in + completion() + }) + } + + private func hideToast() { + UIView.animate(withDuration: Constants.animationDuration, + delay: 0.0, + options: [.curveEaseIn, .beginFromCurrentState], + animations: { + self.toastView.alpha = 0.0 + }, completion: { _ in + self.toastView.removeFromSuperview() + DispatchQueue.main.asyncAfter(deadline: .now() + Constants.timeBetweenToasts) { + self.finish() + self.completion?() + } + }) + } + + private func invalidateTimer() { + timer?.invalidate() + timer = nil + } + +} + +// MARK: - ToastView + +/// Default view for a basic toast. +private class ToastView: UIView, Themable { + + private enum Constants { + static let padding: UIEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + static let cornerRadius: CGFloat = 8.0 + } + + private lazy var imageView: UIImageView = { + let view = UIImageView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .clear + return view + }() + + private lazy var messageLabel: UILabel = { + let label = UILabel() + label.font = ThemeService.shared().theme.fonts.body + label.backgroundColor = .clear + label.numberOfLines = 0 + label.textAlignment = .left + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private lazy var stackView: UIStackView = { + let result = UIStackView() + result.axis = .horizontal + result.distribution = .fill + result.alignment = .center + result.spacing = 8.0 + result.backgroundColor = .clear + + addSubview(result) + result.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + result.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.padding.left), + result.topAnchor.constraint(equalTo: topAnchor, constant: Constants.padding.top), + result.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.padding.right), + result.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.padding.bottom) + ]) + + return result + }() + + init(withMessage message: String?, + image: UIImage? = nil) { + super.init(frame: .zero) + + if let image = image { + imageView.image = image + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: image.size.width), + imageView.heightAnchor.constraint(equalToConstant: image.size.height) + ]) + stackView.addArrangedSubview(imageView) + } + + messageLabel.text = message + stackView.addArrangedSubview(messageLabel) + + stackView.layoutIfNeeded() + layer.cornerRadius = Constants.cornerRadius + layer.masksToBounds = true + registerThemeServiceDidChangeThemeNotification() + themeDidChange() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, + selector: #selector(themeDidChange), + name: .themeServiceDidChangeTheme, + object: nil) + } + + @objc + private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + // MARK: Themable + + func update(theme: Theme) { + backgroundColor = theme.colors.quinaryContent + imageView.tintColor = theme.colors.tertiaryContent + messageLabel.textColor = theme.colors.primaryContent + messageLabel.font = theme.fonts.body + } + +} diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index cd3786e3d..4248aeeaf 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -20,6 +20,8 @@ internal typealias AssetImageTypeAlias = ImageAsset.Image // swiftlint:disable identifier_name line_length nesting type_body_length type_name internal enum Asset { internal enum Images { + internal static let analyticsCheckmark = ImageAsset(name: "AnalyticsCheckmark") + internal static let analyticsLogo = ImageAsset(name: "AnalyticsLogo") internal static let socialLoginButtonApple = ImageAsset(name: "social_login_button_apple") internal static let socialLoginButtonFacebook = ImageAsset(name: "social_login_button_facebook") internal static let socialLoginButtonGithub = ImageAsset(name: "social_login_button_github") @@ -109,11 +111,20 @@ internal enum Asset { internal static let cameraStop = ImageAsset(name: "camera_stop") internal static let cameraVideoCapture = ImageAsset(name: "camera_video_capture") internal static let videoIcon = ImageAsset(name: "video_icon") + internal static let onboardingSplashScreenPage1 = ImageAsset(name: "OnboardingSplashScreenPage1") + internal static let onboardingSplashScreenPage1Dark = ImageAsset(name: "OnboardingSplashScreenPage1Dark") + internal static let onboardingSplashScreenPage2 = ImageAsset(name: "OnboardingSplashScreenPage2") + internal static let onboardingSplashScreenPage2Dark = ImageAsset(name: "OnboardingSplashScreenPage2Dark") + internal static let onboardingSplashScreenPage3 = ImageAsset(name: "OnboardingSplashScreenPage3") + internal static let onboardingSplashScreenPage3Dark = ImageAsset(name: "OnboardingSplashScreenPage3Dark") + internal static let onboardingSplashScreenPage4 = ImageAsset(name: "OnboardingSplashScreenPage4") + internal static let onboardingSplashScreenPage4Dark = ImageAsset(name: "OnboardingSplashScreenPage4Dark") internal static let peopleEmptyScreenArtwork = ImageAsset(name: "people_empty_screen_artwork") internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark") internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action") internal static let actionCamera = ImageAsset(name: "action_camera") internal static let actionFile = ImageAsset(name: "action_file") + internal static let actionLocation = ImageAsset(name: "action_location") internal static let actionMediaLibrary = ImageAsset(name: "action_media_library") internal static let actionPoll = ImageAsset(name: "action_poll") internal static let actionSticker = ImageAsset(name: "action_sticker") @@ -133,6 +144,7 @@ internal enum Asset { internal static let roomContextMenuMore = ImageAsset(name: "room_context_menu_more") internal static let roomContextMenuReply = ImageAsset(name: "room_context_menu_reply") internal static let roomContextMenuRetry = ImageAsset(name: "room_context_menu_retry") + internal static let roomContextMenuThread = ImageAsset(name: "room_context_menu_thread") internal static let inputCloseIcon = ImageAsset(name: "input_close_icon") internal static let inputEditIcon = ImageAsset(name: "input_edit_icon") internal static let inputReplyIcon = ImageAsset(name: "input_reply_icon") @@ -144,13 +156,21 @@ internal enum Asset { internal static let videoCall = ImageAsset(name: "video_call") internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon") internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") + internal static let locationMarkerIcon = ImageAsset(name: "location_marker_icon") + internal static let locationShareIcon = ImageAsset(name: "location_share_icon") + internal static let locationUserMarker = ImageAsset(name: "location_user_marker") internal static let pollCheckboxDefault = ImageAsset(name: "poll_checkbox_default") internal static let pollCheckboxSelected = ImageAsset(name: "poll_checkbox_selected") internal static let pollDeleteIcon = ImageAsset(name: "poll_delete_icon") internal static let pollDeleteOptionIcon = ImageAsset(name: "poll_delete_option_icon") internal static let pollEditIcon = ImageAsset(name: "poll_edit_icon") internal static let pollEndIcon = ImageAsset(name: "poll_end_icon") + internal static let pollTypeCheckboxDefault = ImageAsset(name: "poll_type_checkbox_default") + internal static let pollTypeCheckboxSelected = ImageAsset(name: "poll_type_checkbox_selected") internal static let pollWinnerIcon = ImageAsset(name: "poll_winner_icon") + internal static let threadsFilter = ImageAsset(name: "threads_filter") + internal static let threadsFilterApplied = ImageAsset(name: "threads_filter_applied") + internal static let threadsIcon = ImageAsset(name: "threads_icon") internal static let urlPreviewClose = ImageAsset(name: "url_preview_close") internal static let urlPreviewCloseDark = ImageAsset(name: "url_preview_close_dark") internal static let voiceMessageCancelGradient = ImageAsset(name: "voice_message_cancel_gradient") @@ -167,7 +187,9 @@ internal enum Asset { internal static let addParticipants = ImageAsset(name: "add_participants") internal static let detailsIcon = ImageAsset(name: "details_icon") internal static let editIcon = ImageAsset(name: "edit_icon") + internal static let fileAttachment = ImageAsset(name: "file_attachment") internal static let integrationsIcon = ImageAsset(name: "integrations_icon") + internal static let linkIcon = ImageAsset(name: "link_icon") internal static let mainAliasIcon = ImageAsset(name: "main_alias_icon") internal static let membersListIcon = ImageAsset(name: "members_list_icon") internal static let modIcon = ImageAsset(name: "mod_icon") @@ -199,15 +221,27 @@ internal enum Asset { internal static let sideMenuNotifIcon = ImageAsset(name: "side_menu_notif_icon") internal static let featureUnavaibleArtwork = ImageAsset(name: "feature_unavaible_artwork") internal static let featureUnavaibleArtworkDark = ImageAsset(name: "feature_unavaible_artwork_dark") - internal static let spaceHomeIcon = ImageAsset(name: "space_home_icon") + internal static let spaceAddRoom = ImageAsset(name: "space_add_room") + internal static let spaceCreationCamera = ImageAsset(name: "space_creation_camera") + internal static let spaceCreationPrivate = ImageAsset(name: "space_creation_private") + internal static let spaceCreationPublic = ImageAsset(name: "space_creation_public") + internal static let spaceHomeIconDark = ImageAsset(name: "space_home_icon_dark") + internal static let spaceHomeIconLight = ImageAsset(name: "space_home_icon_light") + internal static let spaceInviteUser = ImageAsset(name: "space_invite_user") internal static let spaceMenuClose = ImageAsset(name: "space_menu_close") internal static let spaceMenuLeave = ImageAsset(name: "space_menu_leave") internal static let spaceMenuMembers = ImageAsset(name: "space_menu_members") + internal static let spaceMenuPlusIcon = ImageAsset(name: "space_menu_plus_icon") internal static let spaceMenuRooms = ImageAsset(name: "space_menu_rooms") internal static let spacePrivateIcon = ImageAsset(name: "space_private_icon") internal static let spaceRoomIcon = ImageAsset(name: "space_room_icon") internal static let spaceTypeIcon = ImageAsset(name: "space_type_icon") internal static let spaceUserIcon = ImageAsset(name: "space_user_icon") + internal static let spacesAddSpaceDark = ImageAsset(name: "spaces_add_space_dark") + internal static let spacesAddSpaceLight = ImageAsset(name: "spaces_add_space_light") + internal static let spacesInviteUsers = ImageAsset(name: "spaces_invite_users") + internal static let spacesModalBack = ImageAsset(name: "spaces_modal_back") + internal static let spacesModalClose = ImageAsset(name: "spaces_modal_close") internal static let spacesMore = ImageAsset(name: "spaces_more") internal static let tabFavourites = ImageAsset(name: "tab_favourites") internal static let tabGroups = ImageAsset(name: "tab_groups") diff --git a/Riot/Generated/MatrixKitStrings.swift b/Riot/Generated/MatrixKitStrings.swift index e5c1e7f31..b178f6d8d 100644 --- a/Riot/Generated/MatrixKitStrings.swift +++ b/Riot/Generated/MatrixKitStrings.swift @@ -695,6 +695,10 @@ public class MatrixKitL10n: NSObject { public static var messageReplyToSenderSentAnImage: String { return MatrixKitL10n.tr("message_reply_to_sender_sent_an_image") } + /// has shared their location. + public static var messageReplyToSenderSentTheirLocation: String { + return MatrixKitL10n.tr("message_reply_to_sender_sent_their_location") + } /// There are unsaved changes. Leaving will discard them. public static var messageUnsavedChanges: String { return MatrixKitL10n.tr("message_unsaved_changes") diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 34615dd94..ada7e6b05 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -147,6 +147,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: MajorUpdateViewController.self) } + internal enum OptionListViewController: StoryboardType { + internal static let storyboardName = "OptionListViewController" + + internal static let initialScene = InitialSceneType(storyboard: OptionListViewController.self) + } internal enum QRCodeReaderViewController: StoryboardType { internal static let storyboardName = "QRCodeReaderViewController" @@ -279,6 +284,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: TemplateScreenViewController.self) } + internal enum ThreadListViewController: StoryboardType { + internal static let storyboardName = "ThreadListViewController" + + internal static let initialScene = InitialSceneType(storyboard: ThreadListViewController.self) + } internal enum UserVerificationSessionStatusViewController: StoryboardType { internal static let storyboardName = "UserVerificationSessionStatusViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index fd2300081..31d79cac7 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -15,6 +15,10 @@ public class VectorL10n: NSObject { public static var accept: String { return VectorL10n.tr("Vector", "accept") } + /// button + public static var accessibilityButtonLabel: String { + return VectorL10n.tr("Vector", "accessibility_button_label") + } /// checkbox public static var accessibilityCheckboxLabel: String { return VectorL10n.tr("Vector", "accessibility_checkbox_label") @@ -31,6 +35,58 @@ public class VectorL10n: NSObject { public static func activeCallDetails(_ p1: String) -> String { return VectorL10n.tr("Vector", "active_call_details", p1) } + /// Help us identify issues and improve %@ by sharing anonymous usage data. To understand how people use multiple devices, we’ll generate a random identifier, shared by your devices. + public static func analyticsPromptMessageNewUser(_ p1: String) -> String { + return VectorL10n.tr("Vector", "analytics_prompt_message_new_user", p1) + } + /// You previously consented to share anonymous usage data with us. Now, to help understand how people use multiple devices, we’ll generate a random identifier, shared by your devices. + public static var analyticsPromptMessageUpgrade: String { + return VectorL10n.tr("Vector", "analytics_prompt_message_upgrade") + } + /// Not now + public static var analyticsPromptNotNow: String { + return VectorL10n.tr("Vector", "analytics_prompt_not_now") + } + /// We don't record or profile any account data + public static var analyticsPromptPoint1: String { + return VectorL10n.tr("Vector", "analytics_prompt_point_1") + } + /// We don't share information with third parties + public static var analyticsPromptPoint2: String { + return VectorL10n.tr("Vector", "analytics_prompt_point_2") + } + /// You can turn this off anytime in settings + public static var analyticsPromptPoint3: String { + return VectorL10n.tr("Vector", "analytics_prompt_point_3") + } + /// Stop sharing + public static var analyticsPromptStop: String { + return VectorL10n.tr("Vector", "analytics_prompt_stop") + } + /// here + public static var analyticsPromptTermsLinkNewUser: String { + return VectorL10n.tr("Vector", "analytics_prompt_terms_link_new_user") + } + /// here + public static var analyticsPromptTermsLinkUpgrade: String { + return VectorL10n.tr("Vector", "analytics_prompt_terms_link_upgrade") + } + /// You can read all our terms %@. + public static func analyticsPromptTermsNewUser(_ p1: String) -> String { + return VectorL10n.tr("Vector", "analytics_prompt_terms_new_user", p1) + } + /// Read all our terms %@. Is that OK? + public static func analyticsPromptTermsUpgrade(_ p1: String) -> String { + return VectorL10n.tr("Vector", "analytics_prompt_terms_upgrade", p1) + } + /// Help improve %@ + public static func analyticsPromptTitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "analytics_prompt_title", p1) + } + /// Yes, that's fine + public static var analyticsPromptYes: String { + return VectorL10n.tr("Vector", "analytics_prompt_yes") + } /// Please review and accept the policies of this homeserver: public static var authAcceptPolicies: String { return VectorL10n.tr("Vector", "auth_accept_policies") @@ -1235,6 +1291,10 @@ public class VectorL10n: NSObject { public static var emojiPickerTitle: String { return VectorL10n.tr("Vector", "emoji_picker_title") } + /// Enable + public static var enable: String { + return VectorL10n.tr("Vector", "enable") + } /// Send an encrypted message… public static var encryptedRoomMessagePlaceholder: String { return VectorL10n.tr("Vector", "encrypted_room_message_placeholder") @@ -1359,6 +1419,10 @@ public class VectorL10n: NSObject { public static func eventFormatterMemberUpdates(_ p1: Int) -> String { return VectorL10n.tr("Vector", "event_formatter_member_updates", p1) } + /// Message deleted + public static var eventFormatterMessageDeleted: String { + return VectorL10n.tr("Vector", "event_formatter_message_deleted") + } /// (edited) public static var eventFormatterMessageEditedMention: String { return VectorL10n.tr("Vector", "event_formatter_message_edited_mention") @@ -1439,10 +1503,6 @@ public class VectorL10n: NSObject { public static var gdprConsentNotGivenAlertReviewNowAction: String { return VectorL10n.tr("Vector", "gdpr_consent_not_given_alert_review_now_action") } - /// Would you like to help improve %@ by automatically reporting anonymous crash reports and usage data? - public static func googleAnalyticsUsePrompt(_ p1: String) -> String { - return VectorL10n.tr("Vector", "google_analytics_use_prompt", p1) - } /// Home public static var groupDetailsHome: String { return VectorL10n.tr("Vector", "group_details_home") @@ -1539,6 +1599,46 @@ public class VectorL10n: NSObject { public static var groupSection: String { return VectorL10n.tr("Vector", "group_section") } + /// Favourite + public static var homeContextMenuFavourite: String { + return VectorL10n.tr("Vector", "home_context_menu_favourite") + } + /// Leave + public static var homeContextMenuLeave: String { + return VectorL10n.tr("Vector", "home_context_menu_leave") + } + /// Low priority + public static var homeContextMenuLowPriority: String { + return VectorL10n.tr("Vector", "home_context_menu_low_priority") + } + /// Move to People + public static var homeContextMenuMakeDm: String { + return VectorL10n.tr("Vector", "home_context_menu_make_dm") + } + /// Move to Rooms + public static var homeContextMenuMakeRoom: String { + return VectorL10n.tr("Vector", "home_context_menu_make_room") + } + /// Mute + public static var homeContextMenuMute: String { + return VectorL10n.tr("Vector", "home_context_menu_mute") + } + /// Normal priority + public static var homeContextMenuNormalPriority: String { + return VectorL10n.tr("Vector", "home_context_menu_normal_priority") + } + /// Notifications + public static var homeContextMenuNotifications: String { + return VectorL10n.tr("Vector", "home_context_menu_notifications") + } + /// Remove from Favourites + public static var homeContextMenuUnfavourite: String { + return VectorL10n.tr("Vector", "home_context_menu_unfavourite") + } + /// Unmute + public static var homeContextMenuUnmute: String { + return VectorL10n.tr("Vector", "home_context_menu_unmute") + } /// The all-in-one secure chat app for teams, friends and organisations. Tap the + button below to add people and rooms. public static var homeEmptyViewInformation: String { return VectorL10n.tr("Vector", "home_empty_view_information") @@ -2135,6 +2235,62 @@ public class VectorL10n: NSObject { public static var less: String { return VectorL10n.tr("Vector", "less") } + /// Close + public static var locationSharingCloseAction: String { + return VectorL10n.tr("Vector", "location_sharing_close_action") + } + /// %@ does not have permission to access your location. You can enable access in Settings > Location + public static func locationSharingInvalidAuthorizationErrorTitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_error_title", p1) + } + /// Not now + public static var locationSharingInvalidAuthorizationNotNow: String { + return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_not_now") + } + /// Settings + public static var locationSharingInvalidAuthorizationSettings: String { + return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_settings") + } + /// %@ could not load the map. Please try again later. + public static func locationSharingLoadingMapErrorTitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "location_sharing_loading_map_error_title", p1) + } + /// %@ could not access your location. Please try again later. + public static func locationSharingLocatingUserErrorTitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "location_sharing_locating_user_error_title", p1) + } + /// Open in Apple Maps + public static var locationSharingOpenAppleMaps: String { + return VectorL10n.tr("Vector", "location_sharing_open_apple_maps") + } + /// Open in Google Maps + public static var locationSharingOpenGoogleMaps: String { + return VectorL10n.tr("Vector", "location_sharing_open_google_maps") + } + /// %@ could not send your location. Please try again later. + public static func locationSharingPostFailureSubtitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "location_sharing_post_failure_subtitle", p1) + } + /// We couldn’t send your location + public static var locationSharingPostFailureTitle: String { + return VectorL10n.tr("Vector", "location_sharing_post_failure_title") + } + /// Location sharing + public static var locationSharingSettingsHeader: String { + return VectorL10n.tr("Vector", "location_sharing_settings_header") + } + /// Enable location sharing + public static var locationSharingSettingsToggleTitle: String { + return VectorL10n.tr("Vector", "location_sharing_settings_toggle_title") + } + /// Share + public static var locationSharingShareAction: String { + return VectorL10n.tr("Vector", "location_sharing_share_action") + } + /// Location + public static var locationSharingTitle: String { + return VectorL10n.tr("Vector", "location_sharing_title") + } /// Got it public static var majorUpdateDoneAction: String { return VectorL10n.tr("Vector", "major_update_done_action") @@ -2211,6 +2367,10 @@ public class VectorL10n: NSObject { public static var mediaTypeAccessibilityVideo: String { return VectorL10n.tr("Vector", "media_type_accessibility_video") } + /// From a thread + public static var messageFromAThread: String { + return VectorL10n.tr("Vector", "message_from_a_thread") + } /// More public static var more: String { return VectorL10n.tr("Vector", "more") @@ -2235,10 +2395,54 @@ public class VectorL10n: NSObject { public static var off: String { return VectorL10n.tr("Vector", "off") } + /// OK + public static var ok: String { + return VectorL10n.tr("Vector", "ok") + } /// On public static var on: String { return VectorL10n.tr("Vector", "on") } + /// I already have an account + public static var onboardingSplashLoginButtonTitle: String { + return VectorL10n.tr("Vector", "onboarding_splash_login_button_title") + } + /// Secure and independent communication that gives you the same level of privacy as a face-to-face conversation in your own home. + public static var onboardingSplashPage1Message: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_1_message") + } + /// Own your conversations. + public static var onboardingSplashPage1Title: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_1_title") + } + /// Choose where your conversations are kept, giving you control and independence. Connected via Matrix. + public static var onboardingSplashPage2Message: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_2_message") + } + /// You’re in control. + public static var onboardingSplashPage2Title: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_2_title") + } + /// End-to-end encrypted and no phone number required. No ads or datamining. + public static var onboardingSplashPage3Message: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_3_message") + } + /// Secure messaging. + public static var onboardingSplashPage3Title: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_3_title") + } + /// Element is also great for the workplace. It’s trusted by the world’s most secure organisations. + public static var onboardingSplashPage4Message: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_4_message") + } + /// Messaging for your team. + public static var onboardingSplashPage4TitleNoPun: String { + return VectorL10n.tr("Vector", "onboarding_splash_page_4_title_no_pun") + } + /// Create account + public static var onboardingSplashRegisterButtonTitle: String { + return VectorL10n.tr("Vector", "onboarding_splash_register_button_title") + } /// Open public static var `open`: String { return VectorL10n.tr("Vector", "open") @@ -2387,9 +2591,25 @@ public class VectorL10n: NSObject { public static var pollEditFormPollQuestionOrTopic: String { return VectorL10n.tr("Vector", "poll_edit_form_poll_question_or_topic") } - /// OK - public static var pollEditFormPostFailureAction: String { - return VectorL10n.tr("Vector", "poll_edit_form_post_failure_action") + /// Poll type + public static var pollEditFormPollType: String { + return VectorL10n.tr("Vector", "poll_edit_form_poll_type") + } + /// Closed poll + public static var pollEditFormPollTypeClosed: String { + return VectorL10n.tr("Vector", "poll_edit_form_poll_type_closed") + } + /// Results are only revealed when you end the poll + public static var pollEditFormPollTypeClosedDescription: String { + return VectorL10n.tr("Vector", "poll_edit_form_poll_type_closed_description") + } + /// Open poll + public static var pollEditFormPollTypeOpen: String { + return VectorL10n.tr("Vector", "poll_edit_form_poll_type_open") + } + /// Voters see results as soon as they have voted + public static var pollEditFormPollTypeOpenDescription: String { + return VectorL10n.tr("Vector", "poll_edit_form_poll_type_open_description") } /// Please try again public static var pollEditFormPostFailureSubtitle: String { @@ -2403,9 +2623,13 @@ public class VectorL10n: NSObject { public static var pollEditFormQuestionOrTopic: String { return VectorL10n.tr("Vector", "poll_edit_form_question_or_topic") } - /// OK - public static var pollTimelineNotClosedAction: String { - return VectorL10n.tr("Vector", "poll_timeline_not_closed_action") + /// Please try again + public static var pollEditFormUpdateFailureSubtitle: String { + return VectorL10n.tr("Vector", "poll_edit_form_update_failure_subtitle") + } + /// Failed to update poll + public static var pollEditFormUpdateFailureTitle: String { + return VectorL10n.tr("Vector", "poll_edit_form_update_failure_title") } /// Please try again public static var pollTimelineNotClosedSubtitle: String { @@ -2447,10 +2671,6 @@ public class VectorL10n: NSObject { public static func pollTimelineTotalVotesNotVoted(_ p1: Int) -> String { return VectorL10n.tr("Vector", "poll_timeline_total_votes_not_voted", p1) } - /// OK - public static var pollTimelineVoteNotRegisteredAction: String { - return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_action") - } /// Sorry, your vote was not registered, please try again public static var pollTimelineVoteNotRegisteredSubtitle: String { return VectorL10n.tr("Vector", "poll_timeline_vote_not_registered_subtitle") @@ -2467,6 +2687,14 @@ public class VectorL10n: NSObject { public static var preview: String { return VectorL10n.tr("Vector", "preview") } + /// Private + public static var `private`: String { + return VectorL10n.tr("Vector", "private") + } + /// Public + public static var `public`: String { + return VectorL10n.tr("Vector", "public") + } /// Public Rooms (at %@): public static func publicRoomSectionTitle(_ p1: String) -> String { return VectorL10n.tr("Vector", "public_room_section_title", p1) @@ -2523,6 +2751,14 @@ public class VectorL10n: NSObject { public static var roomAccessibilitySearch: String { return VectorL10n.tr("Vector", "room_accessibility_search") } + /// More + public static var roomAccessibilityThreadMore: String { + return VectorL10n.tr("Vector", "room_accessibility_thread_more") + } + /// Threads + public static var roomAccessibilityThreads: String { + return VectorL10n.tr("Vector", "room_accessibility_threads") + } /// Upload public static var roomAccessibilityUpload: String { return VectorL10n.tr("Vector", "room_accessibility_upload") @@ -3003,7 +3239,7 @@ public class VectorL10n: NSObject { public static var roomEventActionMore: String { return VectorL10n.tr("Vector", "room_event_action_more") } - /// Permalink + /// Copy link to message public static var roomEventActionPermalink: String { return VectorL10n.tr("Vector", "room_event_action_permalink") } @@ -3035,6 +3271,10 @@ public class VectorL10n: NSObject { public static var roomEventActionReply: String { return VectorL10n.tr("Vector", "room_event_action_reply") } + /// Thread + public static var roomEventActionReplyInThread: String { + return VectorL10n.tr("Vector", "room_event_action_reply_in_thread") + } /// Report content public static var roomEventActionReport: String { return VectorL10n.tr("Vector", "room_event_action_report") @@ -3067,10 +3307,18 @@ public class VectorL10n: NSObject { public static var roomEventActionViewEncryption: String { return VectorL10n.tr("Vector", "room_event_action_view_encryption") } + /// View in room + public static var roomEventActionViewInRoom: String { + return VectorL10n.tr("Vector", "room_event_action_view_in_room") + } /// View Source public static var roomEventActionViewSource: String { return VectorL10n.tr("Vector", "room_event_action_view_source") } + /// Link copied to clipboard. + public static var roomEventCopyLinkInfo: String { + return VectorL10n.tr("Vector", "room_event_copy_link_info") + } /// Failed to send public static var roomEventFailedToSend: String { return VectorL10n.tr("Vector", "room_event_failed_to_send") @@ -3127,6 +3375,26 @@ public class VectorL10n: NSObject { public static var roomIntroCellInformationRoomWithoutTopicSentence2Part2: String { return VectorL10n.tr("Vector", "room_intro_cell_information_room_without_topic_sentence2_part2") } + /// You do not have permission to invite people to this room + public static var roomInviteNotEnoughPermission: String { + return VectorL10n.tr("Vector", "room_invite_not_enough_permission") + } + /// They won’t be a part of %@. + public static func roomInviteToRoomOptionDetail(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_invite_to_room_option_detail", p1) + } + /// To just this room + public static var roomInviteToRoomOptionTitle: String { + return VectorL10n.tr("Vector", "room_invite_to_room_option_title") + } + /// They can explore %@, but won’t be a member of %@. + public static func roomInviteToSpaceOptionDetail(_ p1: String, _ p2: String) -> String { + return VectorL10n.tr("Vector", "room_invite_to_space_option_detail", p1, p2) + } + /// To %@ + public static func roomInviteToSpaceOptionTitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "room_invite_to_space_option_title", p1) + } /// Join public static var roomJoinGroupCall: String { return VectorL10n.tr("Vector", "room_join_group_call") @@ -3407,6 +3675,10 @@ public class VectorL10n: NSObject { public static var roomParticipantsInvitePromptTitle: String { return VectorL10n.tr("Vector", "room_participants_invite_prompt_title") } + /// Are you sure you want to invite %@ to %@? + public static func roomParticipantsInvitePromptToMsg(_ p1: String, _ p2: String) -> String { + return VectorL10n.tr("Vector", "room_participants_invite_prompt_to_msg", p1, p2) + } /// INVITED public static var roomParticipantsInvitedSection: String { return VectorL10n.tr("Vector", "room_participants_invited_section") @@ -3635,6 +3907,10 @@ public class VectorL10n: NSObject { public static var roomSlideToEndGroupCall: String { return VectorL10n.tr("Vector", "room_slide_to_end_group_call") } + /// Thread + public static var roomThreadTitle: String { + return VectorL10n.tr("Vector", "room_thread_title") + } /// Invite members public static var roomTitleInviteMembers: String { return VectorL10n.tr("Vector", "room_title_invite_members") @@ -4227,6 +4503,10 @@ public class VectorL10n: NSObject { public static var settingsAdvanced: String { return VectorL10n.tr("Vector", "settings_advanced") } + /// Send crash and analytics data + public static var settingsAnalyticsAndCrashData: String { + return VectorL10n.tr("Vector", "settings_analytics_and_crash_data") + } /// Call invitations public static var settingsCallInvitations: String { return VectorL10n.tr("Vector", "settings_call_invitations") @@ -4439,6 +4719,10 @@ public class VectorL10n: NSObject { public static var settingsEnableRageshake: String { return VectorL10n.tr("Vector", "settings_enable_rageshake") } + /// Message bubbles + public static var settingsEnableRoomMessageBubbles: String { + return VectorL10n.tr("Vector", "settings_enable_room_message_bubbles") + } /// Encrypted direct messages public static var settingsEncryptedDirectMessages: String { return VectorL10n.tr("Vector", "settings_encrypted_direct_messages") @@ -4615,6 +4899,10 @@ public class VectorL10n: NSObject { public static var settingsLabsEnableRingingForGroupCalls: String { return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls") } + /// Threaded messaging + public static var settingsLabsEnableThreads: String { + return VectorL10n.tr("Vector", "settings_labs_enable_threads") + } /// Polls public static var settingsLabsEnabledPolls: String { return VectorL10n.tr("Vector", "settings_labs_enabled_polls") @@ -4755,10 +5043,6 @@ public class VectorL10n: NSObject { public static var settingsSecurity: String { return VectorL10n.tr("Vector", "settings_security") } - /// Send anon crash & usage data - public static var settingsSendCrashReport: String { - return VectorL10n.tr("Vector", "settings_send_crash_report") - } /// SENDING IMAGES AND VIDEOS public static var settingsSendingMedia: String { return VectorL10n.tr("Vector", "settings_sending_media") @@ -4895,6 +5179,18 @@ public class VectorL10n: NSObject { public static var shareExtensionSendNow: String { return VectorL10n.tr("Vector", "share_extension_send_now") } + /// Share invite link + public static var shareInviteLinkAction: String { + return VectorL10n.tr("Vector", "share_invite_link_action") + } + /// Hey, join this room on %@ + public static func shareInviteLinkRoomText(_ p1: String) -> String { + return VectorL10n.tr("Vector", "share_invite_link_room_text", p1) + } + /// Hey, join this space on %@ + public static func shareInviteLinkSpaceText(_ p1: String) -> String { + return VectorL10n.tr("Vector", "share_invite_link_space_text", p1) + } /// Feedback public static var sideMenuActionFeedback: String { return VectorL10n.tr("Vector", "side_menu_action_feedback") @@ -5039,6 +5335,10 @@ public class VectorL10n: NSObject { public static var spaceHomeShowAllRooms: String { return VectorL10n.tr("Vector", "space_home_show_all_rooms") } + /// You do not have permission to invite people to this space + public static var spaceInviteNotEnoughPermission: String { + return VectorL10n.tr("Vector", "space_invite_not_enough_permission") + } /// Ban from this space public static var spaceParticipantsActionBan: String { return VectorL10n.tr("Vector", "space_participants_action_ban") @@ -5051,26 +5351,234 @@ public class VectorL10n: NSObject { public static var spacePrivateJoinRule: String { return VectorL10n.tr("Vector", "space_private_join_rule") } + /// Invite only, best for yourself or teams + public static var spacePrivateJoinRuleDetail: String { + return VectorL10n.tr("Vector", "space_private_join_rule_detail") + } /// Public space public static var spacePublicJoinRule: String { return VectorL10n.tr("Vector", "space_public_join_rule") } + /// Open to anyone, best for communities + public static var spacePublicJoinRuleDetail: String { + return VectorL10n.tr("Vector", "space_public_join_rule_detail") + } /// space public static var spaceTag: String { return VectorL10n.tr("Vector", "space_tag") } + /// description + public static var spaceTopic: String { + return VectorL10n.tr("Vector", "space_topic") + } + /// Add room + public static var spacesAddRoom: String { + return VectorL10n.tr("Vector", "spaces_add_room") + } /// Adding rooms coming soon public static var spacesAddRoomsComingSoonTitle: String { return VectorL10n.tr("Vector", "spaces_add_rooms_coming_soon_title") } - /// This feature hasn’t been implemented here, but it’s on the way. For now, you can do that with Element on your computer. - public static var spacesComingSoonDetail: String { - return VectorL10n.tr("Vector", "spaces_coming_soon_detail") + /// Add space + public static var spacesAddSpace: String { + return VectorL10n.tr("Vector", "spaces_add_space") + } + /// Create space + public static var spacesAddSpaceTitle: String { + return VectorL10n.tr("Vector", "spaces_add_space_title") + } + /// This feature hasn’t been implemented here, but it’s on the way. For now, you can do that with %@ on your computer. + public static func spacesComingSoonDetail(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_coming_soon_detail", p1) } /// Coming soon public static var spacesComingSoonTitle: String { return VectorL10n.tr("Vector", "spaces_coming_soon_title") } + /// Create a space + public static var spacesCreateSpaceTitle: String { + return VectorL10n.tr("Vector", "spaces_create_space_title") + } + /// As this space is just for you, no one will be informed. You can add more later. + public static var spacesCreationAddRoomsMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_add_rooms_message") + } + /// What do you want to add? + public static var spacesCreationAddRoomsTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_add_rooms_title") + } + /// Address + public static var spacesCreationAddress: String { + return VectorL10n.tr("Vector", "spaces_creation_address") + } + /// %@\nalready exists + public static func spacesCreationAddressAlreadyExists(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_address_already_exists", p1) + } + /// Your space will be viewable at\n%@ + public static func spacesCreationAddressDefaultMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_address_default_message", p1) + } + /// %@\nhas invalid characters + public static func spacesCreationAddressInvalidCharacters(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_address_invalid_characters", p1) + } + /// Your progress will be lost. + public static var spacesCreationCancelMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_cancel_message") + } + /// Stop creating a space? + public static var spacesCreationCancelTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_cancel_title") + } + /// Email + public static var spacesCreationEmailInvitesEmailTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_email_invites_email_title") + } + /// You can invite them later too. + public static var spacesCreationEmailInvitesMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_email_invites_message") + } + /// Invite your team + public static var spacesCreationEmailInvitesTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_email_invites_title") + } + /// Name required + public static var spacesCreationEmptyRoomNameError: String { + return VectorL10n.tr("Vector", "spaces_creation_empty_room_name_error") + } + /// You can change this later + public static var spacesCreationFooter: String { + return VectorL10n.tr("Vector", "spaces_creation_footer") + } + /// Spaces are a new way to group rooms and people. + public static var spacesCreationHint: String { + return VectorL10n.tr("Vector", "spaces_creation_hint") + } + /// in %@ spaces + public static func spacesCreationInManySpaces(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_in_many_spaces", p1) + } + /// in 1 space + public static var spacesCreationInOneSpace: String { + return VectorL10n.tr("Vector", "spaces_creation_in_one_space") + } + /// in %@ + public static func spacesCreationInSpacename(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_in_spacename", p1) + } + /// in %@ + %@ spaces + public static func spacesCreationInSpacenamePlusMany(_ p1: String, _ p2: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_in_spacename_plus_many", p1, p2) + } + /// in %@ + 1 space + public static func spacesCreationInSpacenamePlusOne(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_in_spacename_plus_one", p1) + } + /// Invite by username + public static var spacesCreationInviteByUsername: String { + return VectorL10n.tr("Vector", "spaces_creation_invite_by_username") + } + /// You can invite them later too. + public static var spacesCreationInviteByUsernameMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_invite_by_username_message") + } + /// Invite your team + public static var spacesCreationInviteByUsernameTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_invite_by_username_title") + } + /// General + public static var spacesCreationNewRoomsGeneral: String { + return VectorL10n.tr("Vector", "spaces_creation_new_rooms_general") + } + /// We’ll create a room for each one. + public static var spacesCreationNewRoomsMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_new_rooms_message") + } + /// Random + public static var spacesCreationNewRoomsRandom: String { + return VectorL10n.tr("Vector", "spaces_creation_new_rooms_random") + } + /// Room name + public static var spacesCreationNewRoomsRoomNameTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_new_rooms_room_name_title") + } + /// Support + public static var spacesCreationNewRoomsSupport: String { + return VectorL10n.tr("Vector", "spaces_creation_new_rooms_support") + } + /// What are some discussions you’ll have? + public static var spacesCreationNewRoomsTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_new_rooms_title") + } + /// Adding %@ rooms + public static func spacesCreationPostProcessAddingRooms(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_post_process_adding_rooms", p1) + } + /// Creating %@ + public static func spacesCreationPostProcessCreatingRoom(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_post_process_creating_room", p1) + } + /// Creating space + public static var spacesCreationPostProcessCreatingSpace: String { + return VectorL10n.tr("Vector", "spaces_creation_post_process_creating_space") + } + /// Creating %@ + public static func spacesCreationPostProcessCreatingSpaceTask(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_post_process_creating_space_task", p1) + } + /// Inviting %@ users + public static func spacesCreationPostProcessInvitingUsers(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_post_process_inviting_users", p1) + } + /// Uploading avatar + public static var spacesCreationPostProcessUploadingAvatar: String { + return VectorL10n.tr("Vector", "spaces_creation_post_process_uploading_avatar") + } + /// Your private space + public static var spacesCreationPrivateSpaceTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_private_space_title") + } + /// Your public space + public static var spacesCreationPublicSpaceTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_public_space_title") + } + /// Add some details to help it stand out. You can change these at any point. + public static var spacesCreationSettingsMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_settings_message") + } + /// A private space to organise your rooms + public static var spacesCreationSharingTypeJustMeDetail: String { + return VectorL10n.tr("Vector", "spaces_creation_sharing_type_just_me_detail") + } + /// Just me + public static var spacesCreationSharingTypeJustMeTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_sharing_type_just_me_title") + } + /// A private space for you & your teammates + public static var spacesCreationSharingTypeMeAndTeammatesDetail: String { + return VectorL10n.tr("Vector", "spaces_creation_sharing_type_me_and_teammates_detail") + } + /// Me and teammates + public static var spacesCreationSharingTypeMeAndTeammatesTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_sharing_type_me_and_teammates_title") + } + /// Make sure the right people have access %@. You can change this later. + public static func spacesCreationSharingTypeMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_creation_sharing_type_message", p1) + } + /// Who are you working with? + public static var spacesCreationSharingTypeTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_sharing_type_title") + } + /// To join an existing space, you need an invite. + public static var spacesCreationVisibilityMessage: String { + return VectorL10n.tr("Vector", "spaces_creation_visibility_message") + } + /// What type of space do you want to create? + public static var spacesCreationVisibilityTitle: String { + return VectorL10n.tr("Vector", "spaces_creation_visibility_title") + } /// Some rooms may be hidden because they’re private and you need an invite. public static var spacesEmptySpaceDetail: String { return VectorL10n.tr("Vector", "spaces_empty_space_detail") @@ -5087,6 +5595,10 @@ public class VectorL10n: NSObject { public static var spacesHomeSpaceTitle: String { return VectorL10n.tr("Vector", "spaces_home_space_title") } + /// Invite people + public static var spacesInvitePeople: String { + return VectorL10n.tr("Vector", "spaces_invite_people") + } /// Invites coming soon public static var spacesInvitesComingSoonTitle: String { return VectorL10n.tr("Vector", "spaces_invites_coming_soon_title") @@ -5115,6 +5627,10 @@ public class VectorL10n: NSObject { public static var start: String { return VectorL10n.tr("Vector", "start") } + /// Stop + public static var stop: String { + return VectorL10n.tr("Vector", "stop") + } /// Element is a new type of messenger and collaboration app that:\n\n1. Puts you in control to preserve your privacy\n2. Lets you communicate with anyone in the Matrix network, and even beyond by integrating with apps such as Slack\n3. Protects you from advertising, datamining, backdoors and walled gardens\n4. Secures you through end-to-end encryption, with cross-signing to verify others\n\nElement is completely different from other messaging and collaboration apps because it is decentralised and open source.\n\nElement lets you self-host - or choose a host - so that you have privacy, ownership and control of your data and conversations. It gives you access to an open network; so you’re not just stuck speaking to other Element users only. And it is very secure.\n\nElement is able to do all this because it operates on Matrix - the standard for open, decentralised communication. \n\nElement puts you in control by letting you choose who hosts your conversations. From the Element app, you can choose to host in different ways:\n\n1. Get a free account on the matrix.org public server\n2. Self-host your account by running a server on your own hardware\n3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform\n\nWhy choose Element?\n\nOWN YOUR DATA: You decide where to keep your data and messages. You own it and control it, not some MEGACORP that mines your data or gives access to third parties.\n\nOPEN MESSAGING AND COLLABORATION: You can chat with anyone else in the Matrix network, whether they’re using Element or another Matrix app, and even if they are using a different messaging system of the likes of Slack, IRC or XMPP.\n\nSUPER-SECURE: Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signing to verify the devices of conversation participants.\n\nCOMPLETE COMMUNICATION: Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.\n\nEVERYWHERE YOU ARE: Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://element.io/app. public static var storeFullDescription: String { return VectorL10n.tr("Vector", "store_full_description") @@ -5131,6 +5647,42 @@ public class VectorL10n: NSObject { public static var `switch`: String { return VectorL10n.tr("Vector", "switch") } + /// Copy link to thread + public static var threadCopyLinkToThread: String { + return VectorL10n.tr("Vector", "thread_copy_link_to_thread") + } + /// All threads + public static var threadsActionAllThreads: String { + return VectorL10n.tr("Vector", "threads_action_all_threads") + } + /// My threads + public static var threadsActionMyThreads: String { + return VectorL10n.tr("Vector", "threads_action_my_threads") + } + /// Threads help keep your conversations on-topic and easy to track. + public static var threadsEmptyInfoAll: String { + return VectorL10n.tr("Vector", "threads_empty_info_all") + } + /// Reply to an ongoing thread or tap a message and use “Thread” to start a new one. + public static var threadsEmptyInfoMy: String { + return VectorL10n.tr("Vector", "threads_empty_info_my") + } + /// Show all threads + public static var threadsEmptyShowAllThreads: String { + return VectorL10n.tr("Vector", "threads_empty_show_all_threads") + } + /// Tip: Tap a message and use “Thread” to start one. + public static var threadsEmptyTip: String { + return VectorL10n.tr("Vector", "threads_empty_tip") + } + /// Keep discussions organised with threads + public static var threadsEmptyTitle: String { + return VectorL10n.tr("Vector", "threads_empty_title") + } + /// Threads + public static var threadsTitle: String { + return VectorL10n.tr("Vector", "threads_title") + } /// Favourites public static var titleFavourites: String { return VectorL10n.tr("Vector", "title_favourites") diff --git a/Riot/Managers/Analytics/Analytics.h b/Riot/Managers/Analytics/Analytics.h deleted file mode 100644 index 5ad851929..000000000 --- a/Riot/Managers/Analytics/Analytics.h +++ /dev/null @@ -1,65 +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 - -#import - - -// Metrics related to notifications -FOUNDATION_EXPORT NSString *const AnalyticsNoficationsCategory; -FOUNDATION_EXPORT NSString *const AnalyticsNoficationsTimeToDisplayContent; -/** - The analytics value for accept/decline of the identity server's terms. - */ -FOUNDATION_EXPORT NSString *const AnalyticsContactsIdentityServerAccepted; - - -/** - `Analytics` sends analytics to an analytics tool. - */ -@interface Analytics : NSObject - -/** - Returns the shared Analytics manager. - - @return the shared Analytics manager. - */ -+ (instancetype)sharedInstance; - -/** - Start doing analytics if the settings `enableCrashReport` is enabled. - */ -- (void)start; - -/** - Stop doing analytics. - */ -- (void)stop; - -/** - Track a screen display. - - @param screenName the name of the displayed screen. - */ -- (void)trackScreen:(NSString*)screenName; - -/** - Flush analytics data. - */ -- (void)dispatch; - -@end diff --git a/Riot/Managers/Analytics/Analytics.m b/Riot/Managers/Analytics/Analytics.m deleted file mode 100644 index 6bf7269b8..000000000 --- a/Riot/Managers/Analytics/Analytics.m +++ /dev/null @@ -1,162 +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 "Analytics.h" - -#import "GeneratedInterface-Swift.h" - - -NSString *const AnalyticsNoficationsCategory = @"notifications"; -NSString *const AnalyticsNoficationsTimeToDisplayContent = @"timelineDisplay"; -NSString *const AnalyticsContactsIdentityServerAccepted = @"identityServerAccepted"; - - -// Duration data will be visible under the Piwik category called "Performance". -// Other values will be visible in "Metrics". -// Some Matomo screenshots are available at https://github.com/vector-im/element-ios/pull/3789. -NSString *const kAnalyticsPerformanceCategory = @"Performance"; -NSString *const kAnalyticsMetricsCategory = @"Metrics"; - - -@import MatomoTracker; - -@interface Analytics () -{ - MatomoTracker *matomoTracker; -} - -@end - -@implementation Analytics - -+ (instancetype)sharedInstance -{ - static Analytics *sharedInstance = nil; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - sharedInstance = [[Analytics alloc] init]; - }); - - return sharedInstance; -} - -- (instancetype)init -{ - self = [super init]; - if (self) - { - matomoTracker = [[MatomoTracker alloc] initWithSiteId:BuildSettings.analyticsAppId - baseURL:BuildSettings.analyticsServerUrl - userAgent:@"iOSMatomoTracker"]; - [self migrateFromFourPointFourSharedInstance]; - } - return self; -} - -- (void)migrateFromFourPointFourSharedInstance -{ - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"migratedFromFourPointFourSharedInstance"]) return; - [matomoTracker copyFromOldSharedInstance]; - [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"migratedFromFourPointFourSharedInstance"]; -} - -- (void)start -{ - // Check whether the user has enabled the sending of crash reports. - if (RiotSettings.shared.enableCrashReport) - { - matomoTracker.isOptedOut = NO; - - [matomoTracker setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; - [matomoTracker setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion]; - - // The language is either the one selected by the user within the app - // or, else, the one configured by the OS - NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; - [matomoTracker setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - if (account) - { - [matomoTracker setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; - [matomoTracker setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; - } - - // TODO: We should also track device and os version - // But that needs to be decided for all platforms - - // Catch and log crashes - [MXLogger logCrashes:YES]; - [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; - -#ifdef DEBUG - // Disable analytics in debug as it pollutes stats - matomoTracker.isOptedOut = YES; -#endif - } - else - { - MXLogDebug(@"[AppDelegate] The user decided to not send analytics"); - matomoTracker.isOptedOut = YES; - [MXLogger logCrashes:NO]; - } -} - -- (void)stop -{ - matomoTracker.isOptedOut = YES; - [MXLogger logCrashes:NO]; -} - -- (void)trackScreen:(NSString *)screenName -{ - // Use the same pattern as Android - NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - NSString *appVersion = [AppDelegate theDelegate].appVersion; - - [matomoTracker trackWithView:@[@"ios", appName, appVersion, screenName] - url:nil]; -} - -- (void)dispatch -{ - [matomoTracker dispatch]; -} - -#pragma mark - MXAnalyticsDelegate - -- (void)trackDuration:(NSTimeInterval)seconds category:(NSString*)category name:(NSString*)name -{ - // Report time in ms to make figures look better in Matomo - NSNumber *value = @(seconds * 1000); - [matomoTracker trackWithEventWithCategory:kAnalyticsPerformanceCategory - action:category - name:name - number:value - url:nil]; -} - -- (void)trackValue:(NSNumber*)value category:(NSString*)category name:(NSString*)name -{ - [matomoTracker trackWithEventWithCategory:kAnalyticsMetricsCategory - action:category - name:name - number:value - url:nil]; -} - -@end diff --git a/Riot/Managers/Analytics/DecryptionFailure.h b/Riot/Managers/Analytics/DecryptionFailure.h deleted file mode 100644 index 7ea43962c..000000000 --- a/Riot/Managers/Analytics/DecryptionFailure.h +++ /dev/null @@ -1,51 +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 - -/** - Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI. - */ -struct DecryptionFailureReasonStruct -{ - __unsafe_unretained NSString * const unspecified; - __unsafe_unretained NSString * const olmKeysNotSent; - __unsafe_unretained NSString * const olmIndexError; - __unsafe_unretained NSString * const unexpected; -}; -extern const struct DecryptionFailureReasonStruct DecryptionFailureReason; - -/** - `DecryptionFailure` represents a decryption failure. - */ -@interface DecryptionFailure : NSObject - -/** - The id of the event that was unabled to decrypt. - */ -@property (nonatomic) NSString *failedEventId; - -/** - The time the failure has been reported. - */ -@property (nonatomic, readonly) NSTimeInterval ts; - -/** - Decryption failure reason. - */ -@property (nonatomic) NSString *reason; - -@end diff --git a/Riot/Managers/AppInfo/AppInfo.swift b/Riot/Managers/AppInfo/AppInfo.swift index 457a7af87..fde62d75b 100644 --- a/Riot/Managers/AppInfo/AppInfo.swift +++ b/Riot/Managers/AppInfo/AppInfo.swift @@ -19,17 +19,14 @@ import Foundation /// Used to handle the application information @objcMembers final class AppInfo: NSObject { - + // MARK: - Constants /// Current application information static var current: AppInfo { - let appDisplayName = BuildSettings.bundleDisplayName - let buildInfo: BuildInfo = BuildInfo() - - return AppInfo(displayName: appDisplayName, + return AppInfo(displayName: self.bundleDisplayName, appVersion: AppVersion.current, - buildInfo: buildInfo) + buildInfo: BuildInfo()) } // MARK: - Properties @@ -52,4 +49,11 @@ final class AppInfo: NSObject { self.appVersion = appVersion self.buildInfo = buildInfo } + + private static var bundleDisplayName: String { + guard let bundleDisplayName = Bundle.app.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String else { + fatalError("CFBundleDisplayName should be defined") + } + return bundleDisplayName + } } diff --git a/Riot/Managers/OnBoarding/OnBoardingManager.swift b/Riot/Managers/OnBoarding/OnBoardingManager.swift index c2016019f..dec2287bc 100644 --- a/Riot/Managers/OnBoarding/OnBoardingManager.swift +++ b/Riot/Managers/OnBoarding/OnBoardingManager.swift @@ -81,19 +81,18 @@ final public class OnBoardingManager: NSObject { // MARK: - Private private func isUserJoinedARoom() -> Bool { - guard let roomSummaries = self.session.roomsSummaries() else { - return false - } + var isUserJoinedARoom = false - var isUSerJoinedARoom = false - - for roomSummary in roomSummaries { + for room in session.rooms { + guard let roomSummary = room.summary else { + continue + } if case .join = roomSummary.membership { - isUSerJoinedARoom = true + isUserJoinedARoom = true break } } - return isUSerJoinedARoom + return isUserJoinedARoom } } diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 1a5877758..450e63497 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -23,7 +23,8 @@ final class RiotSettings: NSObject { // MARK: - Constants public enum UserDefaultsKeys { - static let enableCrashReport = "enableCrashReport" + static let enableAnalytics = "enableAnalytics" + static let matomoAnalytics = "enableCrashReport" static let notificationsShowDecryptedContent = "showDecryptedContent" static let allowStunServerFallback = "allowStunServerFallback" static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif" @@ -100,13 +101,31 @@ final class RiotSettings: NSObject { // MARK: Other - /// Indicate if `enableCrashReport` settings has been set once. - var isEnableCrashReportHasBeenSetOnce: Bool { - return RiotSettings.defaults.object(forKey: UserDefaultsKeys.enableCrashReport) != nil + /// Whether the user was previously shown the Matomo analytics prompt. + var hasSeenAnalyticsPrompt: Bool { + RiotSettings.defaults.object(forKey: UserDefaultsKeys.enableAnalytics) != nil } - @UserDefault(key: UserDefaultsKeys.enableCrashReport, defaultValue: false, storage: defaults) - var enableCrashReport + /// Whether the user has both seen the Matomo analytics prompt and declined it. + var hasDeclinedMatomoAnalytics: Bool { + RiotSettings.defaults.object(forKey: UserDefaultsKeys.matomoAnalytics) != nil && !RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics) + } + + /// Whether the user previously accepted the Matomo analytics prompt. + /// This allows these users to be shown a different prompt to explain the changes. + var hasAcceptedMatomoAnalytics: Bool { + RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics) + } + + /// `true` when the user has opted in to send analytics. + @UserDefault(key: UserDefaultsKeys.enableAnalytics, defaultValue: false, storage: defaults) + var enableAnalytics + + /// Indicates if the device has already called identify for this session to PostHog. + /// This is separate to `enableAnalytics` as logging out will leave analytics + /// enabled but reset identification. + @UserDefault(key: "isIdentifiedForAnalytics", defaultValue: false, storage: defaults) + var isIdentifiedForAnalytics @UserDefault(key: "enableRageShake", defaultValue: false, storage: defaults) var enableRageShake @@ -117,6 +136,10 @@ final class RiotSettings: NSObject { @UserDefault(key: "enableRingingForGroupCalls", defaultValue: false, storage: defaults) var enableRingingForGroupCalls + /// Indicates if threads enabled in the timeline. + @UserDefault(key: "enableThreads", defaultValue: false, storage: defaults) + var enableThreads + // MARK: Calls /// Indicate if `allowStunServerFallback` settings has been set once. @@ -165,13 +188,17 @@ final class RiotSettings: NSObject { @UserDefault(key: "roomScreenAllowFilesAction", defaultValue: BuildSettings.roomScreenAllowFilesAction, storage: defaults) var roomScreenAllowFilesAction - - @UserDefault(key: "roomScreenAllowPollsAction", defaultValue: false, storage: defaults) - var roomScreenAllowPollsAction @UserDefault(key: "roomScreenShowsURLPreviews", defaultValue: true, storage: defaults) var roomScreenShowsURLPreviews + @UserDefault(key: "roomScreenEnableMessageBubbles", defaultValue: BuildSettings.isRoomScreenEnableMessageBubblesByDefault, storage: defaults) + var roomScreenEnableMessageBubbles + + var roomTimelineStyleIdentifier: RoomTimelineStyleIdentifier { + return self.roomScreenEnableMessageBubbles ? .bubble : .plain + } + // MARK: - Room Contextual Menu @UserDefault(key: "roomContextualMenuShowMoreOptionForMessages", defaultValue: BuildSettings.roomContextualMenuShowMoreOptionForMessages, storage: defaults) diff --git a/Riot/Managers/Theme/Theme.swift b/Riot/Managers/Theme/Theme.swift index 8c6f52a66..2418cc967 100644 --- a/Riot/Managers/Theme/Theme.swift +++ b/Riot/Managers/Theme/Theme.swift @@ -32,6 +32,7 @@ import DesignKit var searchBackgroundColor: UIColor { get } var searchPlaceholderColor: UIColor { get } + var searchResultHighlightColor: UIColor { get } var headerBackgroundColor: UIColor { get } var headerBorderColor: UIColor { get } @@ -96,16 +97,22 @@ import DesignKit /// Color to use in shadows. Should be contrast to `backgroundColor`. var shadowColor: UIColor { get } + + // Timeline cells + + var roomCellIncomingBubbleBackgroundColor: UIColor { get } + + var roomCellOutgoingBubbleBackgroundColor: UIColor { get } // MARK: - Customisation methods - /// Apply the theme on a button. + /// Apply the theme on a tab bar. /// - /// - Parameter tabBar: The tabBar to customise. + /// - Parameter tabBar: The tab bar to customise. func applyStyle(onTabBar tabBar: UITabBar) - /// Apply the theme on a navigation bar + /// Apply the theme on a navigation bar. /// /// - Parameter navigationBar: the navigation bar to customise. func applyStyle(onNavigationBar navigationBar: UINavigationBar) diff --git a/Riot/Managers/Theme/ThemeService.m b/Riot/Managers/Theme/ThemeService.m index b205bbb6d..9d544ddce 100644 --- a/Riot/Managers/Theme/ThemeService.m +++ b/Riot/Managers/Theme/ThemeService.m @@ -52,7 +52,7 @@ NSString *const kThemeServiceDidChangeThemeNotification = @"kThemeServiceDidChan [self updateAppearance]; - [[NSNotificationCenter defaultCenter] postNotificationName:kThemeServiceDidChangeThemeNotification object:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:kThemeServiceDidChangeThemeNotification object:self]; } } @@ -146,6 +146,12 @@ NSString *const kThemeServiceDidChangeThemeNotification = @"kThemeServiceDidChan { [UIScrollView appearance].indicatorStyle = self.theme.scrollBarStyle; + // Remove the extra height added to section headers in iOS 15 + if (@available(iOS 15.0, *)) + { + UITableView.appearance.sectionHeaderTopPadding = 0; + } + // Define the navigation bar text color [[UINavigationBar appearance] setTintColor:self.theme.tintColor]; diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index b6aabc3ff..ebac661c8 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -33,6 +33,7 @@ class DarkTheme: NSObject, Theme { var searchBackgroundColor: UIColor = UIColor(rgb: 0x15191E) var searchPlaceholderColor: UIColor = UIColor(rgb: 0xA9B2BC) + var searchResultHighlightColor: UIColor = UIColor(rgb: 0xFCC639).withAlphaComponent(0.3) var headerBackgroundColor: UIColor = UIColor(rgb: 0x21262C) var headerBorderColor: UIColor = UIColor(rgb: 0x15191E) @@ -91,25 +92,53 @@ class DarkTheme: NSObject, Theme { var shadowColor: UIColor = UIColor(rgb: 0xFFFFFF) var messageTickColor: UIColor = .white + + var roomCellIncomingBubbleBackgroundColor: UIColor { + return self.colors.system + } + + var roomCellOutgoingBubbleBackgroundColor: UIColor = UIColor(rgb: 0x133A34) func applyStyle(onTabBar tabBar: UITabBar) { tabBar.unselectedItemTintColor = self.tabBarUnselectedItemTintColor tabBar.tintColor = self.tintColor tabBar.barTintColor = self.baseColor - tabBar.isTranslucent = false + + // Support standard scrollEdgeAppearance iOS 15 without visual issues. + if #available(iOS 15.0, *) { + tabBar.isTranslucent = true + } else { + tabBar.isTranslucent = false + } } - // Note: We are not using UINavigationBarAppearance on iOS 13+ atm because of UINavigationBar directly include UISearchBar on their titleView that cause crop issues with UINavigationController pop. + // Note: We are not using UINavigationBarAppearance on iOS 13/14 because of UINavigationBar directly including UISearchBar on their titleView that cause crop issues with UINavigationController pop. func applyStyle(onNavigationBar navigationBar: UINavigationBar) { navigationBar.tintColor = self.tintColor - navigationBar.titleTextAttributes = [ - NSAttributedString.Key.foregroundColor: self.textPrimaryColor - ] - navigationBar.barTintColor = self.baseColor - navigationBar.shadowImage = UIImage() // Remove bottom shadow - - // The navigation bar needs to be opaque so that its background color is the expected one - navigationBar.isTranslucent = false + + // On iOS 15 use UINavigationBarAppearance to fix visual issues with the scrollEdgeAppearance style. + if #available(iOS 15.0, *) { + let appearance = UINavigationBarAppearance() + + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = self.baseColor + appearance.shadowColor = nil + appearance.titleTextAttributes = [ + NSAttributedString.Key.foregroundColor: self.textPrimaryColor + ] + + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + } else { + navigationBar.titleTextAttributes = [ + NSAttributedString.Key.foregroundColor: self.textPrimaryColor + ] + navigationBar.barTintColor = self.baseColor + navigationBar.shadowImage = UIImage() // Remove bottom shadow + + // The navigation bar needs to be opaque so that its background color is the expected one + navigationBar.isTranslucent = false + } } func applyStyle(onSearchBar searchBar: UISearchBar) { diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index 545b801b9..2be798d23 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -33,6 +33,7 @@ class DefaultTheme: NSObject, Theme { var searchBackgroundColor: UIColor = UIColor(rgb: 0xFFFFFF) var searchPlaceholderColor: UIColor = UIColor(rgb: 0x8F97A3) + var searchResultHighlightColor: UIColor = UIColor(rgb: 0xFCC639).withAlphaComponent(0.2) var headerBackgroundColor: UIColor = UIColor(rgb: 0xF5F7FA) var headerBorderColor: UIColor = UIColor(rgb: 0xE9EDF1) @@ -100,24 +101,50 @@ class DefaultTheme: NSObject, Theme { var shadowColor: UIColor = UIColor(rgb: 0x000000) + var roomCellIncomingBubbleBackgroundColor: UIColor = UIColor(rgb: 0xE8EDF4) + + var roomCellOutgoingBubbleBackgroundColor: UIColor = UIColor(rgb: 0xE7F8F3) + func applyStyle(onTabBar tabBar: UITabBar) { tabBar.unselectedItemTintColor = self.tabBarUnselectedItemTintColor tabBar.tintColor = self.tintColor tabBar.barTintColor = self.baseColor - tabBar.isTranslucent = false + + // Support standard scrollEdgeAppearance iOS 15 without visual issues. + if #available(iOS 15.0, *) { + tabBar.isTranslucent = true + } else { + tabBar.isTranslucent = false + } } - // Note: We are not using UINavigationBarAppearance on iOS 13+ atm because of UINavigationBar directly include UISearchBar on their titleView that cause crop issues with UINavigationController pop. + // Note: We are not using UINavigationBarAppearance on iOS 13/14 because of UINavigationBar directly including UISearchBar on their titleView that cause crop issues with UINavigationController pop. func applyStyle(onNavigationBar navigationBar: UINavigationBar) { navigationBar.tintColor = self.tintColor - navigationBar.titleTextAttributes = [ - NSAttributedString.Key.foregroundColor: self.textPrimaryColor - ] - navigationBar.barTintColor = self.baseColor - navigationBar.shadowImage = UIImage() // Remove bottom shadow - - // The navigation bar needs to be opaque so that its background color is the expected one - navigationBar.isTranslucent = false + + // On iOS 15 use UINavigationBarAppearance to fix visual issues with the scrollEdgeAppearance style. + if #available(iOS 15.0, *) { + let appearance = UINavigationBarAppearance() + + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = baseColor + appearance.shadowColor = nil + appearance.titleTextAttributes = [ + NSAttributedString.Key.foregroundColor: textPrimaryColor + ] + + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + } else { + navigationBar.titleTextAttributes = [ + NSAttributedString.Key.foregroundColor: textPrimaryColor + ] + navigationBar.barTintColor = baseColor + navigationBar.shadowImage = UIImage() // Remove bottom shadow + + // The navigation bar needs to be opaque so that its background color is the expected one + navigationBar.isTranslucent = false + } } func applyStyle(onSearchBar searchBar: UISearchBar) { diff --git a/Riot/Managers/UserSessions/UserSessionsService.swift b/Riot/Managers/UserSessions/UserSessionsService.swift index b8618abb0..7a68d9752 100644 --- a/Riot/Managers/UserSessions/UserSessionsService.swift +++ b/Riot/Managers/UserSessions/UserSessionsService.swift @@ -155,7 +155,7 @@ class UserSessionsService: NSObject { let isSessionStateValid: Bool switch mxSession.state { - case .closed, .unknownToken: + case .closed: isSessionStateValid = false default: isSessionStateValid = true diff --git a/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift b/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift index f09d8608c..581133ce8 100644 --- a/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift +++ b/Riot/Model/HomeserverConfiguration/HomeserverConfiguration.swift @@ -16,19 +16,20 @@ import Foundation -/// Represents the homeserver configuration (usually based on HS Well-Known or hardoced values in the project) +/// Represents the homeserver configuration (usually based on HS Well-Known or hardcoded values in the project) @objcMembers final class HomeserverConfiguration: NSObject { // Note: Use an object per configuration subject when there is multiple properties related let jitsi: HomeserverJitsiConfiguration let isE2EEByDefaultEnabled: Bool + let tileServer: HomeserverTileServerConfiguration init(jitsi: HomeserverJitsiConfiguration, - isE2EEByDefaultEnabled: Bool) { + isE2EEByDefaultEnabled: Bool, + tileServer: HomeserverTileServerConfiguration) { self.jitsi = jitsi self.isE2EEByDefaultEnabled = isE2EEByDefaultEnabled - - super.init() + self.tileServer = tileServer } } diff --git a/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift b/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift index a0eab4c13..ca87788b6 100644 --- a/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift +++ b/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift @@ -59,12 +59,26 @@ final class HomeserverConfigurationBuilder: NSObject { jitsiServerURL = hardcodedJitsiServerURL } + // Tile server configuration + + let tileServerMapStyleURL: URL + if let mapStyleURLString = wellKnown?.tileServer?.mapStyleURLString, + let mapStyleURL = URL(string: mapStyleURLString) { + tileServerMapStyleURL = mapStyleURL + } else { + tileServerMapStyleURL = BuildSettings.tileServerMapStyleURL + } + + let tileServerConfiguration = HomeserverTileServerConfiguration(mapStyleURL: tileServerMapStyleURL) + // Create HomeserverConfiguration let jitsiConfiguration = HomeserverJitsiConfiguration(serverDomain: jitsiPreferredDomain, serverURL: jitsiServerURL) - return HomeserverConfiguration(jitsi: jitsiConfiguration, isE2EEByDefaultEnabled: isE2EEByDefaultEnabled) + return HomeserverConfiguration(jitsi: jitsiConfiguration, + isE2EEByDefaultEnabled: isE2EEByDefaultEnabled, + tileServer: tileServerConfiguration) } // MARK: - Private diff --git a/Riot/Model/HomeserverConfiguration/HomeserverTileServiceConfiguration.swift b/Riot/Model/HomeserverConfiguration/HomeserverTileServiceConfiguration.swift new file mode 100644 index 000000000..2e3d8cd44 --- /dev/null +++ b/Riot/Model/HomeserverConfiguration/HomeserverTileServiceConfiguration.swift @@ -0,0 +1,27 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// `HomeserverTileServerConfiguration` defines tile server configuration to be used by the mapping library +@objcMembers +final class HomeserverTileServerConfiguration: NSObject { + let mapStyleURL: URL + + init(mapStyleURL: URL) { + self.mapStyleURL = mapStyleURL + } +} diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift new file mode 100644 index 000000000..d3b9c3ac1 --- /dev/null +++ b/Riot/Modules/Analytics/Analytics.swift @@ -0,0 +1,284 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import PostHog +import AnalyticsEvents + +/// A class responsible for managing an analytics client +/// and sending events through this client. +/// +/// ## Creating Analytics Events +/// +/// Events are managed in a shared repo for all Element clients https://github.com/matrix-org/matrix-analytics-events +/// To add a new event create a PR to that repo with the new/updated schema. Element's Podfile has +/// a local version of the pod (commented out) for development purposes. +/// Once merged into `main`, follow the steps below to integrate the changes into the project: +/// 1. Check if `main` contains any source breaking changes to the events. If so, please +/// wait until you are ready to merge your work into element-ios. +/// 2. Merge `main` into the `release/swift` branch. +/// 3. Run `bundle exec pod update AnalyticsEvents` to update the pod. +/// 4. Make sure to commit `Podfile.lock` with the new commit hash. +/// +@objcMembers class Analytics: NSObject { + + // MARK: - Properties + + /// The singleton instance to be used within the Riot target. + static let shared = Analytics() + + /// The analytics client to send events with. + private var client: AnalyticsClientProtocol = PostHogAnalyticsClient() + + /// The service used to interact with account data settings. + private var service: AnalyticsService? + + /// Whether or not the object is enabled and sending events to the server. + var isRunning: Bool { client.isRunning } + + /// Whether to show the user the analytics opt in prompt. + var shouldShowAnalyticsPrompt: Bool { + // Only show the prompt once, and when analytics are configured in BuildSettings. + !RiotSettings.shared.hasSeenAnalyticsPrompt && PHGPostHogConfiguration.standard != nil + } + + /// Indicates whether the user previously accepted Matomo analytics and should be shown the upgrade prompt. + var promptShouldDisplayUpgradeMessage: Bool { + RiotSettings.shared.hasAcceptedMatomoAnalytics + } + + // MARK: - Public + + /// Opts in to analytics tracking with the supplied session. + /// - Parameter session: An optional session to use to when reading/generating the analytics ID. + /// The session will be ignored if not running. + func optIn(with session: MXSession?) { + RiotSettings.shared.enableAnalytics = true + startIfEnabled() + + guard let session = session else { return } + useAnalyticsSettings(from: session) + } + + /// Stops analytics tracking and calls `reset` to clear any IDs and event queues. + func optOut() { + RiotSettings.shared.enableAnalytics = false + + // The order is important here. PostHog ignores the reset if stopped. + reset() + client.stop() + + MXLog.debug("[Analytics] Stopped.") + } + + /// Starts the analytics client if the user has opted in, otherwise does nothing. + func startIfEnabled() { + guard RiotSettings.shared.enableAnalytics, !isRunning else { return } + + client.start() + + // Sanity check in case something went wrong. + guard client.isRunning else { return } + + MXLog.debug("[Analytics] Started.") + + // Catch and log crashes + MXLogger.logCrashes(true) + MXLogger.setBuildVersion(AppInfo.current.buildInfo.readableBuildVersion) + } + + /// Use the analytics settings from the supplied session to configure analytics. + /// For now this is only used for (pseudonymous) identification. + /// - Parameter session: The session to read analytics settings from. + func useAnalyticsSettings(from session: MXSession) { + guard + RiotSettings.shared.enableAnalytics, + !RiotSettings.shared.isIdentifiedForAnalytics + else { return } + + let service = AnalyticsService(session: session) + self.service = service + + service.settings { [weak self] result in + guard let self = self else { return } + + switch result { + case .success(let settings): + self.identify(with: settings) + self.service = nil + case .failure: + MXLog.error("[Analytics] Failed to use analytics settings. Will continue to run without analytics ID.") + self.service = nil + } + } + } + + /// Resets the any IDs and event queues in the analytics client. This method should + /// be called on sign-out to maintain opt-in status, whilst ensuring the next + /// account used isn't associated with the previous one. + /// Note: **MUST** be called before stopping PostHog or the reset is ignored. + func reset() { + client.reset() + MXLog.debug("[Analytics] Reset.") + RiotSettings.shared.isIdentifiedForAnalytics = false + + // Stop collecting crash logs + MXLogger.logCrashes(false) + } + + /// Flushes the event queue in the analytics client, uploading all pending events. + /// Normally events are sent in batches. Call this method when you need an event + /// to be sent immediately. + func forceUpload() { + client.flush() + } + + // MARK: - Private + + /// Identify (pseudonymously) any future events with the ID from the analytics account data settings. + /// - Parameter settings: The settings to use for identification. The ID must be set *before* calling this method. + private func identify(with settings: AnalyticsSettings) { + guard let id = settings.id else { + MXLog.error("[Analytics] identify(with:) called before an ID has been generated.") + return + } + + client.identify(id: id) + MXLog.debug("[Analytics] Identified.") + RiotSettings.shared.isIdentifiedForAnalytics = true + } + + /// Capture an event in the `client`. + /// - Parameter event: The event to capture. + private func capture(event: AnalyticsEventProtocol) { + client.capture(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. +extension Analytics { + /// Track the presentation of a screen + /// - Parameters: + /// - screen: The screen that was shown. + /// - milliseconds: An optional value representing how long the screen was shown for in milliseconds. + func trackScreen(_ screen: AnalyticsScreen, duration milliseconds: Int?) { + let event = AnalyticsEvent.Screen(durationMs: milliseconds, screenName: screen.screenName) + client.screen(event) + } + + /// The the presentation of a screen without including a duration + /// - Parameter screen: The screen that was shown + func trackScreen(_ screen: AnalyticsScreen) { + trackScreen(screen, duration: nil) + } + + /// Track an element that has been tapped + /// - Parameters: + /// - tap: The element that was tapped + /// - index: The index of the element, if it's in a list of elements + func trackTap(_ tap: AnalyticsUIElement, index: Int?) { + let event = AnalyticsEvent.Click(index: index, name: tap.elementName) + client.capture(event) + } + + /// Track an element that has been tapped without including an index + /// - Parameters: + /// - tap: The element that was tapped + func trackTap(_ tap: AnalyticsUIElement) { + trackTap(tap, index: nil) + } + + /// Track an E2EE error that occurred + /// - Parameters: + /// - reason: The error that occurred. + /// - count: The number of times that error occurred. + func trackE2EEError(_ reason: DecryptionFailureReason, count: Int) { + for _ in 0..) -> Void) { + // Only use the session if it is running otherwise we could wipe out an existing analytics ID. + guard session.state == .running else { + MXLog.warning("[AnalyticsService] Aborting attempt to read analytics settings. The session may not be up-to-date.") + completion(.failure(AnalyticsServiceError.sessionIsNotRunning)) + return + } + + let settings = AnalyticsSettings(accountData: session.accountData) + + // The id has already be set so we are done here. + if settings.id != nil { + completion(.success(settings)) + return + } + + // Create a new ID and modify the event dictionary. + let id = UUID().uuidString + + var eventDictionary = settings.dictionary + eventDictionary[AnalyticsSettings.Constants.idKey] = id + + session.setAccountData(eventDictionary, forType: AnalyticsSettings.eventType) { [weak self] in + guard let self = self else { + completion(.failure(AnalyticsServiceError.unknown)) + return + } + + MXLog.debug("[AnalyticsService] Successfully updated analytics settings in account data.") + let settings = AnalyticsSettings(accountData: self.session.accountData) + completion(.success(settings)) + } failure: { error in + MXLog.warning("[AnalyticsService] Failed to update analytics settings.") + completion(.failure(error ?? AnalyticsServiceError.unknown)) + } + } +} diff --git a/Riot/Modules/Analytics/AnalyticsSettings.swift b/Riot/Modules/Analytics/AnalyticsSettings.swift new file mode 100644 index 000000000..e847f0668 --- /dev/null +++ b/Riot/Modules/Analytics/AnalyticsSettings.swift @@ -0,0 +1,65 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// An analytics settings event from the user's account data. +struct AnalyticsSettings { + static let eventType = "im.vector.analytics" + + enum Constants { + static let idKey = "id" + static let webOptInKey = "pseudonymousAnalyticsOptIn" + } + + /// A randomly generated analytics token for this user. + /// This is suggested to be a UUID string. + let id: String? + + /// Whether the user has opted in on web or not. This is unused on iOS but necessary + /// to store here so that it's value is preserved when updating the account data if we + /// generated an ID on iOS. + /// + /// `true` if opted in on web, `false` if opted out on web and `nil` if the web prompt is not yet seen. + private let webOptIn: Bool? +} + +extension AnalyticsSettings { + // Private as AnalyticsSettings should only be created from an MXSession + private init(dictionary: Dictionary?) { + self.id = dictionary?[Constants.idKey] as? String + self.webOptIn = dictionary?[Constants.webOptInKey] as? Bool + } + + /// A dictionary representation of the settings. + var dictionary: Dictionary { + var dictionary = [AnyHashable: Any]() + dictionary[Constants.idKey] = id + dictionary[Constants.webOptInKey] = webOptIn + + return dictionary + } +} + +// MARK: - Public initializer + +extension AnalyticsSettings { + /// Create the analytics settings from account data. + /// - Parameter accountData: The account data to read the event from. + init(accountData: MXAccountData) { + self.init(dictionary: accountData.accountData(forEventType: AnalyticsSettings.eventType)) + } +} diff --git a/Riot/Modules/Analytics/AnalyticsUIElement.swift b/Riot/Modules/Analytics/AnalyticsUIElement.swift new file mode 100644 index 000000000..93a08e7e2 --- /dev/null +++ b/Riot/Modules/Analytics/AnalyticsUIElement.swift @@ -0,0 +1,32 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AnalyticsEvents + +/// A tappable UI element that can be track in Analytics. +@objc enum AnalyticsUIElement: Int { + case sendMessageButton + + /// The element name reported to the AnalyticsEvent. + var elementName: AnalyticsEvent.Click.Name { + switch self { + // Note: This is a test element that doesn't need to be captured. + // It will likely be removed when the AnalyticsEvent.Click is updated. + case .sendMessageButton: + return .SendMessageButton + } + } +} diff --git a/Riot/Modules/Analytics/DecryptionFailure.swift b/Riot/Modules/Analytics/DecryptionFailure.swift new file mode 100644 index 000000000..d011a0413 --- /dev/null +++ b/Riot/Modules/Analytics/DecryptionFailure.swift @@ -0,0 +1,53 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AnalyticsEvents + +/// Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI. +@objc enum DecryptionFailureReason: Int { + case unspecified + case olmKeysNotSent + case olmIndexError + case unexpected + + var errorName: AnalyticsEvent.Error.Name { + switch self { + case .unspecified: + return .OlmUnspecifiedError + case .olmKeysNotSent: + return .OlmKeysNotSentError + case .olmIndexError: + return .OlmIndexError + case .unexpected: + return .UnknownError + } + } +} + +/// `DecryptionFailure` represents a decryption failure. +@objcMembers class DecryptionFailure: NSObject { + /// 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 + /// Decryption failure reason. + let reason: DecryptionFailureReason + + init(failedEventId: String, reason: DecryptionFailureReason) { + self.failedEventId = failedEventId + self.reason = reason + } +} diff --git a/Riot/Managers/Analytics/DecryptionFailureTracker.h b/Riot/Modules/Analytics/DecryptionFailureTracker.h similarity index 92% rename from Riot/Managers/Analytics/DecryptionFailureTracker.h rename to Riot/Modules/Analytics/DecryptionFailureTracker.h index b2dbbfc77..b8f9ca467 100644 --- a/Riot/Managers/Analytics/DecryptionFailureTracker.h +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.h @@ -16,8 +16,9 @@ #import -#import "DecryptionFailure.h" +@class DecryptionFailureTracker; +@class Analytics; @import MatrixSDK; @interface DecryptionFailureTracker : NSObject @@ -32,7 +33,7 @@ /** The delegate object to receive analytics events. */ -@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) Analytics *delegate; /** Report an event unable to decrypt. diff --git a/Riot/Managers/Analytics/DecryptionFailureTracker.m b/Riot/Modules/Analytics/DecryptionFailureTracker.m similarity index 83% rename from Riot/Managers/Analytics/DecryptionFailureTracker.m rename to Riot/Modules/Analytics/DecryptionFailureTracker.m index 56521eb58..ea770a375 100644 --- a/Riot/Managers/Analytics/DecryptionFailureTracker.m +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.m @@ -15,14 +15,15 @@ */ #import "DecryptionFailureTracker.h" +#import "GeneratedInterface-Swift.h" // Call `checkFailures` every `CHECK_INTERVAL` -#define CHECK_INTERVAL 5 +#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 60 +#define GRACE_PERIOD 4 // E2E failures analytics category. NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure"; @@ -90,31 +91,32 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure"; return; } - DecryptionFailure *decryptionFailure = [[DecryptionFailure alloc] init]; - decryptionFailure.failedEventId = event.eventId; + NSString *failedEventId = event.eventId; + DecryptionFailureReason reason; // Categorise the error switch (event.decryptionError.code) { case MXDecryptingErrorUnknownInboundSessionIdCode: - decryptionFailure.reason = DecryptionFailureReason.olmKeysNotSent; + reason = DecryptionFailureReasonOlmKeysNotSent; break; case MXDecryptingErrorOlmCode: - decryptionFailure.reason = DecryptionFailureReason.olmIndexError; + reason = DecryptionFailureReasonOlmIndexError; break; case MXDecryptingErrorEncryptionNotEnabledCode: case MXDecryptingErrorUnableToDecryptCode: - decryptionFailure.reason = DecryptionFailureReason.unexpected; + reason = DecryptionFailureReasonUnexpected; break; default: - decryptionFailure.reason = DecryptionFailureReason.unspecified; + reason = DecryptionFailureReasonUnspecified; break; } - reportedFailures[event.eventId] = decryptionFailure; + reportedFailures[event.eventId] = [[DecryptionFailure alloc] initWithFailedEventId:failedEventId + reason:reason]; } - (void)dispatch @@ -152,17 +154,17 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure"; if (failuresToTrack.count) { // Sort failures by error reason - NSMutableDictionary *failuresCounts = [NSMutableDictionary dictionary]; + NSMutableDictionary *failuresCounts = [NSMutableDictionary dictionary]; for (DecryptionFailure *failure in failuresToTrack) { - failuresCounts[failure.reason] = @(failuresCounts[failure.reason].unsignedIntegerValue + 1); + failuresCounts[@(failure.reason)] = @(failuresCounts[@(failure.reason)].unsignedIntegerValue + 1); } MXLogDebug(@"[DecryptionFailureTracker] trackFailures: %@", failuresCounts); - for (NSString *reason in failuresCounts) + for (NSNumber *reason in failuresCounts) { - [_delegate trackValue:failuresCounts[reason] category:kDecryptionFailureTrackerAnalyticsCategory name:reason]; + [self.delegate trackE2EEError:reason.integerValue count:failuresCounts[reason].integerValue]; } } } diff --git a/Riot/Modules/Analytics/Helpers/JoinedRoomSize+MemberCount.swift b/Riot/Modules/Analytics/Helpers/JoinedRoomSize+MemberCount.swift new file mode 100644 index 000000000..d5473d5f9 --- /dev/null +++ b/Riot/Modules/Analytics/Helpers/JoinedRoomSize+MemberCount.swift @@ -0,0 +1,36 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AnalyticsEvents + +extension AnalyticsEvent.JoinedRoom.RoomSize { + init?(memberCount: UInt) { + switch memberCount { + case 2: + self = .Two + case 3...10: + self = .ThreeToTen + case 11...100: + self = .ElevenToOneHundred + case 101...1000: + self = .OneHundredAndOneToAThousand + case 1001...: + self = .MoreThanAThousand + default: + return nil + } + } +} diff --git a/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift b/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift new file mode 100644 index 000000000..4b8911ce8 --- /dev/null +++ b/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift @@ -0,0 +1,38 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AnalyticsEvents + +extension __MXCallHangupReason { + var errorName: AnalyticsEvent.Error.Name { + switch self { + case .userHangup: + return .VoipUserHangup + case .inviteTimeout: + return .VoipInviteTimeout + case .iceFailed: + return .VoipIceFailed + case .iceTimeout: + return .VoipIceTimeout + case .userMediaFailed: + return .VoipUserMediaFailed + case .unknownError: + return .UnknownError + default: + return .UnknownError + } + } +} diff --git a/Riot/Modules/Analytics/Helpers/MXTaskProfileName+Analytics.swift b/Riot/Modules/Analytics/Helpers/MXTaskProfileName+Analytics.swift new file mode 100644 index 000000000..99f89174e --- /dev/null +++ b/Riot/Modules/Analytics/Helpers/MXTaskProfileName+Analytics.swift @@ -0,0 +1,42 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AnalyticsEvents + +extension MXTaskProfileName { + var analyticsName: AnalyticsEvent.PerformanceTimer.Name? { + switch self { + case .startupIncrementalSync: + return .StartupIncrementalSync + case .startupInitialSync: + return .StartupInitialSync + case .startupLaunchScreen: + return .StartupLaunchScreen + case .startupStorePreload: + return .StartupStorePreload + case .startupMountData: + return .StartupStoreReady + case .initialSyncRequest: + return .InitialSyncRequest + case .initialSyncParsing: + return .InitialSyncParsing + case .notificationsOpenEvent: + return .NotificationsOpenEvent + default: + return nil + } + } +} diff --git a/Riot/Modules/Analytics/PHGPostHogConfiguration.swift b/Riot/Modules/Analytics/PHGPostHogConfiguration.swift new file mode 100644 index 000000000..c02b85c30 --- /dev/null +++ b/Riot/Modules/Analytics/PHGPostHogConfiguration.swift @@ -0,0 +1,28 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import PostHog + +extension PHGPostHogConfiguration { + static var standard: PHGPostHogConfiguration? { + guard let apiKey = BuildSettings.analyticsKey, let host = BuildSettings.analyticsHost else { return nil } + + let configuration = PHGPostHogConfiguration(apiKey: apiKey, host: host) + configuration.shouldSendDeviceID = false + + return configuration + } +} diff --git a/Riot/Modules/Analytics/PostHogAnalyticsClient.swift b/Riot/Modules/Analytics/PostHogAnalyticsClient.swift new file mode 100644 index 000000000..1c7172112 --- /dev/null +++ b/Riot/Modules/Analytics/PostHogAnalyticsClient.swift @@ -0,0 +1,65 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import PostHog +import AnalyticsEvents + +/// An analytics client that reports events to a PostHog server. +class PostHogAnalyticsClient: AnalyticsClientProtocol { + /// The PHGPostHog object used to report events. + private var postHog: PHGPostHog? + + var isRunning: Bool { postHog?.enabled ?? false } + + func start() { + // Only start if analytics have been configured in BuildSettings + guard let configuration = PHGPostHogConfiguration.standard else { return } + + if postHog == nil { + postHog = PHGPostHog(configuration: configuration) + } + + postHog?.enable() + } + + func identify(id: String) { + postHog?.identify(id) + } + + func reset() { + postHog?.reset() + } + + func stop() { + postHog?.disable() + + // As of PostHog 1.4.4, setting the client to nil here doesn't release + // it. Keep it around to avoid having multiple instances if the user re-enables + } + + func flush() { + postHog?.flush() + } + + func capture(_ event: AnalyticsEventProtocol) { + postHog?.capture(event.eventName, properties: event.properties) + } + + func screen(_ event: AnalyticsScreenProtocol) { + postHog?.screen(event.screenName.rawValue, properties: event.properties) + } + +} diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index 72dc4f61e..40e3c2377 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -22,7 +22,6 @@ #import "JitsiViewController.h" #import "RageShakeManager.h" -#import "Analytics.h" #import "ThemeService.h" #import "UniversalLink.h" diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 180cfe27a..3d62fdb16 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -433,16 +433,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni _isAppForeground = NO; _handleSelfVerificationRequest = YES; - // Configure our analytics. It will indeed start if the option is enabled - Analytics *analytics = [Analytics sharedInstance]; + // Configure our analytics. It will start if the option is enabled + Analytics *analytics = Analytics.shared; [MXSDKOptions sharedInstance].analyticsDelegate = analytics; - [DecryptionFailureTracker sharedInstance].delegate = [Analytics sharedInstance]; + [DecryptionFailureTracker sharedInstance].delegate = analytics; MXBaseProfiler *profiler = [MXBaseProfiler new]; profiler.analytics = analytics; [MXSDKOptions sharedInstance].profiler = profiler; - [analytics start]; + [analytics startIfEnabled]; self.localAuthenticationService = [[LocalAuthenticationService alloc] initWithPinCodePreferences:[PinCodePreferences shared]]; @@ -587,7 +587,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Analytics: Force to send the pending actions [[DecryptionFailureTracker sharedInstance] dispatch]; - [[Analytics sharedInstance] dispatch]; + [Analytics.shared forceUpload]; } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -648,9 +648,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni MXLogDebug(@"[AppDelegate] afterAppUnlockedByPin"); // Check if there is crash log to send - if (RiotSettings.shared.enableCrashReport) + if (RiotSettings.shared.enableAnalytics) { + #if DEBUG + // Don't show alerts for crashes during development. + #else [self checkExceptionToReport]; + #endif } // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. @@ -1126,7 +1130,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [[NSNotificationCenter defaultCenter] postNotificationName:AppDelegateUniversalLinkDidChangeNotification object:nil]; } - if ([self handleServerProvionningLink:webURL]) + if ([self handleServerProvisioningLink:webURL]) { return YES; } @@ -1248,8 +1252,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { NSString *fragment = universalLinkParameters.fragment; NSURL *universalLinkURL = universalLinkParameters.universalLinkURL; - ScreenPresentationParameters *screenPresentationParameters = universalLinkParameters.presentationParameters; - BOOL restoreInitialDisplay = screenPresentationParameters.restoreInitialDisplay; + ScreenPresentationParameters *presentationParameters = universalLinkParameters.presentationParameters; + BOOL restoreInitialDisplay = presentationParameters.restoreInitialDisplay; BOOL continueUserActivity = NO; MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; @@ -1351,16 +1355,72 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if (room.summary.roomType == MXRoomTypeSpace) { - SpaceNavigationParameters *spaceNavigationParameters = [[SpaceNavigationParameters alloc] initWithRoomId:room.roomId mxSession:account.mxSession presentationParameters:screenPresentationParameters]; + SpaceNavigationParameters *spaceNavigationParameters = [[SpaceNavigationParameters alloc] initWithRoomId:room.roomId mxSession:account.mxSession presentationParameters:presentationParameters]; [self showSpaceWithParameters:spaceNavigationParameters]; } else { // Open the room page - RoomNavigationParameters *roomNavigationParameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId eventId:eventId mxSession:account.mxSession presentationParameters: screenPresentationParameters]; - - [self showRoomWithParameters:roomNavigationParameters]; + if (eventId) + { + __block MXEvent *event = [account.mxSession.store eventWithEventId:eventId inRoom:roomId]; + dispatch_group_t eventDispatchGroup = dispatch_group_create(); + + if (event == nil) + { + dispatch_group_enter(eventDispatchGroup); + // event doesn't exist in the store + [account.mxSession eventWithEventId:eventId + inRoom:roomId + success:^(MXEvent *eventFromServer) { + event = eventFromServer; + dispatch_group_leave(eventDispatchGroup); + } failure:^(NSError *error) { + dispatch_group_leave(eventDispatchGroup); + }]; + } + + dispatch_group_notify(eventDispatchGroup, dispatch_get_main_queue(), ^{ + if (event == nil) + { + return; + } + + ThreadParameters *threadParameters = nil; + if (RiotSettings.shared.enableThreads) + { + if (event.threadId) + { + threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId + stackRoomScreen:NO]; + } + else if ([account.mxSession.threadingService threadWithId:eventId]) + { + threadParameters = [[ThreadParameters alloc] initWithThreadId:eventId + stackRoomScreen:NO]; + } + } + + RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId + eventId:eventId + mxSession:account.mxSession + threadParameters:threadParameters + presentationParameters:presentationParameters]; + [self showRoomWithParameters:parameters]; + }); + } + else + { + // open the regular room timeline + RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId + eventId:eventId + mxSession:account.mxSession + threadParameters:nil + presentationParameters:presentationParameters]; + + [self showRoomWithParameters:parameters]; + } } continueUserActivity = YES; @@ -1368,9 +1428,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni else { void(^findRoom)(void) = ^{ - if ([_masterTabBarController.selectedViewController isKindOfClass:MXKActivityHandlingViewController.class]) + if ([_masterTabBarController.selectedViewController conformsToProtocol:@protocol(MXKViewControllerActivityHandling)]) { - MXKActivityHandlingViewController *homeViewController = (MXKActivityHandlingViewController*)_masterTabBarController.selectedViewController; + UIViewController *homeViewController = (UIViewController*)_masterTabBarController.selectedViewController; [homeViewController startActivityIndicator]; @@ -1408,7 +1468,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { universalLinkFragmentPendingRoomAlias = @{roomId: roomIdOrAlias}; - UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newUniversalLinkFragment universalLinkURL:universalLinkURL presentationParameters:screenPresentationParameters]; + UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newUniversalLinkFragment universalLinkURL:universalLinkURL presentationParameters:presentationParameters]; [self handleUniversalLinkWithParameters:newParameters]; } @@ -1467,14 +1527,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni roomPreviewData.viaServers = queryParams[@"via"]; } - RoomPreviewNavigationParameters *roomPreviewNavigationParameters = [[RoomPreviewNavigationParameters alloc] initWithPreviewData:roomPreviewData presentationParameters:screenPresentationParameters]; + RoomPreviewNavigationParameters *roomPreviewNavigationParameters = [[RoomPreviewNavigationParameters alloc] initWithPreviewData:roomPreviewData presentationParameters:presentationParameters]; [account.mxSession.matrixRestClient roomSummaryWith:roomIdOrAlias via:roomPreviewData.viaServers success:^(MXPublicRoom *room) { if ([room.roomTypeString isEqualToString:MXRoomTypeStringSpace]) { [homeViewController stopActivityIndicator]; - SpacePreviewNavigationParameters *spacePreviewNavigationParameters = [[SpacePreviewNavigationParameters alloc] initWithPublicRoom:room mxSession:account.mxSession presentationParameters:screenPresentationParameters]; + SpacePreviewNavigationParameters *spacePreviewNavigationParameters = [[SpacePreviewNavigationParameters alloc] initWithPublicRoom:room mxSession:account.mxSession presentationParameters:presentationParameters]; [self showSpacePreviewWithParameters:spacePreviewNavigationParameters]; } @@ -1552,7 +1612,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Create the contact related to this member MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:displayName andMatrixID:userId]; - [self showContact:contact presentationParameters:screenPresentationParameters]; + [self showContact:contact presentationParameters:presentationParameters]; continueUserActivity = YES; } @@ -1571,7 +1631,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } // Display the group details - [self showGroup:group withMatrixSession:account.mxSession presentationParamters:screenPresentationParameters]; + [self showGroup:group withMatrixSession:account.mxSession presentationParamters:presentationParameters]; continueUserActivity = YES; } @@ -1588,7 +1648,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Check that 'fragment' has not been cancelled if ([universalLinkFragmentPending isEqualToString:fragment]) { - MXLogDebug(@"[AppDelegate] Universal link: The user is now logged in. Retry the link"); + MXLogDebug(@"[AppDelegate] Universal link: The user is now logged in. Retry the link"); [self handleUniversalLinkWithParameters:universalLinkParameters]; } }]; @@ -1600,7 +1660,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni MXLogDebug(@"[AppDelegate] Universal link with registration parameters"); continueUserActivity = YES; - [_masterTabBarController showAuthenticationScreenWithRegistrationParameters:queryParams]; + [_masterTabBarController showOnboardingFlowWithRegistrationParameters:queryParams]; } else { @@ -1647,11 +1707,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Try to get more information about the room before opening its preview [roomPreviewData peekInRoom:^(BOOL succeeded) { MXStrongifyAndReturnIfNil(self); - - MXKViewController *homeViewController = (MXKViewController*)self.masterTabBarController.selectedViewController; + if ([self.masterTabBarController.selectedViewController conformsToProtocol:@protocol(MXKViewControllerActivityHandling)]) + { + UIViewController *homeViewController = (UIViewController*)self.masterTabBarController.selectedViewController; - // Note: the activity indicator will not disappear if the session is not ready - [homeViewController stopActivityIndicator]; + // Note: the activity indicator will not disappear if the session is not ready + [homeViewController stopActivityIndicator]; + } // If no data is available for this room, we name it with the known room alias (if any). if (!succeeded && self->universalLinkFragmentPendingRoomAlias[roomIdOrAlias]) @@ -1738,33 +1800,36 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni *outQueryParams = queryParams; } - -- (BOOL)handleServerProvionningLink:(NSURL*)link +/** + Parse and handle a server provisioning link. Returns `YES` if a provisioning link was detected and handled. + @param link A link such as https://mobile.element.io/?hs_url=matrix.example.com&is_url=identity.example.com + */ +- (BOOL)handleServerProvisioningLink:(NSURL*)link { - MXLogDebug(@"[AppDelegate] handleServerProvionningLink: %@", link); + MXLogDebug(@"[AppDelegate] handleServerProvisioningLink: %@", link); NSString *homeserver, *identityServer; - [self parseServerProvionningLink:link homeserver:&homeserver identityServer:&identityServer]; + [self parseServerProvisioningLink:link homeserver:&homeserver identityServer:&identityServer]; if (homeserver) { if ([MXKAccountManager sharedManager].activeAccounts.count) { - [self displayServerProvionningLinkBuyAlreadyLoggedInAlertWithCompletion:^(BOOL logout) { + [self displayServerProvisioningLinkBuyAlreadyLoggedInAlertWithCompletion:^(BOOL logout) { - MXLogDebug(@"[AppDelegate] handleServerProvionningLink: logoutWithConfirmation: logout: %@", @(logout)); + MXLogDebug(@"[AppDelegate] handleServerProvisioningLink: logoutWithConfirmation: logout: %@", @(logout)); if (logout) { [self logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) { - [self handleServerProvionningLink:link]; + [self handleServerProvisioningLink:link]; }]; } }]; } else { - [_masterTabBarController showAuthenticationScreen]; - [_masterTabBarController.authViewController showCustomHomeserver:homeserver andIdentityServer:identityServer]; + [_masterTabBarController showOnboardingFlow]; + [_masterTabBarController.onboardingCoordinatorBridgePresenter updateHomeserver:homeserver andIdentityServer:identityServer]; } return YES; @@ -1773,7 +1838,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return NO; } -- (void)parseServerProvionningLink:(NSURL*)link homeserver:(NSString**)homeserver identityServer:(NSString**)identityServer +- (void)parseServerProvisioningLink:(NSURL*)link homeserver:(NSString**)homeserver identityServer:(NSString**)identityServer { if ([link.path isEqualToString:@"/"]) { @@ -1793,14 +1858,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } else { - MXLogDebug(@"[AppDelegate] parseServerProvionningLink: Error: Unknown path: %@", link.path); + MXLogDebug(@"[AppDelegate] parseServerProvisioningLink: Error: Unknown path: %@", link.path); } - MXLogDebug(@"[AppDelegate] parseServerProvionningLink: homeserver: %@ - identityServer: %@", *homeserver, *identityServer); + MXLogDebug(@"[AppDelegate] parseServerProvisioningLink: homeserver: %@ - identityServer: %@", *homeserver, *identityServer); } -- (void)displayServerProvionningLinkBuyAlreadyLoggedInAlertWithCompletion:(void (^)(BOOL logout))completion +- (void)displayServerProvisioningLinkBuyAlreadyLoggedInAlertWithCompletion:(void (^)(BOOL logout))completion { // Ask confirmation self.logoutConfirmation = [UIAlertController alertControllerWithTitle:[VectorL10n errorUserAlreadyLoggedIn] message:nil preferredStyle:UIAlertControllerStyleAlert]; @@ -1880,6 +1945,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self.pushNotificationService checkPushKitPushersInSession:mxSession]; } + else if (mxSession.state == MXSessionStateRunning) + { + // Configure analytics from the session if necessary + [Analytics.shared useAnalyticsSettingsFrom:mxSession]; + } else if (mxSession.state == MXSessionStateClosed) { [self removeMatrixSession:mxSession]; @@ -1954,7 +2024,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [self removeMatrixSession:account.mxSession]; // Return to authentication screen - [self.masterTabBarController showAuthenticationScreenAfterSoftLogout:account.mxCredentials]; + [self.masterTabBarController showSoftLogoutOnboardingFlowWithCredentials:account.mxCredentials]; }]; [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionIgnoredUsersDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull notif) { @@ -2225,6 +2295,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Reset push notification store [self.pushNotificationStore reset]; + // Reset analytics + [Analytics.shared reset]; + #ifdef MX_CALL_STACK_ENDPOINT // Erase all created certificates and private keys by MXEndpointCallStack for (MXKAccount *account in MXKAccountManager.sharedManager.accounts) @@ -2245,7 +2318,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } // Return to authentication screen - [_masterTabBarController showAuthenticationScreen]; + [_masterTabBarController showOnboardingFlow]; // Note: Keep App settings // But enforce usage of member lazy loading @@ -2284,7 +2357,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { BOOL isLaunching = NO; - if (_masterTabBarController.authenticationInProgress) + if (_masterTabBarController.isOnboardingInProgress) { MXLogDebug(@"[AppDelegate] handleAppState: Authentication still in progress"); @@ -2390,8 +2463,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni launchAnimationContainerView = launchLoadingView; - [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:kMXAnalyticsStartupLaunchScreen - category:kMXAnalyticsStartupCategory]; + [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:MXTaskProfileNameStartupLaunchScreen]; } } @@ -2400,7 +2472,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if (launchAnimationContainerView) { id profiler = MXSDKOptions.sharedInstance.profiler; - MXTaskProfile *launchTaskProfile = [profiler taskProfileWithName:kMXAnalyticsStartupLaunchScreen category:kMXAnalyticsStartupCategory]; + MXTaskProfile *launchTaskProfile = [profiler taskProfileWithName:MXTaskProfileNameStartupLaunchScreen]; if (launchTaskProfile) { [profiler stopMeasuringTaskWithProfile:launchTaskProfile]; @@ -2880,7 +2952,10 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:YES]; RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId - eventId:eventId mxSession:mxSession presentationParameters:presentationParameters]; + eventId:eventId + mxSession:mxSession + threadParameters:nil + presentationParameters:presentationParameters]; [self showRoomWithParameters:parameters]; } @@ -3402,7 +3477,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni @"party_id": mxSession.myDeviceId }; - [mxSession.matrixRestClient sendEventToRoom:event.roomId eventType:kMXEventTypeStringCallReject content:content txnId:nil success:nil failure:^(NSError *error) { + [mxSession.matrixRestClient sendEventToRoom:event.roomId threadId:nil eventType:kMXEventTypeStringCallReject content:content txnId:nil success:nil failure:^(NSError *error) { MXLogDebug(@"[AppDelegate] enableNoVoIPOnMatrixSession: ERROR: Cannot send m.call.reject event."); }]; @@ -3841,7 +3916,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return; } - if (_masterTabBarController.authenticationInProgress) + if (_masterTabBarController.isOnboardingInProgress) { MXLogDebug(@"[AppDelegate][KeyVerification] keyVerificationNewRequestNotification: Postpone requests during the authentication process"); @@ -4483,15 +4558,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni - (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId { - AuthenticationViewController *authVC = self.masterTabBarController.authViewController; + OnboardingCoordinatorBridgePresenter *bridgePresenter = self.masterTabBarController.onboardingCoordinatorBridgePresenter; - if (!authVC) + if (!bridgePresenter) { MXLogDebug(@"[AppDelegate] Fail to continue SSO login"); return NO; } - return [authVC continueSSOLoginWithToken:loginToken txnId:txnId]; + return [bridgePresenter continueSSOLoginWithToken:loginToken transactionID:txnId]; } #pragma mark - Private diff --git a/Riot/Modules/Application/ScreenNavigation/RoomNavigationParameters.swift b/Riot/Modules/Application/ScreenNavigation/RoomNavigationParameters.swift index 7308a5701..b44d00356 100644 --- a/Riot/Modules/Application/ScreenNavigation/RoomNavigationParameters.swift +++ b/Riot/Modules/Application/ScreenNavigation/RoomNavigationParameters.swift @@ -16,6 +16,24 @@ import Foundation +@objcMembers +class ThreadParameters: NSObject { + + /// If not nil, the thread will be opened on this room + let threadId: String + + /// If true, related room screen will be stacked in the navigation stack + let stackRoomScreen: Bool + + init(threadId: String, + stackRoomScreen: Bool) { + self.threadId = threadId + self.stackRoomScreen = stackRoomScreen + super.init() + } + +} + /// Navigation parameters to display a room with a provided identifier in a specific matrix session. @objcMembers class RoomNavigationParameters: NSObject { @@ -31,6 +49,9 @@ class RoomNavigationParameters: NSObject { /// The Matrix session in which the room should be available. let mxSession: MXSession + /// Navigation parameters for a thread + let threadParameters: ThreadParameters? + /// Screen presentation parameters. let presentationParameters: ScreenPresentationParameters @@ -39,10 +60,12 @@ class RoomNavigationParameters: NSObject { init(roomId: String, eventId: String?, mxSession: MXSession, + threadParameters: ThreadParameters?, presentationParameters: ScreenPresentationParameters) { self.roomId = roomId self.eventId = eventId self.mxSession = mxSession + self.threadParameters = threadParameters self.presentationParameters = presentationParameters super.init() diff --git a/Riot/Modules/Application/ScreenNavigation/RoomPreviewNavigationParameters.swift b/Riot/Modules/Application/ScreenNavigation/RoomPreviewNavigationParameters.swift index e59d2687f..3a9532c02 100644 --- a/Riot/Modules/Application/ScreenNavigation/RoomPreviewNavigationParameters.swift +++ b/Riot/Modules/Application/ScreenNavigation/RoomPreviewNavigationParameters.swift @@ -34,6 +34,7 @@ class RoomPreviewNavigationParameters: RoomNavigationParameters { super.init(roomId: previewData.roomId, eventId: previewData.eventId, mxSession: previewData.mxSession, + threadParameters: nil, presentationParameters: presentationParameters) } } diff --git a/Riot/Modules/Authentication/AuthenticationCoordinator.swift b/Riot/Modules/Authentication/AuthenticationCoordinator.swift new file mode 100644 index 000000000..a310cdb1a --- /dev/null +++ b/Riot/Modules/Authentication/AuthenticationCoordinator.swift @@ -0,0 +1,87 @@ +// File created from ScreenTemplate +// $ createScreen.sh Onboarding Authentication +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +/// A coordinator that handles authentication, verification and setting a PIN. +final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtocol { + + // MARK: - Properties + + // MARK: Private + + private let authenticationViewController: AuthenticationViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: (() -> Void)? + + // MARK: - Setup + + override init() { + let authenticationViewController = AuthenticationViewController() + self.authenticationViewController = authenticationViewController + + // Preload the view as this can a second and lock up the UI at presentation. + // The coordinator is initialised early in the onboarding flow to take advantage of this. + authenticationViewController.loadViewIfNeeded() + + super.init() + } + + // MARK: - Public + + func start() { + // Listen to the end of the authentication flow + authenticationViewController.authVCDelegate = self + } + + func toPresentable() -> UIViewController { + return self.authenticationViewController + } + + func update(authenticationType: MXKAuthenticationType) { + authenticationViewController.authType = authenticationType + } + + func update(externalRegistrationParameters: [AnyHashable: Any]) { + authenticationViewController.externalRegistrationParameters = externalRegistrationParameters + } + + func update(softLogoutCredentials: MXCredentials) { + authenticationViewController.softLogoutCredentials = softLogoutCredentials + } + + func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?) { + authenticationViewController.showCustomHomeserver(homeserver, andIdentityServer: identityServer) + } + + func continueSSOLogin(withToken loginToken: String, transactionID: String) -> Bool { + authenticationViewController.continueSSOLogin(withToken: loginToken, txnId: transactionID) + } +} + +// MARK: - AuthenticationViewControllerDelegate +extension AuthenticationCoordinator: AuthenticationViewControllerDelegate { + func authenticationViewControllerDidDismiss(_ authenticationViewController: AuthenticationViewController!) { + completion?() + } +} diff --git a/Riot/Modules/Authentication/AuthenticationCoordinatorProtocol.swift b/Riot/Modules/Authentication/AuthenticationCoordinatorProtocol.swift new file mode 100644 index 000000000..7037f9d1f --- /dev/null +++ b/Riot/Modules/Authentication/AuthenticationCoordinatorProtocol.swift @@ -0,0 +1,40 @@ +// File created from ScreenTemplate +// $ createScreen.sh Onboarding Authentication +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// `AuthenticationCoordinatorProtocol` is a protocol describing a Coordinator that handle's the authentication navigation flow. +protocol AuthenticationCoordinatorProtocol: Coordinator, Presentable { + var completion: (() -> Void)? { get set } + + /// Update the screen to display registration or login. + func update(authenticationType: MXKAuthenticationType) + + /// Force a registration process based on a predefined set of parameters from a server provisioning link. + /// For more information see `AuthenticationViewController.externalRegistrationParameters`. + func update(externalRegistrationParameters: [AnyHashable: Any]) + + /// Update the screen to use any credentials to use after a soft logout has taken place. + func update(softLogoutCredentials: MXCredentials) + + /// Set up the authentication screen with the specified homeserver and/or identity server. + func updateHomeserver(_ homeserver: String?, andIdentityServer identityServer: String?) + + /// When SSO login succeeded, when SFSafariViewController is used, continue login with success parameters. + func continueSSOLogin(withToken loginToken: String, transactionID: String) -> Bool +} diff --git a/Riot/Modules/Authentication/AuthenticationViewController.h b/Riot/Modules/Authentication/AuthenticationViewController.h index 133f0a867..894ea8b45 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.h +++ b/Riot/Modules/Authentication/AuthenticationViewController.h @@ -26,13 +26,6 @@ // MXKAuthenticationViewController has already a `delegate` member @property (nonatomic, weak) id authVCDelegate; -@property (weak, nonatomic) IBOutlet UIView *navigationBackView; -@property (weak, nonatomic) IBOutlet UINavigationBar *navigationBar; -@property (weak, nonatomic) IBOutlet UIView *navigationBarSeparatorView; - -@property (weak, nonatomic) IBOutlet UINavigationItem *mainNavigationItem; -@property (weak, nonatomic) IBOutlet UIBarButtonItem *rightBarButtonItem; - @property (weak, nonatomic) IBOutlet UIView *optionsContainer; @property (weak, nonatomic) IBOutlet UIButton *skipButton; diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 188e33268..4f76a029b 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -126,8 +126,11 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; { [super viewDidLoad]; - self.mainNavigationItem.title = nil; - self.rightBarButtonItem.title = [VectorL10n authRegister]; + self.navigationItem.title = nil; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:VectorL10n.authRegister + style:UIBarButtonItemStylePlain + target:self + action:@selector(onButtonPressed:)]; self.defaultHomeServerUrl = RiotSettings.shared.homeserverUrlString; @@ -152,8 +155,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; if (!BuildSettings.authScreenShowRegister) { - self.rightBarButtonItem.enabled = NO; - self.rightBarButtonItem.title = nil; + self.navigationItem.rightBarButtonItem.enabled = NO; + self.navigationItem.rightBarButtonItem.title = nil; } self.serverOptionsContainer.hidden = !BuildSettings.authScreenShowCustomServerOptions; @@ -211,16 +214,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; - (void)userInterfaceThemeDidChange { - self.navigationBackView.backgroundColor = ThemeService.shared.theme.baseColor; - [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationBar]; - self.navigationBarSeparatorView.backgroundColor = ThemeService.shared.theme.lineBreakColor; - - // This view controller is not part of a navigation controller - // so that applyStyleOnNavigationBar does not fully work. - // In order to have the right status bar color, use the expected status bar color - // as the main view background color. - // Hopefully, subviews define their own background color with `theme.backgroundColor`, - // which makes all work together. + [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; + self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; self.authenticationScrollView.backgroundColor = ThemeService.shared.theme.backgroundColor; @@ -309,11 +304,9 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"Authentication"]; [_keyboardAvoider startAvoiding]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; } - (void)viewDidAppear:(BOOL)animated @@ -330,7 +323,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; return; } - // Verify that the app does not show the authentification screean whereas + // Verify that the app does not show the authentication screen whereas // the user has already logged in. // This bug rarely happens (https://github.com/vector-im/riot-ios/issues/1643) // but it invites the user to log in again. They will then lose all their @@ -340,7 +333,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; didCheckFalseAuthScreenDisplay = YES; MXLogDebug(@"[AuthenticationVC] viewDidAppear: Checking false logout"); - [[MXKAccountManager sharedManager] forceReloadAccounts]; + [MXKAccountManager sharedManagerWithReload: YES]; if ([MXKAccountManager sharedManager].activeAccounts.count) { // For now, we do not have better solution than forcing the user to restart the app @@ -482,14 +475,18 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; { BOOL hideAuthInputView = NO; - // Hide input view when there is only social login actions to present - if ((self.authType == MXKAuthenticationTypeLogin || self.authType == MXKAuthenticationTypeRegister) + // Hide input view when there is only social login actions to present at login + if ((self.authType == MXKAuthenticationTypeLogin) && self.currentLoginSSOFlow - && !self.isAuthSessionContainsPasswordFlow) + && !self.isAuthSessionContainsPasswordFlow + && BuildSettings.authScreenShowSocialLoginSection) { hideAuthInputView = YES; } + // Note: Registration will hide the input view in onFailureDuringMXOperation + // if registration has been disabled. + self.authInputsView.hidden = hideAuthInputView; } @@ -498,7 +495,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; super.userInteractionEnabled = userInteractionEnabled; // Reset - self.rightBarButtonItem.enabled = YES; + self.navigationItem.rightBarButtonItem.enabled = YES; // Show/Hide server options if (_optionsContainer.hidden == userInteractionEnabled) @@ -512,10 +509,10 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; if (!userInteractionEnabled) { // The right bar button is used to cancel the running request. - self.rightBarButtonItem.title = [VectorL10n cancel]; + self.navigationItem.rightBarButtonItem.title = [VectorL10n cancel]; // Remove the potential back button. - self.mainNavigationItem.leftBarButtonItem = nil; + self.navigationItem.leftBarButtonItem = nil; } else { @@ -532,18 +529,18 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; && !self.softLogoutCredentials && BuildSettings.authScreenShowRegister) { - self.rightBarButtonItem.title = [VectorL10n authRegister]; + self.navigationItem.rightBarButtonItem.title = [VectorL10n authRegister]; } else { // Disable register on SSO - self.rightBarButtonItem.enabled = NO; - self.rightBarButtonItem.title = nil; + self.navigationItem.rightBarButtonItem.enabled = NO; + self.navigationItem.rightBarButtonItem.title = nil; } } else if (self.authType == MXKAuthenticationTypeRegister) { - self.rightBarButtonItem.title = [VectorL10n authLogin]; + self.navigationItem.rightBarButtonItem.title = [VectorL10n authLogin]; // Restore the back button if (authInputsview) @@ -554,7 +551,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; else if (self.authType == MXKAuthenticationTypeForgotPassword) { // The right bar button is used to return to login. - self.rightBarButtonItem.title = [VectorL10n cancel]; + self.navigationItem.rightBarButtonItem.title = [VectorL10n cancel]; } } } @@ -564,14 +561,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; KeyVerificationCoordinatorBridgePresenter *keyVerificationCoordinatorBridgePresenter = [[KeyVerificationCoordinatorBridgePresenter alloc] initWithSession:session]; keyVerificationCoordinatorBridgePresenter.delegate = self; - if (self.navigationController) - { - [keyVerificationCoordinatorBridgePresenter pushCompleteSecurityFrom:self.navigationController isNewSignIn:YES animated:YES]; - } - else - { - [keyVerificationCoordinatorBridgePresenter presentCompleteSecurityFrom:self isNewSignIn:YES animated:YES]; - } + [keyVerificationCoordinatorBridgePresenter presentCompleteSecurityFrom:self isNewSignIn:YES animated:YES]; self.keyVerificationCoordinatorBridgePresenter = keyVerificationCoordinatorBridgePresenter; } @@ -581,17 +571,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; self.userInteractionEnabled = YES; [self.authenticationActivityIndicator stopAnimating]; - // Remove auth view controller on successful login - if (self.navigationController) - { - // Pop the view controller - [self.navigationController popViewControllerAnimated:YES]; - } - else - { - // Dismiss on successful login - [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; - } + // Dismiss (key verification) on successful login + [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; if (self.authVCDelegate) { @@ -609,7 +590,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [self loginWithToken:loginToken]; return YES; } - + MXLogDebug(@"[AuthenticationVC] Fail to continue SSO login"); return NO; } @@ -687,8 +668,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; // Customise the screen for soft logout self.customServersTickButton.hidden = YES; - self.rightBarButtonItem.title = nil; - self.mainNavigationItem.title = [VectorL10n authSoftlogoutSignedOut]; + self.navigationItem.rightBarButtonItem.title = nil; + self.navigationItem.title = [VectorL10n authSoftlogoutSignedOut]; [self showSoftLogoutClearDataContainer]; } @@ -832,14 +813,21 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; } } -- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession +- (void)refreshAuthenticationSession +{ + // Hide the social login buttons while the session refreshes + [self hideSocialLoginView]; + [super refreshAuthenticationSession]; +} + +- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession withFallbackSSOFlow:(MXLoginSSOFlow *)fallbackSSOFlow { // Make some cleaning from the server response according to what the app supports authSession = [self handleSupportedFlowsInAuthenticationSession:authSession]; - [super handleAuthenticationSession:authSession]; + [super handleAuthenticationSession:authSession withFallbackSSOFlow:fallbackSSOFlow]; - self.currentLoginSSOFlow = [self logginSSOFlowWithProvidersFromFlows:authSession.flows]; + self.currentLoginSSOFlow = [self loginSSOFlowWithProvidersFromFlows:authSession.flows] ?: fallbackSSOFlow; [self updateAuthInputViewVisibility]; [self updateSocialLoginViewVisibility]; @@ -895,27 +883,6 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; return NO; } -- (MXLoginSSOFlow*)logginSSOFlowWithProvidersFromFlows:(NSArray*)loginFlows -{ - MXLoginSSOFlow *ssoFlowWithProviders; - - for (MXLoginFlow *loginFlow in loginFlows) - { - if ([loginFlow isKindOfClass:MXLoginSSOFlow.class]) - { - MXLoginSSOFlow *ssoFlow = (MXLoginSSOFlow *)loginFlow; - - if (ssoFlow.identityProviders.count) - { - ssoFlowWithProviders = ssoFlow; - break; - } - } - } - - return ssoFlowWithProviders; -} - - (IBAction)onButtonPressed:(id)sender { if (sender == self.customServersTickButton) @@ -940,7 +907,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; self.authType = MXKAuthenticationTypeForgotPassword; } } - else if (sender == self.rightBarButtonItem) + else if (sender == self.navigationItem.rightBarButtonItem) { // Check whether a request is in progress if (!self.userInteractionEnabled) @@ -951,15 +918,15 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; else if (self.authType == MXKAuthenticationTypeLogin) { self.authType = MXKAuthenticationTypeRegister; - self.rightBarButtonItem.title = [VectorL10n authLogin]; + self.navigationItem.rightBarButtonItem.title = [VectorL10n authLogin]; } else { self.authType = MXKAuthenticationTypeLogin; - self.rightBarButtonItem.title = [VectorL10n authRegister]; + self.navigationItem.rightBarButtonItem.title = [VectorL10n authRegister]; } } - else if (sender == self.mainNavigationItem.leftBarButtonItem) + else if (sender == self.navigationItem.leftBarButtonItem) { if ([self.authInputsView isKindOfClass:AuthInputsView.class]) { @@ -1187,15 +1154,18 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; [self.submitButton setTitle:[VectorL10n authRegister] forState:UIControlStateNormal]; [self.submitButton setTitle:[VectorL10n authRegister] forState:UIControlStateHighlighted]; - self.mainNavigationItem.leftBarButtonItem = nil; + self.navigationItem.leftBarButtonItem = nil; } else { [self.submitButton setTitle:[VectorL10n authSubmit] forState:UIControlStateNormal]; [self.submitButton setTitle:[VectorL10n authSubmit] forState:UIControlStateHighlighted]; - UIBarButtonItem *leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back_icon"] style:UIBarButtonItemStylePlain target:self action:@selector(onButtonPressed:)]; - self.mainNavigationItem.leftBarButtonItem = leftBarButtonItem; + UIBarButtonItem *leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:VectorL10n.back + style:UIBarButtonItemStylePlain + target:self + action:@selector(onButtonPressed:)]; + self.navigationItem.leftBarButtonItem = leftBarButtonItem; } } @@ -1738,8 +1708,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0; - (void)updateSocialLoginViewVisibility { SocialLoginButtonMode socialLoginButtonMode = SocialLoginButtonModeContinue; - - BOOL showSocialLoginView = self.currentLoginSSOFlow ? YES : NO; + + BOOL showSocialLoginView = BuildSettings.authScreenShowSocialLoginSection && (self.currentLoginSSOFlow ? YES : NO); switch (self.authType) { diff --git a/Riot/Modules/Authentication/AuthenticationViewController.xib b/Riot/Modules/Authentication/AuthenticationViewController.xib index 4080c755d..c779ced89 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.xib +++ b/Riot/Modules/Authentication/AuthenticationViewController.xib @@ -34,14 +34,9 @@ - - - - - @@ -59,35 +54,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -129,7 +97,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift new file mode 100644 index 000000000..77ce45a0a --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift @@ -0,0 +1,214 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/ParticipantsInviteModal/OptionList OptionList +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import UIKit + +final class OptionListViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let estimatedRowHeight: CGFloat = 80.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var closeButton: UIButton! + @IBOutlet private weak var tableView: UITableView! + @IBOutlet private weak var bottomMargin: NSLayoutConstraint! + + // MARK: Private + + private var viewModel: OptionListViewModelProtocol! + private var theme: Theme! + private var keyboardAvoider: KeyboardAvoider? + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + private var options: [OptionListItemViewData] = [] + + // MARK: - Setup + + class func instantiate(with viewModel: OptionListViewModelProtocol) -> OptionListViewController { + let viewController = StoryboardScene.OptionListViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.tableView) + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.keyboardAvoider?.startAvoiding() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.keyboardAvoider?.stopAvoiding() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.backgroundColor + self.tableView.backgroundColor = theme.backgroundColor + + self.titleLabel.textColor = theme.colors.primaryContent + self.titleLabel.font = theme.fonts.title3SB + + self.closeButton.backgroundColor = theme.roomInputTextBorder + self.closeButton.tintColor = theme.noticeSecondaryColor + + self.tableView.reloadData() + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + self.setupTableView() + + self.closeButton.layer.masksToBounds = true + self.closeButton.layer.cornerRadius = self.closeButton.bounds.height / 2 + } + + private func setupTableView() { + self.tableView.separatorStyle = .none + self.tableView.rowHeight = UITableView.automaticDimension + self.tableView.estimatedRowHeight = Constants.estimatedRowHeight + self.tableView.allowsSelection = true + self.tableView.register(cellType: OptionListViewCell.self) + self.tableView.tableFooterView = UIView() + } + + private func render(viewState: OptionListViewState) { + switch viewState { + case .idle: + break + case .loading: + self.renderLoading() + case .loaded(let title, let options): + self.renderLoaded(title: title, options: options) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(title: String?, options: [OptionListItemViewData]) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.titleLabel.text = title + self.options = options + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + + // MARK: - Actions + + @IBAction private func closeAction(_ sender: Any) { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - OptionListViewModelViewDelegate +extension OptionListViewController: OptionListViewModelViewDelegate { + + func optionListViewModel(_ viewModel: OptionListViewModelProtocol, didUpdateViewState viewSate: OptionListViewState) { + self.render(viewState: viewSate) + } +} + +// MARK: - SlidingModalPresentable +extension OptionListViewController: SlidingModalPresentable { + func allowsDismissOnBackgroundTap() -> Bool { + return true + } + + func layoutHeightFittingWidth(_ width: CGFloat) -> CGFloat { + return tableView.frame.minY + Constants.estimatedRowHeight * CGFloat(options.count) + bottomMargin.constant + } +} + +// MARK: - UITableViewDataSource +extension OptionListViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return options.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let viewData = options[indexPath.row] + + let cell = tableView.dequeueReusableCell(for: indexPath, cellType: OptionListViewCell.self) + cell.update(theme: self.theme) + cell.update(with: viewData) + + return cell + } +} + +// MARK: - UITableViewDelegate +extension OptionListViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if options[indexPath.row].enabled { + viewModel.process(viewAction: .selected(indexPath.row)) + } + } +} diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewModel.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewModel.swift new file mode 100644 index 000000000..0cfe46c19 --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewModel.swift @@ -0,0 +1,66 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/ParticipantsInviteModal/OptionList OptionList +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class OptionListViewModel: OptionListViewModelProtocol { + + // MARK: - Properties + + // MARK: Private + + private let title: String? + private let options: [OptionListItemViewData] + + // MARK: Public + + weak var viewDelegate: OptionListViewModelViewDelegate? + weak var coordinatorDelegate: OptionListViewModelCoordinatorDelegate? + + private(set) var viewState: OptionListViewState = .idle { + didSet { + self.viewDelegate?.optionListViewModel(self, didUpdateViewState: viewState) + } + } + + // MARK: - Setup + + init(title: String?, options: [OptionListItemViewData]) { + self.title = title + self.options = options + } + + // MARK: - Public + + func process(viewAction: OptionListViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .selected(let index): + self.coordinatorDelegate?.optionListViewModel(self, didSelectOptionAt: index) + case .cancel: + self.coordinatorDelegate?.optionListViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + self.viewState = .loaded(title, options) + } +} diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewModelProtocol.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewModelProtocol.swift new file mode 100644 index 000000000..0d127ca05 --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewModelProtocol.swift @@ -0,0 +1,39 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/ParticipantsInviteModal/OptionList OptionList +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol OptionListViewModelViewDelegate: AnyObject { + func optionListViewModel(_ viewModel: OptionListViewModelProtocol, didUpdateViewState viewSate: OptionListViewState) +} + +protocol OptionListViewModelCoordinatorDelegate: AnyObject { + func optionListViewModel(_ viewModel: OptionListViewModelProtocol, didSelectOptionAt index: Int) + func optionListViewModelDidCancel(_ viewModel: OptionListViewModelProtocol) +} + +/// Protocol describing the view model used by `OptionListViewController` +protocol OptionListViewModelProtocol { + + var viewDelegate: OptionListViewModelViewDelegate? { get set } + var coordinatorDelegate: OptionListViewModelCoordinatorDelegate? { get set } + + func process(viewAction: OptionListViewAction) + + var viewState: OptionListViewState { get } +} diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewState.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewState.swift new file mode 100644 index 000000000..e64c36034 --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewState.swift @@ -0,0 +1,27 @@ +// File created from ScreenTemplate +// $ createScreen.sh Room/ParticipantsInviteModal/OptionList OptionList +/* + Copyright 2021 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// OptionListViewController view state +enum OptionListViewState { + case idle + case loading + case loaded(_ title: String?, _ options: [OptionListItemViewData]) + case error(Error) +} diff --git a/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift b/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..804c271b3 --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift @@ -0,0 +1,214 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objc protocol RoomParticipantsInviteCoordinatorBridgePresenterDelegate { + func roomParticipantsInviteCoordinatorBridgePresenterDidStartLoading(_ coordinatorBridgePresenter: RoomParticipantsInviteCoordinatorBridgePresenter) + func roomParticipantsInviteCoordinatorBridgePresenterDidEndLoading(_ coordinatorBridgePresenter: RoomParticipantsInviteCoordinatorBridgePresenter) + func roomParticipantsInviteCoordinatorBridgePresenterDidComplete(_ coordinatorBridgePresenter: RoomParticipantsInviteCoordinatorBridgePresenter) +} + +/// RoomParticipantsInviteCoordinatorBridgePresenter enables to start ContactsPickerCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class RoomParticipantsInviteCoordinatorBridgePresenter: NSObject { + // MARK: - Properties + + // MARK: Private + + private let session: MXSession? + private let room: MXRoom? + private let parentSpaceId: String? + private let currentSearchText: String? + private var actualParticipants: [Contact]? + private var invitedParticipants: [Contact]? + private var userParticipant: Contact? + private var roomOptions: [RoomOptionListItemViewData] = [] + + private weak var contactsPickerViewController: ContactsTableViewController? + private weak var currentAlert: UIAlertController? + private var contactPickerCoordinator: ContactsPickerCoordinator? + private var optionListCoordinator: OptionListCoordinator? + private var navigationRouter: NavigationRouterType? + + // MARK: Public + + weak var delegate: RoomParticipantsInviteCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(session: MXSession?, room: MXRoom?, parentSpaceId: String?) { + self.session = session + self.room = room + self.parentSpaceId = parentSpaceId + self.currentSearchText = nil + self.actualParticipants = nil + self.invitedParticipants = nil + self.userParticipant = nil + + super.init() + } + + init(session: MXSession?, room: MXRoom?, parentSpaceId: String?, currentSearchText: String? = nil, actualParticipants: [Contact]? = nil, invitedParticipants: [Contact]? = nil, userParticipant: Contact? = nil) { + self.session = session + self.room = room + self.parentSpaceId = parentSpaceId + self.currentSearchText = currentSearchText + self.actualParticipants = actualParticipants + self.invitedParticipants = invitedParticipants + self.userParticipant = userParticipant + + super.init() + } + + func present(from viewController: UIViewController, animated: Bool) { + guard let room = self.room else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] present: nil room found") + return + } + + if let navigationController = viewController.navigationController { + self.navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController) + } else { + self.navigationRouter = nil + } + + if let spaceId = self.parentSpaceId, let spaceRoom = self.session?.spaceService.getSpace(withId: spaceId)?.room { + self.presentRoomSelector(between: room, and: spaceRoom) + return + } + + self.pushContactsPicker(for: room) + } + + // MARK: - Private + + private class RoomOptionListItemViewData: OptionListItemViewData { + let room: MXRoom + + init(title: String? = nil, + detail: String? = nil, + image: UIImage? = nil, + room: MXRoom, + accessoryImage: UIImage? = Asset.Images.chevron.image, + enabled: Bool = true) { + self.room = room + super.init(title: title, detail: detail, image: image, accessoryImage: accessoryImage, enabled: enabled) + } + } + + private func presentRoomSelector(between room: MXRoom, and spaceRoom: MXRoom) { + let roomName = room.displayName ?? "" + let spaceName = spaceRoom.displayName ?? "" + + self.roomOptions = [ + RoomOptionListItemViewData(title: VectorL10n.roomInviteToSpaceOptionTitle(spaceName), + detail: VectorL10n.roomInviteToSpaceOptionDetail(spaceName, roomName), + image: Asset.Images.addParticipants.image, room: spaceRoom, + accessoryImage: Asset.Images.chevron.image), + RoomOptionListItemViewData(title: VectorL10n.roomInviteToRoomOptionTitle, + detail: VectorL10n.roomInviteToRoomOptionDetail(spaceName), + image: Asset.Images.addParticipants.image, room: room, + accessoryImage: Asset.Images.chevron.image) + ] + + let coordinator = OptionListCoordinator(parameters: OptionListCoordinatorParameters(title: VectorL10n.roomIntroCellAddParticipantsAction, options: self.roomOptions, navigationRouter: self.navigationRouter)) + coordinator.delegate = self + coordinator.start() + + self.optionListCoordinator = coordinator + } + + private func pushContactsPicker(for room: MXRoom) { + guard let session = self.session else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] pushContactsPicker: nil session found") + return + } + + canInvite(to: room) { [weak self] canInvite in + guard let self = self else { return } + + guard canInvite else { + let message = room.summary?.roomType == .space ? VectorL10n.spaceInviteNotEnoughPermission : VectorL10n.roomInviteNotEnoughPermission + let alert = UIAlertController(title: VectorL10n.spacesInvitePeople, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil)) + self.navigationRouter?.present(alert, animated: true) + return + } + + let coordinator = ContactsPickerCoordinator(session: session, + room: room, + initialSearchText: self.currentSearchText, + actualParticipants: self.actualParticipants, + invitedParticipants: self.invitedParticipants, + userParticipant: self.userParticipant, + navigationRouter: self.navigationRouter) + coordinator.delegate = self + coordinator.start() + + self.contactPickerCoordinator = coordinator + } + } + + private func canInvite(to room: MXRoom, completion: @escaping (Bool) -> Void) { + guard let userId = self.session?.myUserId else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] canInvite: userId not found") + completion(false) + return + } + + room.state { roomState in + guard let powerLevels = roomState?.powerLevels else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] canInvite: room powerLevels not found") + completion(false) + return + } + let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + + completion(userPowerLevel >= powerLevels.invite) + } + } +} + +extension RoomParticipantsInviteCoordinatorBridgePresenter: ContactsPickerCoordinatorDelegate { + func contactsPickerCoordinatorDidStartLoading(_ coordinator: ContactsPickerCoordinatorProtocol) { + delegate?.roomParticipantsInviteCoordinatorBridgePresenterDidStartLoading(self) + } + + func contactsPickerCoordinatorDidEndLoading(_ coordinator: ContactsPickerCoordinatorProtocol) { + delegate?.roomParticipantsInviteCoordinatorBridgePresenterDidEndLoading(self) + } + + func contactsPickerCoordinatorDidClose(_ coordinator: ContactsPickerCoordinatorProtocol) { + delegate?.roomParticipantsInviteCoordinatorBridgePresenterDidComplete(self) + } +} + +extension RoomParticipantsInviteCoordinatorBridgePresenter: OptionListCoordinatorDelegate { + func optionListCoordinator(_ coordinator: OptionListCoordinatorProtocol, didSelectOptionAt index: Int) { + optionListCoordinator = nil + self.pushContactsPicker(for: roomOptions[index].room) + } + + func optionListCoordinator(_ coordinator: OptionListCoordinatorProtocol, didCompleteWithUserDisplayName userDisplayName: String?) { + optionListCoordinator = nil + } + + func optionListCoordinatorDidCancel(_ coordinator: OptionListCoordinatorProtocol) { + optionListCoordinator = nil + } +} diff --git a/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkHeaderView.swift b/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkHeaderView.swift new file mode 100644 index 000000000..11d9f5394 --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkHeaderView.swift @@ -0,0 +1,77 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +@objc +protocol ShareInviteLinkHeaderViewDelegate: AnyObject { + func shareInviteLinkHeaderView(_ headerView: ShareInviteLinkHeaderView, didTapButton button: UIButton) +} + +@objcMembers +final class ShareInviteLinkHeaderView: UIView, NibLoadable, Themable { + + // MARK: - Constants + + private enum Constants { + static let buttonHighlightedAlpha: CGFloat = 0.2 + } + + // MARK: - Properties + + @IBOutlet private weak var button: CustomRoundedButton! + + weak var delegate: ShareInviteLinkHeaderViewDelegate? + + // MARK: - Setup + + static func instantiate() -> ShareInviteLinkHeaderView { + let view = ShareInviteLinkHeaderView.loadFromNib() + view.update(theme: ThemeService.shared().theme) + return view + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + button.setTitle(VectorL10n.shareInviteLinkAction, for: .normal) + button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside) + button.layer.cornerRadius = 8 + button.layer.borderWidth = 2 + } + + // MARK: - Public + + func update(theme: Theme) { + button.layer.borderColor = theme.tintColor.cgColor + button.setTitleColor(theme.tintColor, for: .normal) + button.setTitleColor(theme.tintColor.withAlphaComponent(Constants.buttonHighlightedAlpha), for: .highlighted) + button.vc_setBackgroundColor(theme.baseColor, for: .normal) + + let buttonImage = Asset.Images.shareActionButton.image.vc_tintedImage(usingColor: theme.tintColor) + + button.setImage(buttonImage, for: .normal) + } + + // MARK: - Action + + @objc private func buttonAction(_ sender: UIButton) { + delegate?.shareInviteLinkHeaderView(self, didTapButton: button) + } +} diff --git a/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkHeaderView.xib b/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkHeaderView.xib new file mode 100644 index 000000000..546eb5e93 --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkHeaderView.xib @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkPresenter.swift b/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkPresenter.swift new file mode 100644 index 000000000..8cbfead9d --- /dev/null +++ b/Riot/Modules/Room/ParticipantsInviteModal/ShareInviteLink/ShareInviteLinkPresenter.swift @@ -0,0 +1,87 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// ShareInviteLinkPresenter enables to share room alias to someone else +@objcMembers +final class ShareInviteLinkPresenter: NSObject { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Private + + private weak var presentingViewController: UIViewController? + private weak var sourceView: UIView? + + // MARK: - Public + + func present(for room: MXRoom, + from viewController: UIViewController, + sourceView: UIView?, + animated: Bool) { + + self.presentingViewController = viewController + self.sourceView = sourceView + + self.shareInvite(from: room) + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + self.presentingViewController?.dismiss(animated: animated, completion: completion) + } + + // MARK: - Private + + private func shareInvite(from room: MXRoom) { + + let shareText = self.buildShareText(with: room) + + // Set up activity view controller + let activityItems: [Any] = [ shareText ] + let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + + self.present(activityViewController, animated: true) + } + + private func buildShareText(with room: MXRoom) -> String { + let roomAliasOrId: String + if let alias = room.summary?.aliases?.first { + roomAliasOrId = alias + } else { + roomAliasOrId = room.matrixItemId + } + + if room.summary?.roomType == .space { + return VectorL10n.shareInviteLinkSpaceText(MXTools.permalink(toRoom: roomAliasOrId)) + } else { + return VectorL10n.shareInviteLinkRoomText(MXTools.permalink(toRoom: roomAliasOrId)) + } + } + + private func present(_ viewController: UIViewController, animated: Bool) { + + // Configure source view when view controller is presented with a popover + if let sourceView = self.sourceView, let popoverPresentationController = viewController.popoverPresentationController { + popoverPresentationController.sourceView = sourceView + popoverPresentationController.sourceRect = sourceView.bounds + } + + self.presentingViewController?.present(viewController, animated: animated, completion: nil) + } +} diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index 7f860168f..f789af407 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -30,8 +30,6 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { private let activityIndicatorPresenter: ActivityIndicatorPresenterType private var selectedEventId: String? - private var pollEditFormCoordinator: PollEditFormCoordinator? - private var roomDataSourceManager: MXKRoomDataSourceManager { return MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session) } @@ -74,11 +72,18 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { self.parameters = parameters self.selectedEventId = parameters.eventId - self.roomViewController = RoomViewController.instantiate() + if let threadId = parameters.threadId { + self.roomViewController = ThreadViewController.instantiate(withThreadId: threadId, + configuration: parameters.displayConfiguration) + } else { + self.roomViewController = RoomViewController.instantiate(with: parameters.displayConfiguration) + } self.activityIndicatorPresenter = ActivityIndicatorPresenter() + self.roomViewController.parentSpaceId = parameters.parentSpaceId + if #available(iOS 14, *) { - PollTimelineProvider.shared.session = parameters.session + TimelinePollProvider.shared.session = parameters.session } super.init() @@ -105,8 +110,13 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { if let previewData = self.parameters.previewData { self.loadRoomPreview(withData: previewData, completion: completion) + } else if let threadId = self.parameters.threadId { + self.loadRoom(withId: self.parameters.roomId, + andThreadId: threadId, + eventId: self.parameters.eventId, + completion: completion) } else if let eventId = self.selectedEventId { - self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion) + self.loadRoom(withId: self.parameters.roomId, andEventId: eventId, completion: completion) } else { self.loadRoom(withId: self.parameters.roomId, completion: completion) } @@ -126,7 +136,7 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { self.selectedEventId = eventId if self.hasStartedOnce { - self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion) + self.roomViewController.highlightAndDisplayEvent(eventId, completion: completion) } else { self.start(withCompletion: completion) } @@ -162,7 +172,7 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { }) } - private func loadRoom(withId roomId: String, and eventId: String, completion: (() -> Void)?) { + private func loadRoom(withId roomId: String, andEventId eventId: String, completion: (() -> Void)?) { // Present activity indicator when retrieving roomDataSource for given room ID self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false) @@ -170,6 +180,7 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { // Open the room on the requested event RoomDataSource.load(withRoomId: roomId, initialEventId: eventId, + threadId: nil, andMatrixSession: self.parameters.session) { [weak self] (dataSource) in guard let self = self else { @@ -192,12 +203,117 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { } } + private func loadRoom(withId roomId: String, andThreadId threadId: String, eventId: String?, completion: (() -> Void)?) { + + // Present activity indicator when retrieving roomDataSource for given room ID + self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false) + + // Open the thread on the requested event + ThreadDataSource.load(withRoomId: roomId, + initialEventId: eventId, + threadId: threadId, + andMatrixSession: self.parameters.session) { [weak self] (dataSource) in + + guard let self = self else { + return + } + + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + + guard let threadDataSource = dataSource as? ThreadDataSource else { + return + } + + threadDataSource.markTimelineInitialEvent = false + threadDataSource.highlightedEventId = eventId + self.roomViewController.displayRoom(threadDataSource) + + // Give the data source ownership to the room view controller. + self.roomViewController.hasRoomDataSourceOwnership = true + + completion?() + } + } + private func loadRoomPreview(withData previewData: RoomPreviewData, completion: (() -> Void)?) { self.roomViewController.displayRoomPreview(previewData) completion?() } + + private func startLocationCoordinatorWithEvent(_ event: MXEvent? = nil, bubbleData: MXKRoomBubbleCellDataStoring? = nil) { + guard #available(iOS 14.0, *) else { + return + } + + guard let navigationRouter = self.navigationRouter, + let mediaManager = mxSession?.mediaManager, + let user = mxSession?.myUser else { + MXLog.error("[RoomCoordinator] Invalid location sharing coordinator parameters. Returning.") + return + } + + var avatarData: AvatarInputProtocol + if event != nil, let bubbleData = bubbleData { + avatarData = AvatarInput(mxContentUri: bubbleData.senderAvatarUrl, + matrixItemId: bubbleData.senderId, + displayName: bubbleData.senderDisplayName) + } else { + avatarData = AvatarInput(mxContentUri: user.avatarUrl, + matrixItemId: user.userId, + displayName: user.displayname) + } + + var location: CLLocationCoordinate2D? + if let locationContent = event?.location { + location = CLLocationCoordinate2D(latitude: locationContent.latitude, longitude: locationContent.longitude) + } + + let parameters = LocationSharingCoordinatorParameters(roomDataSource: roomViewController.roomDataSource, + mediaManager: mediaManager, + avatarData: avatarData, + location: location) + + 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) { + guard #available(iOS 14.0, *) else { + return + } + + let parameters = PollEditFormCoordinatorParameters(room: roomViewController.roomDataSource.room, pollStartEvent: startEvent) + let coordinator = PollEditFormCoordinator(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() + } } // MARK: - RoomIdentifiable @@ -207,6 +323,10 @@ extension RoomCoordinator: RoomIdentifiable { return self.parameters.roomId } + var threadId: String? { + return self.parameters.threadId + } + var mxSession: MXSession? { self.parameters.session } @@ -224,8 +344,8 @@ extension RoomCoordinator: UIAdaptivePresentationControllerDelegate { // MARK: - RoomViewControllerDelegate extension RoomCoordinator: RoomViewControllerDelegate { - func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String) { - self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID) + func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String, eventId eventID: String?) { + self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID, eventId: eventID) } func roomViewController(_ roomViewController: RoomViewController, showMemberDetails roomMember: MXRoomMember) { @@ -257,14 +377,23 @@ extension RoomCoordinator: RoomViewControllerDelegate { } func roomViewControllerDidRequestPollCreationFormPresentation(_ roomViewController: RoomViewController) { - guard #available(iOS 14.0, *) else { - return + startEditPollCoordinator() + } + + func roomViewControllerDidRequestLocationSharingFormPresentation(_ roomViewController: RoomViewController) { + startLocationCoordinatorWithEvent() + } + + func roomViewController(_ roomViewController: RoomViewController, didRequestLocationPresentationFor event: MXEvent, bubbleData: MXKRoomBubbleCellDataStoring) { + startLocationCoordinatorWithEvent(event, bubbleData: bubbleData) + } + + func roomViewController(_ roomViewController: RoomViewController, locationShareActivityViewControllerFor event: MXEvent) -> UIActivityViewController? { + guard let location = event.location else { + return nil } - let parameters = PollEditFormCoordinatorParameters(navigationRouter: self.navigationRouter, room: roomViewController.roomDataSource.room) - pollEditFormCoordinator = PollEditFormCoordinator(parameters: parameters) - - pollEditFormCoordinator?.start() + return LocationSharingCoordinator.shareLocationActivityController(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) } func roomViewController(_ roomViewController: RoomViewController, canEndPollWithEventIdentifier eventIdentifier: String) -> Bool { @@ -272,7 +401,7 @@ extension RoomCoordinator: RoomViewControllerDelegate { return false } - return PollTimelineProvider.shared.pollTimelineCoordinatorForEventIdentifier(eventIdentifier)?.canEndPoll() ?? false + return TimelinePollProvider.shared.timelinePollCoordinatorForEventIdentifier(eventIdentifier)?.canEndPoll() ?? false } func roomViewController(_ roomViewController: RoomViewController, endPollWithEventIdentifier eventIdentifier: String) { @@ -280,6 +409,18 @@ extension RoomCoordinator: RoomViewControllerDelegate { return } - PollTimelineProvider.shared.pollTimelineCoordinatorForEventIdentifier(eventIdentifier)?.endPoll() + TimelinePollProvider.shared.timelinePollCoordinatorForEventIdentifier(eventIdentifier)?.endPoll() + } + + func roomViewController(_ roomViewController: RoomViewController, canEditPollWithEventIdentifier eventIdentifier: String) -> Bool { + guard #available(iOS 14.0, *) else { + return false + } + + return TimelinePollProvider.shared.timelinePollCoordinatorForEventIdentifier(eventIdentifier)?.canEditPoll() ?? false + } + + func roomViewController(_ roomViewController: RoomViewController, didRequestEditForPollWithStart startEvent: MXEvent) { + startEditPollCoordinator(startEvent: startEvent) } } diff --git a/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift b/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift index d6ec2aa4e..dea412d54 100644 --- a/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift @@ -18,7 +18,9 @@ import Foundation @objc protocol RoomCoordinatorBridgePresenterDelegate { func roomCoordinatorBridgePresenterDidLeaveRoom(_ bridgePresenter: RoomCoordinatorBridgePresenter) func roomCoordinatorBridgePresenterDidCancelRoomPreview(_ bridgePresenter: RoomCoordinatorBridgePresenter) - func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didSelectRoomWithId roomId: String) + func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, + didSelectRoomWithId roomId: String, + eventId: String?) func roomCoordinatorBridgePresenterDidDismissInteractively(_ bridgePresenter: RoomCoordinatorBridgePresenter) } @@ -31,19 +33,34 @@ class RoomCoordinatorBridgePresenterParameters: NSObject { /// The room identifier let roomId: String + /// The identifier of the parent space. `nil` for home space + let parentSpaceId: String? + /// If not nil, the room will be opened on this event. let eventId: String? + /// If not nil, specified thread will be opened. + let threadId: String? + + /// Display configuration for the room + let displayConfiguration: RoomDisplayConfiguration + /// The data for the room preview. let previewData: RoomPreviewData? init(session: MXSession, roomId: String, + parentSpaceId: String?, eventId: String?, + threadId: String?, + displayConfiguration: RoomDisplayConfiguration, previewData: RoomPreviewData?) { self.session = session self.roomId = roomId + self.parentSpaceId = parentSpaceId self.eventId = eventId + self.threadId = threadId + self.displayConfiguration = displayConfiguration self.previewData = previewData } } @@ -60,6 +77,12 @@ final class RoomCoordinatorBridgePresenter: NSObject { private let bridgeParameters: RoomCoordinatorBridgePresenterParameters private var coordinator: RoomCoordinator? + private var navigationType: NavigationType = .present + + private enum NavigationType { + case present + case push + } // MARK: Public @@ -75,8 +98,7 @@ final class RoomCoordinatorBridgePresenter: NSObject { // MARK: - Public func present(from viewController: UIViewController, animated: Bool) { - - let coordinator = self.createRoomCoordinator() + let coordinator = self.createRoomCoordinator(parentSpaceId: bridgeParameters.parentSpaceId) coordinator.delegate = self let presentable = coordinator.toPresentable() presentable.modalPresentationStyle = .formSheet @@ -84,24 +106,37 @@ final class RoomCoordinatorBridgePresenter: NSObject { coordinator.start() self.coordinator = coordinator + self.navigationType = .present } func push(from navigationController: UINavigationController, animated: Bool) { let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController) - let coordinator = self.createRoomCoordinator(with: navigationRouter) + let coordinator = self.createRoomCoordinator(with: navigationRouter, parentSpaceId: bridgeParameters.parentSpaceId) coordinator.delegate = self coordinator.start() // Will trigger view controller push self.coordinator = coordinator + self.navigationType = .push } func dismiss(animated: Bool, completion: (() -> Void)?) { guard let coordinator = self.coordinator else { return } - coordinator.toPresentable().dismiss(animated: animated) { + switch navigationType { + case .present: + coordinator.toPresentable().dismiss(animated: animated) { + self.coordinator = nil + + completion?() + } + case .push: + guard let navigationController = coordinator.toPresentable().navigationController else { + return + } + navigationController.popViewController(animated: animated) self.coordinator = nil completion?() @@ -110,14 +145,20 @@ final class RoomCoordinatorBridgePresenter: NSObject { // MARK: - Private - private func createRoomCoordinator(with navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController())) -> RoomCoordinator { + private func createRoomCoordinator(with navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController()), parentSpaceId: String?) -> RoomCoordinator { let coordinatorParameters: RoomCoordinatorParameters if let previewData = self.bridgeParameters.previewData { - coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, previewData: previewData) + coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, parentSpaceId: parentSpaceId, previewData: previewData) } else { - coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, session: self.bridgeParameters.session, roomId: self.bridgeParameters.roomId, eventId: self.bridgeParameters.eventId) + coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, + session: self.bridgeParameters.session, + parentSpaceId: parentSpaceId, + roomId: self.bridgeParameters.roomId, + eventId: self.bridgeParameters.eventId, + threadId: self.bridgeParameters.threadId, + displayConfiguration: self.bridgeParameters.displayConfiguration) } return RoomCoordinator(parameters: coordinatorParameters) @@ -127,8 +168,8 @@ final class RoomCoordinatorBridgePresenter: NSObject { // MARK: - RoomNotificationSettingsCoordinatorDelegate extension RoomCoordinatorBridgePresenter: RoomCoordinatorDelegate { - func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) { - self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId) + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String, eventId: String?) { + self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId, eventId: eventId) } func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) { diff --git a/Riot/Modules/Room/RoomCoordinatorParameters.swift b/Riot/Modules/Room/RoomCoordinatorParameters.swift index fbc9f3511..92b2ad8da 100644 --- a/Riot/Modules/Room/RoomCoordinatorParameters.swift +++ b/Riot/Modules/Room/RoomCoordinatorParameters.swift @@ -34,9 +34,18 @@ struct RoomCoordinatorParameters { /// The room identifier let roomId: String + /// The identifier of the parent space. `nil` for home space + let parentSpaceId: String? + /// If not nil, the room will be opened on this event. let eventId: String? + /// If not nil, specified thread will be opened. + let threadId: String? + + /// Display configuration for the room + let displayConfiguration: RoomDisplayConfiguration + /// The data for the room preview. let previewData: RoomPreviewData? @@ -46,13 +55,19 @@ struct RoomCoordinatorParameters { navigationRouterStore: NavigationRouterStoreProtocol?, session: MXSession, roomId: String, + parentSpaceId: String?, eventId: String?, + threadId: String?, + displayConfiguration: RoomDisplayConfiguration, previewData: RoomPreviewData?) { self.navigationRouter = navigationRouter self.navigationRouterStore = navigationRouterStore self.session = session self.roomId = roomId + self.parentSpaceId = parentSpaceId self.eventId = eventId + self.threadId = threadId + self.displayConfiguration = displayConfiguration self.previewData = previewData } @@ -60,17 +75,37 @@ struct RoomCoordinatorParameters { init(navigationRouter: NavigationRouterType? = nil, navigationRouterStore: NavigationRouterStoreProtocol? = nil, session: MXSession, + parentSpaceId: String?, roomId: String, - eventId: String? = nil) { + eventId: String? = nil, + threadId: String? = nil, + displayConfiguration: RoomDisplayConfiguration = .default) { - self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: session, roomId: roomId, eventId: eventId, previewData: nil) + self.init(navigationRouter: navigationRouter, + navigationRouterStore: navigationRouterStore, + session: session, + roomId: roomId, + parentSpaceId: parentSpaceId, + eventId: eventId, + threadId: threadId, + displayConfiguration: displayConfiguration, + previewData: nil) } /// Init to present a room preview init(navigationRouter: NavigationRouterType? = nil, navigationRouterStore: NavigationRouterStoreProtocol? = nil, + parentSpaceId: String?, previewData: RoomPreviewData) { - self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: previewData.mxSession, roomId: previewData.roomId, eventId: nil, previewData: previewData) + self.init(navigationRouter: navigationRouter, + navigationRouterStore: navigationRouterStore, + session: previewData.mxSession, + roomId: previewData.roomId, + parentSpaceId: parentSpaceId, + eventId: nil, + threadId: nil, + displayConfiguration: .default, + previewData: previewData) } } diff --git a/Riot/Modules/Room/RoomCoordinatorProtocol.swift b/Riot/Modules/Room/RoomCoordinatorProtocol.swift index 1c30f02ec..9fe774ce6 100644 --- a/Riot/Modules/Room/RoomCoordinatorProtocol.swift +++ b/Riot/Modules/Room/RoomCoordinatorProtocol.swift @@ -21,7 +21,7 @@ import Foundation protocol RoomCoordinatorDelegate: AnyObject { func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) - func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String, eventId: String?) func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) } diff --git a/Riot/Modules/Room/RoomDisplayConfiguration.swift b/Riot/Modules/Room/RoomDisplayConfiguration.swift new file mode 100644 index 000000000..1a5ec3481 --- /dev/null +++ b/Riot/Modules/Room/RoomDisplayConfiguration.swift @@ -0,0 +1,50 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objcMembers +class RoomDisplayConfiguration: NSObject { + + let callsEnabled: Bool + + let integrationsEnabled: Bool + + let jitsiWidgetRemoverEnabled: Bool + + let sendingPollsEnabled: Bool + + init(callsEnabled: Bool, + integrationsEnabled: Bool, + jitsiWidgetRemoverEnabled: Bool, + sendingPollsEnabled: Bool) { + self.callsEnabled = callsEnabled + self.integrationsEnabled = integrationsEnabled + self.jitsiWidgetRemoverEnabled = jitsiWidgetRemoverEnabled + self.sendingPollsEnabled = sendingPollsEnabled + super.init() + } + + static let `default`: RoomDisplayConfiguration = RoomDisplayConfiguration(callsEnabled: true, + integrationsEnabled: true, + jitsiWidgetRemoverEnabled: true, + sendingPollsEnabled: true) + + static let forThreads: RoomDisplayConfiguration = RoomDisplayConfiguration(callsEnabled: false, + integrationsEnabled: false, + jitsiWidgetRemoverEnabled: false, + sendingPollsEnabled: false) +} diff --git a/Riot/Modules/Room/RoomIdentifiable.swift b/Riot/Modules/Room/RoomIdentifiable.swift index 2ccac42d7..b86770360 100644 --- a/Riot/Modules/Room/RoomIdentifiable.swift +++ b/Riot/Modules/Room/RoomIdentifiable.swift @@ -20,5 +20,6 @@ import Foundation /// Useful to identify existing objects that should be removed when the user leaves a room for example. protocol RoomIdentifiable { var roomId: String? { get } + var threadId: String? { get } var mxSession: MXSession? { get } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index ccfe76a7e..6386ba54a 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -28,6 +28,7 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { private let navigationRouter: NavigationRouterType private let session: MXSession private let room: MXRoom + private let parentSpaceId: String? private let initialSection: RoomInfoSection private weak var roomSettingsViewController: RoomSettingsViewController? @@ -38,10 +39,13 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { participants.finalizeInit() participants.enableMention = true participants.mxRoom = self.room + participants.parentSpaceId = self.parentSpaceId participants.delegate = self + participants.screenTimer = AnalyticsScreenTimer(screen: .roomMembers) let files = RoomFilesViewController() files.finalizeInit() + files.screenTimer = AnalyticsScreenTimer(screen: .roomUploads) MXKRoomDataSource.load(withRoomId: self.room.roomId, andMatrixSession: self.session) { (dataSource) in guard let dataSource = dataSource as? MXKRoomDataSource else { return } dataSource.filterMessagesWithURL = true @@ -52,6 +56,7 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { let settings = RoomSettingsViewController() settings.finalizeInit() + settings.screenTimer = AnalyticsScreenTimer(screen: .roomSettings) settings.initWith(self.session, andRoomId: self.room.roomId) if self.room.isDirect { @@ -95,6 +100,7 @@ final class RoomInfoCoordinator: NSObject, RoomInfoCoordinatorType { self.session = parameters.session self.room = parameters.room + self.parentSpaceId = parameters.parentSpaceId self.initialSection = parameters.initialSection } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorParameters.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorParameters.swift index 42a4aab5b..57261016f 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorParameters.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorParameters.swift @@ -29,16 +29,18 @@ class RoomInfoCoordinatorParameters: NSObject { let session: MXSession let room: MXRoom + let parentSpaceId: String? let initialSection: RoomInfoSection - init(session: MXSession, room: MXRoom, initialSection: RoomInfoSection) { + init(session: MXSession, room: MXRoom, parentSpaceId: String?, initialSection: RoomInfoSection) { self.session = session self.room = room + self.parentSpaceId = parentSpaceId self.initialSection = initialSection super.init() } - convenience init(session: MXSession, room: MXRoom) { - self.init(session: session, room: room, initialSection: .none) + convenience init(session: MXSession, room: MXRoom, parentSpaceId: String?) { + self.init(session: session, room: room, parentSpaceId: parentSpaceId, initialSection: .none) } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 3590b5526..78b5af425 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -40,6 +40,7 @@ final class RoomInfoListViewController: UIViewController { private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! private var isRoomDirect: Bool = false + private var screenTimer = AnalyticsScreenTimer(screen: .roomDetails) private lazy var closeButton: CloseButton = { let button = CloseButton() @@ -128,12 +129,22 @@ final class RoomInfoListViewController: UIViewController { return self.theme.statusBarStyle } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + screenTimer.start() + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() mainTableView.vc_relayoutHeaderView() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + screenTimer.stop() + } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { coordinator.animate(alongsideTransition: {_ in self.basicInfoView.updateTrimmingOnTopic() diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 74941c31e..db8a14e63 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -30,6 +30,7 @@ @class BadgeLabel; @class UniversalLinkParameters; @protocol RoomViewControllerDelegate; +@class RoomDisplayConfiguration; NS_ASSUME_NONNULL_BEGIN @@ -72,12 +73,22 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification; */ @property (nonatomic, readonly, nullable) RoomPreviewData *roomPreviewData; +/** + Display configuration for the room view controller. + */ +@property (nonatomic, readonly) RoomDisplayConfiguration *displayConfiguration; + /** Tell whether a badge must be added next to the chevron (back button) showing number of unread rooms. YES by default. */ @property (nonatomic) BOOL showMissedDiscussionsBadge; +/** + ID of the parent space. `nil` for home space. + */ +@property (nonatomic, nullable) NSString *parentSpaceId; + /** Display the preview of a room that is unknown for the user. @@ -94,12 +105,22 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification; - (IBAction)scrollToBottomAction:(id)sender; +/** + Highlights an event in the timeline. Does not reload room data source if the event is already loaded. Otherwise, loads a new data source around the given event. + + @param eventId Identifier of the event to be highlighted. + @param completion Completion block to be called at the end of process. Optional. + */ +- (void)highlightAndDisplayEvent:(NSString *)eventId completion:(nullable void (^)(void))completion; + /** Creates and returns a new `RoomViewController` object. + @param configuration display configuration for the room view controller. + @return An initialized `RoomViewController` object. */ -+ (instancetype)instantiate; ++ (instancetype)instantiateWithConfiguration:(RoomDisplayConfiguration *)configuration; @end @@ -129,9 +150,11 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification; @param roomViewController the `RoomViewController` instance. @param roomID the selected roomId + @param eventID the selected eventId */ - (void)roomViewController:(RoomViewController *)roomViewController - showRoomWithId:(NSString *)roomID; + showRoomWithId:(NSString *)roomID + eventId:(nullable NSString *)eventID; /** Tells the delegate that the user wants to start a direct chat with a user. @@ -183,12 +206,39 @@ handleUniversalLinkWithParameters:(UniversalLinkParameters*)parameters; */ - (void)roomViewControllerDidRequestPollCreationFormPresentation:(RoomViewController *)roomViewController; +/** + Ask the coordinator to invoke the location sharing form coordinator. + + @param roomViewController the `RoomViewController` instance. + */ +- (void)roomViewControllerDidRequestLocationSharingFormPresentation:(RoomViewController *)roomViewController; + +/** + Ask the coordinator to invoke the location sharing form coordinator. + + @param roomViewController the `RoomViewController` instance. + @param event the event containing location information + @param bubbleData the bubble data containing sender details + */ +- (void)roomViewController:(RoomViewController *)roomViewController +didRequestLocationPresentationForEvent:(MXEvent *)event + bubbleData:(id)bubbleData; + +- (nullable UIActivityViewController *)roomViewController:(RoomViewController *)roomViewController + locationShareActivityViewControllerForEvent:(MXEvent *)event; + - (BOOL)roomViewController:(RoomViewController *)roomViewController canEndPollWithEventIdentifier:(NSString *)eventIdentifier; - (void)roomViewController:(RoomViewController *)roomViewController endPollWithEventIdentifier:(NSString *)eventIdentifier; +- (BOOL)roomViewController:(RoomViewController *)roomViewController +canEditPollWithEventIdentifier:(NSString *)eventIdentifier; + +- (void)roomViewController:(RoomViewController *)roomViewController +didRequestEditForPollWithStartEvent:(MXEvent *)startEvent; + @end NS_ASSUME_NONNULL_END diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 46c83de52..2f487177e 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -54,55 +54,7 @@ #import "JitsiViewController.h" #import "RoomEmptyBubbleCell.h" - -#import "RoomIncomingTextMsgBubbleCell.h" -#import "RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h" -#import "RoomIncomingTextMsgWithPaginationTitleBubbleCell.h" -#import "RoomIncomingTextMsgWithoutSenderNameBubbleCell.h" -#import "RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" -#import "RoomIncomingAttachmentBubbleCell.h" -#import "RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h" -#import "RoomIncomingAttachmentWithPaginationTitleBubbleCell.h" - -#import "RoomIncomingEncryptedTextMsgBubbleCell.h" -#import "RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h" -#import "RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h" -#import "RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h" -#import "RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" -#import "RoomIncomingEncryptedAttachmentBubbleCell.h" -#import "RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h" -#import "RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h" - -#import "RoomOutgoingTextMsgBubbleCell.h" -#import "RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h" -#import "RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h" -#import "RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h" -#import "RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" -#import "RoomOutgoingAttachmentBubbleCell.h" -#import "RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h" -#import "RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h" - -#import "RoomOutgoingEncryptedTextMsgBubbleCell.h" -#import "RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h" -#import "RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h" -#import "RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h" -#import "RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" -#import "RoomOutgoingEncryptedAttachmentBubbleCell.h" -#import "RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h" -#import "RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h" - -#import "RoomMembershipBubbleCell.h" -#import "RoomMembershipWithPaginationTitleBubbleCell.h" -#import "RoomMembershipCollapsedBubbleCell.h" -#import "RoomMembershipCollapsedWithPaginationTitleBubbleCell.h" #import "RoomMembershipExpandedBubbleCell.h" -#import "RoomMembershipExpandedWithPaginationTitleBubbleCell.h" -#import "RoomCreationWithPaginationCollapsedBubbleCell.h" -#import "RoomCreationCollapsedBubbleCell.h" - -#import "RoomSelectedStickerBubbleCell.h" -#import "RoomPredecessorBubbleCell.h" - #import "MXKRoomBubbleTableViewCell+Riot.h" #import "AvatarGenerator.h" @@ -130,6 +82,8 @@ #import "MXSDKOptions.h" +#import "RoomTimelineCellProvider.h" + #import "GeneratedInterface-Swift.h" NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification"; @@ -139,7 +93,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @interface RoomViewController () + RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, UserSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate> { // The preview header @@ -224,6 +178,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Time to display notification content in the timeline MXTaskProfile *notificationTaskProfile; + + // Reference to thread list bar button item, to update it easily later + BadgedBarButtonItem *threadListBarButtonItem; } @property (nonatomic, weak) IBOutlet UIView *overlayContainerView; @@ -244,6 +201,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @property (nonatomic, strong) RoomCreationModalCoordinatorBridgePresenter *roomCreationModalCoordinatorBridgePresenter; @property (nonatomic, strong) RoomInfoCoordinatorBridgePresenter *roomInfoCoordinatorBridgePresenter; @property (nonatomic, strong) CustomSizedPresentationController *customSizedPresentationController; +@property (nonatomic, strong) RoomParticipantsInviteCoordinatorBridgePresenter *participantsInvitePresenter; +@property (nonatomic, strong) ThreadsCoordinatorBridgePresenter *threadsBridgePresenter; @property (nonatomic, getter=isActivitiesViewExpanded) BOOL activitiesViewExpanded; @property (nonatomic, getter=isScrollToBottomHidden) BOOL scrollToBottomHidden; @property (nonatomic, getter=isMissedDiscussionsBadgeHidden) BOOL missedDiscussionsBadgeHidden; @@ -252,10 +211,19 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; @property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter; @property (nonatomic, strong) ShareManager *shareManager; +@property (nonatomic, strong) EventMenuBuilder *eventMenuBuilder; @property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator; @property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView; +@property (nonatomic, readwrite) RoomDisplayConfiguration *displayConfiguration; +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + +// When layout of the screen changes (e.g. height), we no longer know whether +// to autoscroll to the bottom again or not. Instead we need to capture the +// scroll state just before the layout change, and restore it after the layout. +@property (nonatomic) BOOL shouldScrollToBottomAfterLayout; + @end @implementation RoomViewController @@ -271,14 +239,29 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; + (instancetype)roomViewController { - return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; + RoomViewController *controller = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) + bundle:[NSBundle bundleForClass:self.class]]; + controller.displayConfiguration = [RoomDisplayConfiguration default]; + return controller; } -+ (instancetype)instantiate ++ (instancetype)instantiateWithConfiguration:(RoomDisplayConfiguration *)configuration { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; - return [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"]; + NSString *storyboardId = [NSString stringWithFormat:@"%@StoryboardId", self.className]; + RoomViewController *controller = [storyboard instantiateViewControllerWithIdentifier:storyboardId]; + controller.displayConfiguration = configuration; + return controller; +} + ++ (NSString *)className +{ + NSString *result = NSStringFromClass(self.class); + if ([result containsString:@"."]) + { + result = [result componentsSeparatedByString:@"."].lastObject; + } + return result; } #pragma mark - @@ -325,6 +308,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; formattedBodyParser = [FormattedBodyParser new]; + self.eventMenuBuilder = [EventMenuBuilder new]; _showMissedDiscussionsBadge = YES; _scrollToBottomHidden = YES; @@ -338,6 +322,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; _voiceMessageController = [[VoiceMessageController alloc] initWithThemeService:ThemeService.shared mediaServiceProvider:VoiceMessageMediaServiceProvider.sharedProvider]; self.voiceMessageController.delegate = self; + + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRoom]; } - (void)viewDidLoad @@ -345,79 +331,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [super viewDidLoad]; // Register first customized cell view classes used to render bubbles - [self.bubblesTableView registerClass:RoomIncomingTextMsgBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingAttachmentBubbleCell.class forCellReuseIdentifier:RoomIncomingAttachmentBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomIncomingEncryptedTextMsgBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedAttachmentBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedAttachmentBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomOutgoingAttachmentBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingTextMsgBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomOutgoingEncryptedAttachmentBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedAttachmentBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomEmptyBubbleCell.class forCellReuseIdentifier:RoomEmptyBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomMembershipBubbleCell.class forCellReuseIdentifier:RoomMembershipBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomMembershipWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomMembershipCollapsedBubbleCell.class forCellReuseIdentifier:RoomMembershipCollapsedBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomMembershipCollapsedWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipCollapsedWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomMembershipExpandedBubbleCell.class forCellReuseIdentifier:RoomMembershipExpandedBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomMembershipExpandedWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipExpandedWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomSelectedStickerBubbleCell.class forCellReuseIdentifier:RoomSelectedStickerBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomPredecessorBubbleCell.class forCellReuseIdentifier:RoomPredecessorBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:KeyVerificationRequestStatusBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:KeyVerificationConclusionBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:KeyVerificationConclusionWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomCreationCollapsedBubbleCell.class forCellReuseIdentifier:RoomCreationCollapsedBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomCreationWithPaginationCollapsedBubbleCell.class forCellReuseIdentifier:RoomCreationWithPaginationCollapsedBubbleCell.defaultReuseIdentifier]; - - // call cells - [self.bubblesTableView registerClass:RoomDirectCallStatusBubbleCell.class forCellReuseIdentifier:RoomDirectCallStatusBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:RoomGroupCallStatusBubbleCell.class forCellReuseIdentifier:RoomGroupCallStatusBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:RoomCreationIntroCell.class forCellReuseIdentifier:RoomCreationIntroCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerNib:RoomTypingBubbleCell.nib forCellReuseIdentifier:RoomTypingBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:VoiceMessageBubbleCell.class forCellReuseIdentifier:VoiceMessageBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:VoiceMessageWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceMessageWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:VoiceMessageWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceMessageWithPaginationTitleBubbleCell.defaultReuseIdentifier]; - - [self.bubblesTableView registerClass:PollBubbleCell.class forCellReuseIdentifier:PollBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:PollWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:PollWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; - [self.bubblesTableView registerClass:PollWithPaginationTitleBubbleCell.class forCellReuseIdentifier:PollWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [[RoomTimelineConfiguration shared].currentStyle.cellProvider registerCellsForTableView:self.bubblesTableView]; [self vc_removeBackTitle]; @@ -549,6 +463,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.scrollToBottomBadgeLabel.badgeColor = ThemeService.shared.theme.tintColor; + [self updateThreadListBarButtonBadgeWith:self.mainSession.threadingService]; + [threadListBarButtonItem updateWithTheme:ThemeService.shared.theme]; + [self setNeedsStatusBarAppearanceUpdate]; } @@ -567,9 +484,6 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { [super viewWillAppear:animated]; - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"ChatRoom"]; - // Refresh the room title view [self refreshRoomTitle]; @@ -610,8 +524,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self.roomDataSource reload]; [LegacyAppDelegate theDelegate].lastNavigatedRoomIdFromPush = nil; - notificationTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:AnalyticsNoficationsTimeToDisplayContent - category:AnalyticsNoficationsCategory]; + notificationTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:MXTaskProfileNameNotificationsOpenEvent]; } } @@ -636,6 +549,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self cancelEventSelection]; } } + [self cancelEventHighlight]; // Hide preview header to restore navigation bar settings [self showPreviewHeader:NO]; @@ -707,6 +621,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; hasJitsiCall = NO; [self reloadBubblesTable:YES]; } + + // Screen tracking + [self.screenTimer start]; } - (void)viewDidDisappear:(BOOL)animated @@ -742,6 +659,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; hasJitsiCall = YES; [self reloadBubblesTable:YES]; } + + [self.screenTimer stop]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + self.shouldScrollToBottomAfterLayout = self.isBubblesTableScrollViewAtTheBottom; } - (void)viewDidLayoutSubviews @@ -809,9 +733,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.edgesForExtendedLayout = UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight; } - // stay at the bottom if already was - if (self.isBubblesTableScrollViewAtTheBottom) + // re-scroll to the bottom, if at bottom before the most recent layout + if (self.shouldScrollToBottomAfterLayout) { + self.shouldScrollToBottomAfterLayout = NO; [self scrollBubblesTableViewToBottomAnimated:NO]; } @@ -983,6 +908,21 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; #pragma mark - Override MXKRoomViewController +- (void)addMatrixSession:(MXSession *)mxSession +{ + [super addMatrixSession:mxSession]; + + [mxSession.threadingService addDelegate:self]; + [self updateThreadListBarButtonBadgeWith:mxSession.threadingService]; +} + +- (void)removeMatrixSession:(MXSession *)mxSession +{ + [mxSession.threadingService removeDelegate:self]; + + [super removeMatrixSession:mxSession]; +} + - (void)onMatrixSessionChange { [super onMatrixSessionChange]; @@ -1499,8 +1439,40 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; return item; } +- (UIBarButtonItem *)threadMoreBarButtonItem +{ + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"room_context_menu_more"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(onButtonPressed:)]; + item.accessibilityLabel = [VectorL10n roomAccessibilityThreadMore]; + + return item; +} + +- (BadgedBarButtonItem *)threadListBarButtonItem +{ + UIButton *button = [UIButton new]; + UIImage *icon = [[UIImage imageNamed:@"threads_icon"] vc_resizedWith:CGSizeMake(21, 21)]; + button.contentEdgeInsets = UIEdgeInsetsMake(4, 8, 4, 8); + [button setImage:icon + forState:UIControlStateNormal]; + [button addTarget:self + action:@selector(onThreadListTapped:) + forControlEvents:UIControlEventTouchUpInside]; + button.accessibilityLabel = [VectorL10n roomAccessibilityThreads]; + + return [[BadgedBarButtonItem alloc] initWithBaseButton:button + theme:ThemeService.shared.theme]; +} + - (void)setupRemoveJitsiWidgetRemoveView { + if (!self.displayConfiguration.jitsiWidgetRemoverEnabled) + { + return; + } + self.removeJitsiWidgetView = [RemoveJitsiWidgetView instantiate]; self.removeJitsiWidgetView.delegate = self; @@ -1543,6 +1515,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (BOOL)supportCallOption { + if (!self.displayConfiguration.callsEnabled) + { + return NO; + } BOOL callOptionAllowed = (self.roomDataSource.room.isDirect && RiotSettings.shared.roomScreenAllowVoIPForDirectRoom) || (!self.roomDataSource.room.isDirect && RiotSettings.shared.roomScreenAllowVoIPForNonDirectRoom); return callOptionAllowed && BuildSettings.allowVoIPUsage && self.roomDataSource.mxSession.callManager && self.roomDataSource.room.summary.membersCount.joined >= 2; } @@ -1706,12 +1682,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } [rightBarButtonItems addObject:item]; } - - // Do not change title view class here if the expanded header is visible. - [self setRoomTitleViewClass:RoomTitleView.class]; - ((RoomTitleView*)self.titleView).tapGestureDelegate = self; } + // Do not change title view class here if the expanded header is visible. + [self setRoomTitleViewClass:RoomTitleView.class]; + ((RoomTitleView*)self.titleView).tapGestureDelegate = self; + MXKImageView *userPictureView = ((RoomTitleView*)self.titleView).pictureView; // Set user picture in input toolbar @@ -1721,6 +1697,28 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } [self refreshMissedDiscussionsCount:YES]; + + if (RiotSettings.shared.enableThreads) + { + if (self.roomDataSource.threadId) + { + // in a thread + if (rightBarButtonItems == nil) + { + rightBarButtonItems = [NSMutableArray new]; + } + UIBarButtonItem *itemThreadMore = [self threadMoreBarButtonItem]; + [rightBarButtonItems insertObject:itemThreadMore atIndex:0]; + } + else + { + // in a regular timeline + BadgedBarButtonItem *itemThreadList = [self threadListBarButtonItem]; + [rightBarButtonItems insertObject:itemThreadList atIndex:0]; + threadListBarButtonItem = itemThreadList; + [self updateThreadListBarButtonBadgeWith:self.mainSession.threadingService]; + } + } } self.navigationItem.rightBarButtonItems = rightBarButtonItems; @@ -1956,7 +1954,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)showAddParticipants { - [self showRoomInfoWithInitialSection:RoomInfoSectionAddParticipants]; + self.participantsInvitePresenter = [[RoomParticipantsInviteCoordinatorBridgePresenter alloc] initWithSession:self.roomDataSource.mxSession room:self.roomDataSource.room parentSpaceId:self.parentSpaceId]; + self.participantsInvitePresenter.delegate = self; + [self.participantsInvitePresenter presentFrom:self animated:YES]; } - (void)showRoomTopicChange @@ -1971,7 +1971,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)showRoomInfoWithInitialSection:(RoomInfoSection)roomInfoSection { - RoomInfoCoordinatorParameters *parameters = [[RoomInfoCoordinatorParameters alloc] initWithSession:self.roomDataSource.mxSession room:self.roomDataSource.room initialSection:roomInfoSection]; + RoomInfoCoordinatorParameters *parameters = [[RoomInfoCoordinatorParameters alloc] initWithSession:self.roomDataSource.mxSession room:self.roomDataSource.room parentSpaceId:self.parentSpaceId initialSection:roomInfoSection]; self.roomInfoCoordinatorBridgePresenter = [[RoomInfoCoordinatorBridgePresenter alloc] initWithParameters:parameters]; @@ -2017,7 +2017,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self roomInputToolbarViewDidTapFileUpload]; }]]; } - if (RiotSettings.shared.roomScreenAllowPollsAction) + if (BuildSettings.pollsEnabled && self.displayConfiguration.sendingPollsEnabled) { [actionItems addObject:[[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_poll"] andAction:^{ MXStrongifyAndReturnIfNil(self); @@ -2027,6 +2027,16 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self.delegate roomViewControllerDidRequestPollCreationFormPresentation:self]; }]]; } + if (BuildSettings.locationSharingEnabled) + { + [actionItems addObject:[[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_location"] andAction:^{ + MXStrongifyAndReturnIfNil(self); + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + ((RoomInputToolbarView *) self.inputToolbarView).actionMenuOpened = NO; + } + [self.delegate roomViewControllerDidRequestLocationSharingFormPresentation:self]; + }]]; + } if (RiotSettings.shared.roomScreenAllowCameraAction) { [actionItems addObject:[[RoomActionItem alloc] initWithImage:[UIImage imageNamed:@"action_camera"] andAction:^{ @@ -2153,6 +2163,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; [roomInputToolbarView sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset]; }]; + compressionPrompt.popoverPresentationController.sourceView = roomInputToolbarView.attachMediaButton; + compressionPrompt.popoverPresentationController.sourceRect = roomInputToolbarView.attachMediaButton.bounds; [self presentViewController:compressionPrompt animated:YES completion:nil]; } @@ -2168,7 +2180,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (self.delegate) { - [self.delegate roomViewController:self showRoomWithId:roomId]; + [self.delegate roomViewController:self showRoomWithId:roomId eventId:nil]; } else { @@ -2615,14 +2627,23 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (Class)cellViewClassForCellData:(MXKCellData*)cellData { - Class cellViewClass = nil; - BOOL showEncryptionBadge = NO; + RoomTimelineCellIdentifier cellIdentifier = [self cellIdentifierForCellData:cellData andRoomDataSource:customizedRoomDataSource]; + RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared]; + + return [timelineConfiguration.currentStyle.cellProvider cellViewClassForCellIdentifier:cellIdentifier];; +} + +- (RoomTimelineCellIdentifier)cellIdentifierForCellData:(MXKCellData*)cellData andRoomDataSource:(RoomDataSource *)customizedRoomDataSource; +{ // Sanity check if (![cellData conformsToProtocol:@protocol(MXKRoomBubbleCellDataStoring)]) { - return nil; + return RoomTimelineCellIdentifierUnknown; } + + BOOL showEncryptionBadge = NO; + RoomTimelineCellIdentifier cellIdentifier; id bubbleData = (id)cellData; @@ -2637,27 +2658,27 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Select the suitable table view cell class, by considering first the empty bubble cell. if (bubbleData.hasNoDisplay) { - cellViewClass = RoomEmptyBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierEmpty; } else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreationIntro) { - cellViewClass = RoomCreationIntroCell.class; + cellIdentifier = RoomTimelineCellIdentifierRoomCreationIntro; } else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor) { - cellViewClass = RoomPredecessorBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierRoomPredecessor; } else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval) { - cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class : KeyVerificationIncomingRequestApprovalBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierKeyVerificationIncomingRequestApprovalWithPaginationTitle : RoomTimelineCellIdentifierKeyVerificationIncomingRequestApproval; } else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequest) { - cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class : KeyVerificationRequestStatusBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierKeyVerificationRequestStatusWithPaginationTitle : RoomTimelineCellIdentifierKeyVerificationRequestStatus; } else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationConclusion) { - cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationConclusionWithPaginationTitleBubbleCell.class : KeyVerificationConclusionBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierKeyVerificationConclusionWithPaginationTitle : RoomTimelineCellIdentifierKeyVerificationConclusion; } else if (bubbleData.tag == RoomBubbleCellDataTagMembership) { @@ -2665,65 +2686,116 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (bubbleData.nextCollapsableCellData) { - cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipCollapsedWithPaginationTitleBubbleCell.class : RoomMembershipCollapsedBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierMembershipCollapsedWithPaginationTitle : RoomTimelineCellIdentifierMembershipCollapsed; } else { // Use a normal membership cell for a single membership event - cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell.class : RoomMembershipBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierMembershipWithPaginationTitle : RoomTimelineCellIdentifierMembership; } } else if (bubbleData.collapsedAttributedTextMessage) { // The cell (and its series) is not collapsed but this cell is the first // of the series. So, use the cell with the "collapse" button. - cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipExpandedWithPaginationTitleBubbleCell.class : RoomMembershipExpandedBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierMembershipExpandedWithPaginationTitle : RoomTimelineCellIdentifierMembershipExpanded; } else { - cellViewClass = bubbleData.isPaginationFirstBubble ? RoomMembershipWithPaginationTitleBubbleCell.class : RoomMembershipBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierMembershipWithPaginationTitle : RoomTimelineCellIdentifierMembership; } } else if (bubbleData.tag == RoomBubbleCellDataTagRoomCreateConfiguration) { - cellViewClass = bubbleData.isPaginationFirstBubble ? RoomCreationWithPaginationCollapsedBubbleCell.class : RoomCreationCollapsedBubbleCell.class; + cellIdentifier = bubbleData.isPaginationFirstBubble ? RoomTimelineCellIdentifierRoomCreationCollapsedWithPaginationTitle : RoomTimelineCellIdentifierRoomCreationCollapsed; } else if (bubbleData.tag == RoomBubbleCellDataTagCall) { - cellViewClass = RoomDirectCallStatusBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierDirectCallStatus; } else if (bubbleData.tag == RoomBubbleCellDataTagGroupCall) { - cellViewClass = RoomGroupCallStatusBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierGroupCallStatus; } else if (bubbleData.attachment.type == MXKAttachmentTypeVoiceMessage || bubbleData.attachment.type == MXKAttachmentTypeAudio) { - if (bubbleData.isPaginationFirstBubble) + if (bubbleData.isIncoming) { - cellViewClass = VoiceMessageWithPaginationTitleBubbleCell.class; - } - else if (bubbleData.shouldHideSenderInformation) - { - cellViewClass = VoiceMessageWithoutSenderInfoBubbleCell.class; + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceMessageWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceMessageWithoutSenderInfo; + } + else + { + cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceMessage; + } } else { - cellViewClass = VoiceMessageBubbleCell.class; + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceMessageWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceMessageWithoutSenderInfo; + } + else + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceMessage; + } } } else if (bubbleData.tag == RoomBubbleCellDataTagPoll) { if (bubbleData.isPaginationFirstBubble) { - cellViewClass = PollWithPaginationTitleBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierPollWithPaginationTitle; } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = PollWithoutSenderInfoBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierPollWithoutSenderInfo; } else { - cellViewClass = PollBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierPoll; + } + } + else if (bubbleData.tag == RoomBubbleCellDataTagLocation) + { + 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.isIncoming) @@ -2733,19 +2805,34 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Check whether the provided celldata corresponds to a selected sticker if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId]) { - cellViewClass = RoomSelectedStickerBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierSelectedSticker; } else if (bubbleData.isPaginationFirstBubble) { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingAttachmentEncryptedWithPaginationTitle : RoomTimelineCellIdentifierIncomingAttachmentWithPaginationTitle; } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingAttachmentEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierIncomingAttachmentWithoutSenderInfo; } else { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingAttachmentEncrypted : RoomTimelineCellIdentifierIncomingAttachment; + } + } + else if (bubbleData.isAttachment) + { + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithPaginationTitle : RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithoutSenderInfo; + } + else + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncrypted : RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnail; } } else @@ -2754,24 +2841,24 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (bubbleData.shouldHideSenderName) { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitleWithoutSenderName : RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitleWithoutSenderName; } else { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitle : RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitle; } } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderInfo; } else if (bubbleData.shouldHideSenderName) { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderName : RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderName; } else { - cellViewClass = showEncryptionBadge ? RoomIncomingEncryptedTextMsgBubbleCell.class : RoomIncomingTextMsgBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingTextMessageEncrypted : RoomTimelineCellIdentifierIncomingTextMessage; } } } @@ -2783,19 +2870,34 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Check whether the provided celldata corresponds to a selected sticker if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId]) { - cellViewClass = RoomSelectedStickerBubbleCell.class; + cellIdentifier = RoomTimelineCellIdentifierSelectedSticker; } else if (bubbleData.isPaginationFirstBubble) { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class :RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithPaginationTitle : RoomTimelineCellIdentifierOutgoingAttachmentWithPaginationTitle; } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierOutgoingAttachmentWithoutSenderInfo; } else { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedAttachmentBubbleCell.class : RoomOutgoingAttachmentBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingAttachmentEncrypted : RoomTimelineCellIdentifierOutgoingAttachment; + } + } + else if (bubbleData.isAttachment) + { + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithPaginationTitle : RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithoutSenderInfo; + } + else + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncrypted : RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnail; } } else @@ -2804,29 +2906,29 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (bubbleData.shouldHideSenderName) { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName : RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName; } else { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle : RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle; } } else if (bubbleData.shouldHideSenderInformation) { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class :RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo; } else if (bubbleData.shouldHideSenderName) { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName : RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName; } else { - cellViewClass = showEncryptionBadge ? RoomOutgoingEncryptedTextMsgBubbleCell.class : RoomOutgoingTextMsgBubbleCell.class; + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingTextMessageEncrypted : RoomTimelineCellIdentifierOutgoingTextMessage; } } } - return cellViewClass; + return cellIdentifier; } #pragma mark - MXKDataSource delegate @@ -2926,7 +3028,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } else { - [self showContextualMenuForEvent:tappedEvent fromSingleTapGesture:YES cell:cell animated:YES]; + if (tappedEvent.location) { + [_delegate roomViewController:self didRequestLocationPresentationForEvent:tappedEvent bubbleData:bubbleData]; + } else { + [self showContextualMenuForEvent:tappedEvent fromSingleTapGesture:YES cell:cell animated:YES]; + } } } } @@ -3179,15 +3285,34 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; currentAlert = nil; } + [self.eventMenuBuilder reset]; + MXWeakify(self); - UIAlertController *actionsMenu = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + BOOL showThreadOption = RiotSettings.shared.enableThreads + && !self.roomDataSource.threadId + && !selectedEvent.threadId; + if (showThreadOption && [self canCopyEvent:selectedEvent andCell:cell]) + { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeCopy + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCopy] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + + [self copyEvent:selectedEvent inCell:cell]; + }]]; + } // Add actions for a failed event if (selectedEvent.sentState == MXEventSentStateFailed) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeRetrySending + action:[UIAlertAction actionWithTitle:[VectorL10n retry] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3196,9 +3321,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self.roomDataSource resendEventWithEventId:selectedEvent.eventId success:nil failure:nil]; }]]; - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionDelete] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeRemove + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionDelete] + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3207,6 +3333,22 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } + // View in room action + if (self.roomDataSource.threadId && [selectedEvent.eventId isEqualToString:self.roomDataSource.threadId]) + { + // if in the thread and selected event is the root event + // add "View in room" action + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeViewInRoom + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewInRoom] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + [self.delegate roomViewController:self + showRoomWithId:self.roomDataSource.roomId + eventId:selectedEvent.eventId]; + }]]; + } + // Add actions for text message if (!attachment) { @@ -3228,9 +3370,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; selectedEvent.sentState == MXEventSentStateEncrypting || selectedEvent.sentState == MXEventSentStateSending) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeCancelSending + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; @@ -3242,22 +3385,29 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self cancelEventSelection]; }]]; } - - if (selectedEvent.sentState == MXEventSentStateSent && selectedEvent.eventType != MXEventTypePollStart) + + if (selectedEvent.sentState == MXEventSentStateSent && + selectedEvent.eventType != MXEventTypePollStart && + !selectedEvent.location) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeForward + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + [self presentEventForwardingDialogForSelectedEvent:selectedEvent]; }]]; } - + if (!isJitsiCallEvent && selectedEvent.eventType != MXEventTypePollStart) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeQuote + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionQuote] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3270,18 +3420,39 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } + if (selectedEvent.sentState == MXEventSentStateSent && selectedEvent.eventType != MXEventTypePollStart) + { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeForward + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + + [self presentEventForwardingDialogForSelectedEvent:selectedEvent]; + }]]; + } + if (!isJitsiCallEvent && BuildSettings.messageDetailsAllowShare && selectedEvent.eventType != MXEventTypePollStart) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeShare + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; - NSArray *activityItems = @[selectedComponent.textMessage]; + UIActivityViewController *activityViewController = nil; + if (selectedEvent.location) { + activityViewController = [self.delegate roomViewController:self locationShareActivityViewControllerForEvent:selectedEvent]; + } - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; + if (activityViewController == nil) { + NSArray *activityItems = @[selectedComponent.textMessage]; + activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; + } if (activityViewController) { @@ -3301,10 +3472,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo || attachment.type == MXKAttachmentTypeVoiceMessage)) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeForward + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionForward] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + [self presentEventForwardingDialogForSelectedEvent:selectedEvent]; }]]; } @@ -3313,9 +3488,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionSave] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeSaveMedia + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionSave] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3350,9 +3526,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSString *uploadId = roomBubbleTableViewCell.bubbleData.attachment.contentURL; if ([MXMediaManager existingUploaderWithId:uploadId]) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeCancelSending + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelSend] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); @@ -3384,9 +3561,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { if (BuildSettings.messageDetailsAllowShare) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeShare + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionShare] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3431,9 +3609,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSString *downloadId = roomBubbleTableViewCell.bubbleData.attachment.downloadId; if ([MXMediaManager existingDownloaderWithIdentifier:downloadId]) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelDownload] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeCancelDownloading + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionCancelDownload] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3450,23 +3629,89 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } + if (BuildSettings.messageDetailsAllowPermalink) + { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypePermalink + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionPermalink] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + + // Create a matrix.to permalink that is common to all matrix clients + NSString *permalink = [MXTools permalinkToEvent:selectedEvent.eventId inRoom:selectedEvent.roomId]; + NSURL *url = [NSURL URLWithString:permalink]; + + if (url) + { + MXKPasteboardManager.shared.pasteboard.URL = url; + [self.view vc_toastWithMessage:VectorL10n.roomEventCopyLinkInfo + image:[UIImage imageNamed:@"link_icon"] + duration:2.0 + position:ToastPositionBottom + additionalMargin:self.roomInputToolbarContainerHeightConstraint.constant]; + } + else + { + MXLogDebug(@"[RoomViewController] Contextual menu permalink action failed. Permalink is nil room id/event id: %@/%@", selectedEvent.roomId, selectedEvent.eventId); + } + }]]; + } + + if (BuildSettings.messageDetailsAllowViewSource) + { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeViewSource + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewSource] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + + // Display event details + [self showEventDetails:selectedEvent]; + }]]; + + + // Add "View Decrypted Source" for e2ee event we can decrypt + if (selectedEvent.isEncrypted && selectedEvent.clearEvent) + { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeViewDecryptedSource + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewDecryptedSource] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + [self cancelEventSelection]; + + // Display clear event details + [self showEventDetails:selectedEvent.clearEvent]; + }]]; + } + } + // Do not allow to redact the event that enabled encryption (m.room.encryption) // because it breaks everything if (selectedEvent.eventType != MXEventTypeRoomEncryption) { NSString *title; + EventMenuItemType itemType; if (selectedEvent.eventType == MXEventTypePollStart) { title = [VectorL10n roomEventActionRemovePoll]; + itemType = EventMenuItemTypeRemovePoll; } else { title = [VectorL10n roomEventActionRedact]; + itemType = EventMenuItemTypeRemove; } - [actionsMenu addAction:[UIAlertAction actionWithTitle:title - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:itemType + action:[UIAlertAction actionWithTitle:title + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3488,11 +3733,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } - if (selectedEvent.eventType == MXEventTypePollStart && [selectedEvent.sender isEqualToString:self.mainSession.myUser.userId]) { - if ([self.delegate roomViewController:self canEndPollWithEventIdentifier:selectedEvent.eventId]) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionEndPoll] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + if (selectedEvent.eventType == MXEventTypePollStart && [selectedEvent.sender isEqualToString:self.mainSession.myUserId]) + { + if ([self.delegate roomViewController:self canEndPollWithEventIdentifier:selectedEvent.eventId]) + { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeEndPoll + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionEndPoll] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self.delegate roomViewController:self endPollWithEventIdentifier:selectedEvent.eventId]; @@ -3502,43 +3750,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - [self hideContextualMenuAnimated:YES]; - }]]; - - if (BuildSettings.messageDetailsAllowPermalink) - { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionPermalink] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - [self cancelEventSelection]; - - // Create a matrix.to permalink that is common to all matrix clients - NSString *permalink = [MXTools permalinkToEvent:selectedEvent.eventId inRoom:selectedEvent.roomId]; - - if (permalink) - { - MXKPasteboardManager.shared.pasteboard.string = permalink; - } - else - { - MXLogDebug(@"[RoomViewController] Contextual menu permalink action failed. Permalink is nil room id/event id: %@/%@", selectedEvent.roomId, selectedEvent.eventId); - } - }]]; - } - // Add reaction history if event contains reactions if (roomBubbleTableViewCell.bubbleData.reactions[selectedEvent.eventId].aggregatedReactionsWithNonZeroCount) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReactionHistory] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeReactionHistory + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReactionHistory] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3548,41 +3766,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } - if (BuildSettings.messageDetailsAllowViewSource) + if (![selectedEvent.sender isEqualToString:self.mainSession.myUserId] && RiotSettings.shared.roomContextualMenuShowReportContentOption) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewSource] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - [self cancelEventSelection]; - - // Display event details - [self showEventDetails:selectedEvent]; - }]]; - - - // Add "View Decrypted Source" for e2ee event we can decrypt - if (selectedEvent.isEncrypted && selectedEvent.clearEvent) - { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewDecryptedSource] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - [self cancelEventSelection]; - - // Display clear event details - [self showEventDetails:selectedEvent.clearEvent]; - }]]; - } - } - - if (![selectedEvent.sender isEqualToString:self.mainSession.myUser.userId] && RiotSettings.shared.roomContextualMenuShowReportContentOption) - { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReport] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeReport + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionReport] + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3672,9 +3861,10 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (!isJitsiCallEvent && self.roomDataSource.room.summary.isEncrypted) { - [actionsMenu addAction:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeViewEncryption + action:[UIAlertAction actionWithTitle:[VectorL10n roomEventActionViewEncryption] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self cancelEventSelection]; @@ -3684,11 +3874,29 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]]; } + [self.eventMenuBuilder addItemWithType:EventMenuItemTypeCancel + action:[UIAlertAction actionWithTitle:[VectorL10n cancel] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + [self hideContextualMenuAnimated:YES]; + }]]; + } // Do not display empty action sheet - if (actionsMenu.actions.count > 1) + if (!self.eventMenuBuilder.isEmpty) { + UIAlertController *actionsMenu = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + // build actions and add them to the alert + NSArray *actions = [self.eventMenuBuilder build]; + for (UIAlertAction *action in actions) + { + [actionsMenu addAction:action]; + } + NSInteger bubbleComponentIndex = [roomBubbleTableViewCell.bubbleData bubbleComponentIndexForEventId:selectedEvent.eventId]; CGRect sourceRect = [roomBubbleTableViewCell componentFrameInContentViewForIndex:bubbleComponentIndex]; @@ -3696,6 +3904,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [actionsMenu mxk_setAccessibilityIdentifier:@"RoomVCEventMenuAlert"]; [actionsMenu popoverPresentationController].sourceView = roomBubbleTableViewCell; [actionsMenu popoverPresentationController].sourceRect = sourceRect; + [self dismissKeyboard]; [self presentViewController:actionsMenu animated:animated completion:nil]; currentAlert = actionsMenu; } @@ -3943,6 +4152,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; customizedRoomDataSource.showBubbleDateTimeOnSelection = YES; customizedRoomDataSource.selectedEventId = nil; + customizedRoomDataSource.highlightedEventId = nil; [self restoreTextMessageBeforeEditing]; @@ -3997,12 +4207,17 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; #pragma mark - RoomDataSourceDelegate -- (void)roomDataSource:(RoomDataSource *)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +- (void)roomDataSourceDidUpdateEncryptionTrustLevel:(RoomDataSource *)roomDataSource { [self updateInputToolbarEncryptionDecoration]; [self updateTitleViewEncryptionDecoration]; } +- (void)roomDataSource:(RoomDataSource *)roomDataSource didTapThread:(MXThread *)thread +{ + [self openThreadWithId:thread.id]; +} + #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender @@ -4310,6 +4525,15 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self placeCallWithVideo:YES]; } +- (IBAction)onThreadListTapped:(id)sender +{ + self.threadsBridgePresenter = [[ThreadsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession + roomId:self.roomDataSource.roomId + threadId:nil]; + self.threadsBridgePresenter.delegate = self; + [self.threadsBridgePresenter pushFrom:self.navigationController animated:YES]; +} + - (IBAction)onIntegrationsPressed:(id)sender { WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession @@ -4332,7 +4556,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Jump to the last unread event by using a temporary room data source initialized with the last unread event id. MXWeakify(self); - [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId initialEventId:self.roomDataSource.room.accountData.readMarkerEventId andMatrixSession:self.mainSession onComplete:^(id roomDataSource) { + [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId + initialEventId:self.roomDataSource.room.accountData.readMarkerEventId + threadId:self.roomDataSource.threadId + andMatrixSession:self.mainSession + onComplete:^(id roomDataSource) { MXStrongifyAndReturnIfNil(self); [roomDataSource finalizeInitialization]; @@ -4431,6 +4659,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { [super scrollViewWillBeginDragging:scrollView]; } + + [self cancelEventHighlight]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate @@ -4555,7 +4785,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (eventId) { MXWeakify(self); - [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId initialEventId:eventId andMatrixSession:self.mainSession onComplete:^(id roomDataSource) { + [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId + initialEventId:eventId + threadId:self.roomDataSource.threadId + andMatrixSession:self.mainSession + onComplete:^(id roomDataSource) { MXStrongifyAndReturnIfNil(self); [roomDataSource finalizeInitialization]; @@ -4632,7 +4866,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if (typingNotifListener) { MXWeakify(self); - [self.roomDataSource.room liveTimeline:^(MXEventTimeline *liveTimeline) { + [self.roomDataSource.room liveTimeline:^(id liveTimeline) { MXStrongifyAndReturnIfNil(self); [liveTimeline removeListener:self->typingNotifListener]; @@ -4837,6 +5071,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)listenWidgetNotifications { + if (!self.displayConfiguration.jitsiWidgetRemoverEnabled) + { + return; + } + MXWeakify(self); kMXKWidgetManagerDidUpdateWidgetObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kWidgetManagerDidUpdateWidgetNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -4873,6 +5112,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (NSUInteger)widgetsCount:(BOOL)includeUserWidgets { + if (!self.displayConfiguration.integrationsEnabled) + { + return 0; + } + NSUInteger widgetsCount = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsiV1, kWidgetTypeJitsiV2] inRoom:self.roomDataSource.room withRoomState:self.roomDataSource.roomState].count; @@ -4969,7 +5213,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Retrieve the unread messages count NSUInteger unreadCount = self.roomDataSource.room.summary.localUnreadEventCount; - self.scrollToBottomBadgeLabel.text = unreadCount ? [NSString stringWithFormat:@"%lu", unreadCount] : nil; + if (!self.roomDataSource.threadId) + { + self.scrollToBottomBadgeLabel.text = unreadCount ? [NSString stringWithFormat:@"%lu", unreadCount] : nil; + } + else + { + self.scrollToBottomBadgeLabel.text = nil; + } self.scrollToBottomHidden = NO; } else @@ -5017,33 +5268,58 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.updateRoomReadMarker = NO; [self scrollBubblesTableViewToBottomAnimated:YES]; + + [self cancelEventHighlight]; } else { - // Switch back to the room live timeline managed by MXKRoomDataSourceManager - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; - MXWeakify(self); - [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { + + void(^continueBlock)(MXKRoomDataSource *, BOOL) = ^(MXKRoomDataSource *roomDataSource, BOOL hasRoomDataSourceOwnership){ MXStrongifyAndReturnIfNil(self); - + + [roomDataSource finalizeInitialization]; + // Scroll to bottom the bubble history on the display refresh. self->shouldScrollToBottomOnTableRefresh = YES; - + [self displayRoom:roomDataSource]; - - // The room view controller do not have here the data source ownership. - self.hasRoomDataSourceOwnership = NO; - + + // Set the room view controller has the data source ownership here. + self.hasRoomDataSourceOwnership = hasRoomDataSourceOwnership; + [self refreshActivitiesViewDisplay]; [self refreshJumpToLastUnreadBannerDisplay]; - + if (self.saveProgressTextInput) { // Restore the potential message partially typed before jump to last unread messages. self.inputToolbarView.textMessage = roomDataSource.partialTextMessage; } - }]; + }; + + if (self.roomDataSource.threadId) + { + [ThreadDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId + initialEventId:nil + threadId:self.roomDataSource.threadId + andMatrixSession:self.mainSession + onComplete:^(ThreadDataSource *threadDataSource) + { + continueBlock(threadDataSource, YES); + }]; + } + else + { + // Switch back to the room live timeline managed by MXKRoomDataSourceManager + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; + + [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId + create:YES + onComplete:^(MXKRoomDataSource *roomDataSource) { + continueBlock(roomDataSource, NO); + }]; + } } } @@ -5505,6 +5781,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)refreshRemoveJitsiWidgetView { + if (!self.displayConfiguration.jitsiWidgetRemoverEnabled) + { + return; + } + if (self.roomDataSource.isLive && !self.roomDataSource.isPeeking) { Widget *jitsiWidget = [customizedRoomDataSource jitsiWidget]; @@ -5885,25 +6166,29 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; ]; } - BOOL showMoreOption = (event.isState && RiotSettings.shared.roomContextualMenuShowMoreOptionForStates) || (!event.isState && RiotSettings.shared.roomContextualMenuShowMoreOptionForMessages); + BOOL showMoreOption = (event.isState && RiotSettings.shared.roomContextualMenuShowMoreOptionForStates) + || (!event.isState && RiotSettings.shared.roomContextualMenuShowMoreOptionForMessages); + BOOL showThreadOption = RiotSettings.shared.enableThreads && !self.roomDataSource.threadId && !event.threadId; + NSMutableArray *items = [NSMutableArray arrayWithCapacity:5]; + + [items addObject:[self replyMenuItemWithEvent:event]]; + if (showThreadOption) + { + // add "Thread" option only if not already in a thread + [items addObject:[self replyInThreadMenuItemWithEvent:event]]; + } + [items addObject:[self editMenuItemWithEvent:event]]; + if (!showThreadOption) + { + [items addObject:[self copyMenuItemWithEvent:event andCell:cell]]; + } if (showMoreOption) { - return @[ - [self copyMenuItemWithEvent:event andCell:cell], - [self replyMenuItemWithEvent:event], - [self editMenuItemWithEvent:event], - [self moreMenuItemWithEvent:event andCell:cell] - ]; - } - else - { - return @[ - [self copyMenuItemWithEvent:event andCell:cell], - [self replyMenuItemWithEvent:event], - [self editMenuItemWithEvent:event] - ]; + [items addObject:[self moreMenuItemWithEvent:event andCell:cell]]; } + + return items; } - (void)showContextualMenuForEvent:(MXEvent*)event fromSingleTapGesture:(BOOL)usedSingleTapGesture cell:(id)cell animated:(BOOL)animated @@ -6063,44 +6348,75 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; MXWeakify(self); RoomContextualMenuItem *editMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionEdit]; - editMenuItem.action = ^{ - MXStrongifyAndReturnIfNil(self); - [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; - [self editEventContentWithId:event.eventId]; - - // And display the keyboard - [self.inputToolbarView becomeFirstResponder]; - }; - editMenuItem.isEnabled = [self.roomDataSource canEditEventWithId:event.eventId]; + switch (event.eventType) { + case MXEventTypePollStart: { + editMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES cancelEventSelection:YES completion:nil]; + [self.delegate roomViewController:self didRequestEditForPollWithStartEvent:event]; + }; + + editMenuItem.isEnabled = [self.delegate roomViewController:self canEditPollWithEventIdentifier:event.eventId]; + + break; + } + default: { + editMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + [self editEventContentWithId:event.eventId]; + + // And display the keyboard + [self.inputToolbarView becomeFirstResponder]; + }; + + editMenuItem.isEnabled = [self.roomDataSource canEditEventWithId:event.eventId]; + + break; + } + } return editMenuItem; } - (RoomContextualMenuItem *)copyMenuItemWithEvent:(MXEvent*)event andCell:(id)cell +{ + MXWeakify(self); + + RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; + copyMenuItem.isEnabled = [self canCopyEvent:event andCell:cell]; + copyMenuItem.action = ^{ + MXStrongifyAndReturnIfNil(self); + + [self copyEvent:event inCell:cell]; + }; + + return copyMenuItem; +} + +- (BOOL)canCopyEvent:(MXEvent*)event andCell:(id)cell { MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; - MXWeakify(self); - - BOOL isCopyActionEnabled = (event.eventType != MXEventTypePollStart && (!attachment || attachment.type != MXKAttachmentTypeSticker)); + BOOL result = (event.eventType != MXEventTypePollStart && (!attachment || attachment.type != MXKAttachmentTypeSticker)); if (attachment && !BuildSettings.messageDetailsAllowCopyMedia) { - isCopyActionEnabled = NO; + result = NO; } - if (isCopyActionEnabled) + if (result) { switch (event.eventType) { case MXEventTypeRoomMessage: { - NSString *messageType = event.content[@"msgtype"]; + NSString *messageType = event.content[kMXMessageTypeKey]; if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) { - isCopyActionEnabled = NO; + result = NO; } break; } @@ -6110,7 +6426,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; case MXEventTypeKeyVerificationMac: case MXEventTypeKeyVerificationDone: case MXEventTypeKeyVerificationCancel: - isCopyActionEnabled = NO; + result = NO; break; case MXEventTypeCustom: if ([event.type isEqualToString:kWidgetMatrixEventTypeString] @@ -6120,7 +6436,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; if ([widget.type isEqualToString:kWidgetTypeJitsiV1] || [widget.type isEqualToString:kWidgetTypeJitsiV2]) { - isCopyActionEnabled = NO; + result = NO; } } default: @@ -6128,60 +6444,60 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } - RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; - copyMenuItem.isEnabled = isCopyActionEnabled; - copyMenuItem.action = ^{ - MXStrongifyAndReturnIfNil(self); - - if (!attachment) - { - NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; - MXKRoomBubbleComponent *selectedComponent; - for (selectedComponent in components) - { - if ([selectedComponent.event.eventId isEqualToString:event.eventId]) - { - break; - } - selectedComponent = nil; - } - NSString *textMessage = selectedComponent.textMessage; - - if (textMessage) - { - MXKPasteboardManager.shared.pasteboard.string = textMessage; - } - else - { - MXLogDebug(@"[RoomViewController] Contextual menu copy failed. Text is nil for room id/event id: %@/%@", selectedComponent.event.roomId, selectedComponent.event.eventId); - } - - [self hideContextualMenuAnimated:YES]; - } - else if (attachment.type != MXKAttachmentTypeSticker) - { - [self hideContextualMenuAnimated:YES completion:^{ - [self startActivityIndicator]; - - [attachment copy:^{ - - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - [self stopActivityIndicator]; - - //Alert user - [self showError:error]; - }]; - - // Start animation in case of download during attachment preparing - [roomBubbleTableViewCell startProgressUI]; - }]; - } - }; + return result; +} + +- (void)copyEvent:(MXEvent*)event inCell:(id)cell +{ + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell; + MXKAttachment *attachment = roomBubbleTableViewCell.bubbleData.attachment; - return copyMenuItem; + if (!attachment) + { + NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; + MXKRoomBubbleComponent *selectedComponent; + for (selectedComponent in components) + { + if ([selectedComponent.event.eventId isEqualToString:event.eventId]) + { + break; + } + selectedComponent = nil; + } + NSString *textMessage = selectedComponent.textMessage; + + if (textMessage) + { + MXKPasteboardManager.shared.pasteboard.string = textMessage; + } + else + { + MXLogDebug(@"[RoomViewController] Contextual menu copy failed. Text is nil for room id/event id: %@/%@", selectedComponent.event.roomId, selectedComponent.event.eventId); + } + + [self hideContextualMenuAnimated:YES]; + } + else if (attachment.type != MXKAttachmentTypeSticker) + { + [self hideContextualMenuAnimated:YES completion:^{ + [self startActivityIndicator]; + + [attachment copy:^{ + + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + [self stopActivityIndicator]; + + //Alert user + [self showError:error]; + }]; + + // Start animation in case of download during attachment preparing + [roomBubbleTableViewCell startProgressUI]; + }]; + } } - (RoomContextualMenuItem *)replyMenuItemWithEvent:(MXEvent*)event @@ -6203,6 +6519,23 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; return replyMenuItem; } +- (RoomContextualMenuItem *)replyInThreadMenuItemWithEvent:(MXEvent*)event +{ + MXWeakify(self); + + RoomContextualMenuItem *item = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionReplyInThread]; + item.isEnabled = [self.roomDataSource canReplyToEventWithId:event.eventId] && !self.voiceMessageController.isRecordingAudio; + item.action = ^{ + MXStrongifyAndReturnIfNil(self); + + [self hideContextualMenuAnimated:YES cancelEventSelection:NO completion:nil]; + + [self openThreadWithId:event.eventId]; + }; + + return item; +} + - (RoomContextualMenuItem *)moreMenuItemWithEvent:(MXEvent*)event andCell:(id)cell { MXWeakify(self); @@ -6217,6 +6550,135 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; return moreMenuItem; } +#pragma mark - Threads + +- (void)openThreadWithId:(NSString *)threadId +{ + if (self.threadsBridgePresenter) + { + [self.threadsBridgePresenter dismissWithAnimated:YES completion:nil]; + self.threadsBridgePresenter = nil; + } + + self.threadsBridgePresenter = [[ThreadsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession + roomId:self.roomDataSource.roomId + threadId:threadId]; + self.threadsBridgePresenter.delegate = self; + [self.threadsBridgePresenter pushFrom:self.navigationController animated:YES]; +} + +- (void)highlightAndDisplayEvent:(NSString *)eventId completion:(void (^)(void))completion +{ + NSInteger row = [self.roomDataSource indexOfCellDataWithEventId:eventId]; + if (row == NSNotFound) + { + // event with eventId is not loaded into data source yet, load another data source and display it + [self startActivityIndicator]; + MXWeakify(self); + [RoomDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId + initialEventId:eventId + threadId:nil + andMatrixSession:self.roomDataSource.mxSession + onComplete:^(RoomDataSource *roomDataSource) { + MXStrongifyAndReturnIfNil(self); + [roomDataSource finalizeInitialization]; + [self stopActivityIndicator]; + roomDataSource.markTimelineInitialEvent = YES; + [self displayRoom:roomDataSource]; + // Give the data source ownership to the room view controller. + self.hasRoomDataSourceOwnership = YES; + if (completion) + { + completion(); + } + }]; + return; + } + + self->customizedRoomDataSource.highlightedEventId = eventId; + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0]; + if ([[self.bubblesTableView indexPathsForVisibleRows] containsObject:indexPath]) + { + [self.bubblesTableView reloadRowsAtIndexPaths:@[indexPath] + withRowAnimation:UITableViewRowAnimationNone]; + [self.bubblesTableView scrollToRowAtIndexPath:indexPath + atScrollPosition:UITableViewScrollPositionMiddle + animated:YES]; + } + else if ([self.bubblesTableView vc_hasIndexPath:indexPath]) + { + [self.bubblesTableView scrollToRowAtIndexPath:indexPath + atScrollPosition:UITableViewScrollPositionMiddle + animated:YES]; + } + if (completion) + { + completion(); + } +} + +- (void)cancelEventHighlight +{ + // if data source is highlighting an event, dismiss the highlight when user dragges the table view + if (customizedRoomDataSource.highlightedEventId) + { + NSInteger row = [self.roomDataSource indexOfCellDataWithEventId:customizedRoomDataSource.highlightedEventId]; + if (row == NSNotFound) + { + customizedRoomDataSource.highlightedEventId = nil; + return; + } + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0]; + if ([[self.bubblesTableView indexPathsForVisibleRows] containsObject:indexPath]) + { + customizedRoomDataSource.highlightedEventId = nil; + [self.bubblesTableView reloadRowsAtIndexPaths:@[indexPath] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + } +} + +- (void)updateThreadListBarButtonBadgeWith:(MXThreadingService *)service +{ + if (!threadListBarButtonItem || !service) + { + // there is no thread list bar button, ignore + return; + } + + MXThreadNotificationsCount *notificationsCount = [service notificationsCountForRoom:self.roomDataSource.roomId]; + + if (notificationsCount.numberOfHighlightedThreads > 0) + { + threadListBarButtonItem.badgeText = [self threadListBadgeTextFor:notificationsCount.numberOfHighlightedThreads]; + threadListBarButtonItem.badgeBackgroundColor = ThemeService.shared.theme.colors.alert; + } + else if (notificationsCount.numberOfNotifiedThreads > 0) + { + threadListBarButtonItem.badgeText = [self threadListBadgeTextFor:notificationsCount.numberOfNotifiedThreads]; + threadListBarButtonItem.badgeBackgroundColor = ThemeService.shared.theme.noticeSecondaryColor; + } + else + { + // remove badge + threadListBarButtonItem.badgeText = nil; + } +} + +- (NSString *)threadListBadgeTextFor:(NSUInteger)numberOfThreads +{ + if (numberOfThreads < 100) + { + return [NSString stringWithFormat:@"%tu", numberOfThreads]; + } + else + { + return @"···"; + } +} + #pragma mark - RoomContextualMenuViewControllerDelegate - (void)roomContextualMenuViewControllerDidTapBackgroundOverlay:(RoomContextualMenuViewController *)viewController @@ -6616,4 +7078,54 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self mention:member]; } +#pragma mark - ThreadsCoordinatorBridgePresenterDelegate + +- (void)threadsCoordinatorBridgePresenterDelegateDidComplete:(ThreadsCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + self.threadsBridgePresenter = nil; +} + +- (void)threadsCoordinatorBridgePresenterDelegateDidSelect:(ThreadsCoordinatorBridgePresenter *)coordinatorBridgePresenter roomId:(NSString *)roomId eventId:(NSString *)eventId +{ + MXWeakify(self); + [self.threadsBridgePresenter dismissWithAnimated:YES completion:^{ + MXStrongifyAndReturnIfNil(self); + + if (eventId) + { + [self highlightAndDisplayEvent:eventId completion:nil]; + } + }]; +} + +- (void)threadsCoordinatorBridgePresenterDidDismissInteractively:(ThreadsCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + self.threadsBridgePresenter = nil; +} + +#pragma mark - MXThreadingServiceDelegate + +- (void)threadingServiceDidUpdateThreads:(MXThreadingService *)service +{ + [self updateThreadListBarButtonBadgeWith:service]; +} + +#pragma mark - RoomParticipantsInviteCoordinatorBridgePresenterDelegate + +- (void)roomParticipantsInviteCoordinatorBridgePresenterDidComplete:(RoomParticipantsInviteCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + self.participantsInvitePresenter = nil; +} + +- (void)roomParticipantsInviteCoordinatorBridgePresenterDidStartLoading:(RoomParticipantsInviteCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self startActivityIndicator]; +} + +- (void)roomParticipantsInviteCoordinatorBridgePresenterDidEndLoading:(RoomParticipantsInviteCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self stopActivityIndicator]; +} + @end + diff --git a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m index f35ac5c7f..5b99eea2d 100644 --- a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m +++ b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m @@ -57,30 +57,69 @@ { // Prepare text font used to highlight the search pattern. UIFont *patternFont = [roomDataSource.eventFormatter bingTextFont]; + + dispatch_group_t group = dispatch_group_create(); // Convert the HS results into `RoomViewController` cells for (MXSearchResult *result in roomEventResults.results) { - // Let the `RoomViewController` ecosystem do the job - // The search result contains only room message events, no state events. - // Thus, passing the current room state is not a huge problem. Only - // the user display name and his avatar may be wrong. - RoomBubbleCellData *cellData = [[RoomBubbleCellData alloc] initWithEvent:result.result andRoomState:roomDataSource.roomState andRoomDataSource:roomDataSource]; - if (cellData) + dispatch_group_enter(group); + + void(^continueBlock)(void) = ^{ + // Let the `RoomViewController` ecosystem do the job + // The search result contains only room message events, no state events. + // Thus, passing the current room state is not a huge problem. Only + // the user display name and his avatar may be wrong. + RoomBubbleCellData *cellData = [[RoomBubbleCellData alloc] initWithEvent:result.result andRoomState:self->roomDataSource.roomState andRoomDataSource:self->roomDataSource]; + if (cellData) + { + // Highlight the search pattern + [cellData highlightPatternInTextMessage:self.searchText + withBackgroundColor:ThemeService.shared.theme.searchResultHighlightColor + foregroundColor:ThemeService.shared.theme.textPrimaryColor + andFont:patternFont]; + + // Use profile information as data to display + MXSearchUserProfile *userProfile = result.context.profileInfo[result.result.sender]; + cellData.senderDisplayName = userProfile.displayName; + cellData.senderAvatarUrl = userProfile.avatarUrl; + + [self->cellDataArray insertObject:cellData atIndex:0]; + } + + dispatch_group_leave(group); + }; + + if (RiotSettings.shared.enableThreads) { - // Highlight the search pattern - [cellData highlightPatternInTextMessage:self.searchText withForegroundColor:ThemeService.shared.theme.tintColor andFont:patternFont]; - - // Use profile information as data to display - MXSearchUserProfile *userProfile = result.context.profileInfo[result.result.sender]; - cellData.senderDisplayName = userProfile.displayName; - cellData.senderAvatarUrl = userProfile.avatarUrl; - - [cellDataArray insertObject:cellData atIndex:0]; + if (result.result.isInThread) + { + continueBlock(); + } + else + { + [roomDataSource.room liveTimeline:^(id liveTimeline) { + [liveTimeline paginate:NSUIntegerMax + direction:MXTimelineDirectionBackwards + onlyFromStore:YES + complete:^{ + [liveTimeline resetPagination]; + continueBlock(); + } failure:^(NSError * _Nonnull error) { + continueBlock(); + }]; + }]; + } + } + else + { + continueBlock(); } } - onComplete(); + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + onComplete(); + }); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath @@ -91,11 +130,70 @@ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class]) { MXKRoomBubbleTableViewCell *bubbleCell = (MXKRoomBubbleTableViewCell*)cell; - + // Display date for each message [bubbleCell addDateLabel]; + + if (RiotSettings.shared.enableThreads) + { + RoomBubbleCellData *cellData = (RoomBubbleCellData*)[self cellDataAtIndex:indexPath.row]; + MXEvent *event = cellData.events.firstObject; + + if (event) + { + if (cellData.hasThreadRoot) + { + MXThread *thread = cellData.bubbleComponents.firstObject.thread; + ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread]; + [bubbleCell.tmpSubviews addObject:threadSummaryView]; + + threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:threadSummaryView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + CGFloat height = [ThreadSummaryView contentViewHeightForThread:thread fitting:cellData.maxTextViewWidth]; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:0]; + CGFloat bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + + // Set constraints for the summary view + [NSLayoutConstraint activateConstraints: @[ + [threadSummaryView.leadingAnchor constraintEqualToAnchor:threadSummaryView.superview.leadingAnchor + constant:leftMargin], + [threadSummaryView.topAnchor constraintEqualToAnchor:threadSummaryView.superview.topAnchor + constant:bottomPositionY + RoomBubbleCellLayout.threadSummaryViewTopMargin], + [threadSummaryView.heightAnchor constraintEqualToConstant:height], + [threadSummaryView.trailingAnchor constraintLessThanOrEqualToAnchor:threadSummaryView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin] + ]]; + } + else if (event.isInThread) + { + FromAThreadView *fromAThreadView = [FromAThreadView instantiate]; + [bubbleCell.tmpSubviews addObject:fromAThreadView]; + + fromAThreadView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:fromAThreadView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + CGFloat height = [FromAThreadView contentViewHeightForEvent:event fitting:cellData.maxTextViewWidth]; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:0]; + CGFloat bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + + // Set constraints for the summary view + [NSLayoutConstraint activateConstraints: @[ + [fromAThreadView.leadingAnchor constraintEqualToAnchor:fromAThreadView.superview.leadingAnchor + constant:leftMargin], + [fromAThreadView.topAnchor constraintEqualToAnchor:fromAThreadView.superview.topAnchor + constant:bottomPositionY + RoomBubbleCellLayout.fromAThreadViewTopMargin], + [fromAThreadView.heightAnchor constraintEqualToConstant:height], + [fromAThreadView.trailingAnchor constraintLessThanOrEqualToAnchor:fromAThreadView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin] + ]]; + } + } + } } - + return cell; } diff --git a/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.h b/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.h index a01848797..8494412e0 100644 --- a/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.h +++ b/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.h @@ -18,9 +18,4 @@ @interface RoomFilesSearchViewController : MXKSearchViewController -/** - The event selected in the search results - */ -@property (nonatomic, readonly) MXEvent *selectedEvent; - @end diff --git a/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.m b/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.m index 0e14b53ee..b0c6c589a 100644 --- a/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.m +++ b/Riot/Modules/Room/Search/Files/RoomFilesSearchViewController.m @@ -109,9 +109,6 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"RoomFilesSearch"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -173,18 +170,16 @@ { // Data in the cells are actually Vector RoomBubbleCellData FilesSearchCellData *cellData = (FilesSearchCellData*)[self.dataSource cellDataAtIndex:indexPath.row]; - _selectedEvent = cellData.searchResult.result; + MXEvent *event = cellData.searchResult.result; + + RoomSearchViewController *roomSearchViewController = (RoomSearchViewController*)self.parentViewController; // Hide the keyboard handled by the search text input which belongs to RoomSearchViewController - [((RoomSearchViewController*)self.parentViewController).searchBar resignFirstResponder]; + [roomSearchViewController resignFirstResponder]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; - // Make the RoomSearchViewController (that contains this VC) open the RoomViewController - [self.parentViewController performSegueWithIdentifier:@"showTimeline" sender:self]; - - // Reset the selected event. RoomSearchViewController got it when here - _selectedEvent = nil; + [roomSearchViewController selectEvent:event]; } @end diff --git a/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.h b/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.h index b8d0d6d51..64fb26c97 100644 --- a/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.h +++ b/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.h @@ -20,9 +20,4 @@ @interface RoomMessagesSearchViewController : MXKSearchViewController -/** - The event selected in the search results - */ -@property (nonatomic, readonly) MXEvent *selectedEvent; - @end diff --git a/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.m b/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.m index 4e93f01d6..85a15e267 100644 --- a/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.m +++ b/Riot/Modules/Room/Search/Messages/RoomMessagesSearchViewController.m @@ -111,9 +111,6 @@ { [super viewWillAppear:animated]; - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"RoomMessagesSearch"]; - // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -209,18 +206,16 @@ { // Data in the cells are actually Vector RoomBubbleCellData RoomBubbleCellData *cellData = (RoomBubbleCellData*)[self.dataSource cellDataAtIndex:indexPath.row]; - _selectedEvent = cellData.bubbleComponents[0].event; + MXEvent *event = cellData.bubbleComponents[0].event; + + RoomSearchViewController *roomSearchViewController = (RoomSearchViewController*)self.parentViewController; // Hide the keyboard handled by the search text input which belongs to RoomSearchViewController - [((RoomSearchViewController*)self.parentViewController).searchBar resignFirstResponder]; + [roomSearchViewController.searchBar resignFirstResponder]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; - // Make the RoomSearchViewController (that contains this VC) open the RoomViewController - [self.parentViewController performSegueWithIdentifier:@"showTimeline" sender:self]; - - // Reset the selected event. RoomSearchViewController got it when here - _selectedEvent = nil; + [roomSearchViewController selectEvent:event]; } @end diff --git a/Riot/Modules/Room/Search/RoomSearchViewController.h b/Riot/Modules/Room/Search/RoomSearchViewController.h index 7d72226b5..bbf20aa57 100644 --- a/Riot/Modules/Room/Search/RoomSearchViewController.h +++ b/Riot/Modules/Room/Search/RoomSearchViewController.h @@ -27,4 +27,6 @@ + (instancetype)instantiate; +- (void)selectEvent:(MXEvent *)event; + @end diff --git a/Riot/Modules/Room/Search/RoomSearchViewController.m b/Riot/Modules/Room/Search/RoomSearchViewController.m index 2a0983263..7d830ecb3 100644 --- a/Riot/Modules/Room/Search/RoomSearchViewController.m +++ b/Riot/Modules/Room/Search/RoomSearchViewController.m @@ -33,6 +33,8 @@ MXKSearchDataSource *filesSearchDataSource; } +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + @end @implementation RoomSearchViewController @@ -49,6 +51,8 @@ [super finalizeInit]; // The navigation bar tint color and the rageShake Manager are handled by super (see SegmentedViewController). + + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRoomSearch]; } - (void)viewDidLoad @@ -89,6 +93,9 @@ UIImage *image = [MXKTools paintImage:backgroundImageView.image withColor:ThemeService.shared.theme.matrixSearchBackgroundImageTintColor]; backgroundImageView.image = image; } + + // Match the search bar color to the navigation bar color as it extends slightly outside the frame. + self.searchBar.backgroundColor = ThemeService.shared.theme.baseColor; } - (void)destroy @@ -106,9 +113,6 @@ [self.activityIndicator stopAnimating]; self.activityIndicator = nil; } - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"RoomsSearch"]; // Enable the search field by default at the screen opening if (self.searchBarHidden) @@ -124,6 +128,8 @@ // Refresh the search results. // Note: We wait for 'viewDidAppear' call to consider the actual view size during this update. [self updateSearch]; + + [self.screenTimer start]; } - (void)viewWillDisappear:(BOOL)animated @@ -138,11 +144,45 @@ [super viewWillDisappear:animated]; } +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [self.screenTimer stop]; +} + - (UIStatusBarStyle)preferredStatusBarStyle { return ThemeService.shared.theme.statusBarStyle; } +- (void)selectEvent:(MXEvent *)event +{ + ThreadParameters *threadParameters = nil; + if (RiotSettings.shared.enableThreads) + { + if (event.threadId) + { + threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId + stackRoomScreen:NO]; + } + else if ([self.mainSession.threadingService isEventThreadRoot:event]) + { + threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId + stackRoomScreen:NO]; + } + } + + ScreenPresentationParameters *screenParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO + stackAboveVisibleViews:YES]; + + RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:event.roomId + eventId:event.eventId + mxSession:self.mainSession + threadParameters:threadParameters + presentationParameters:screenParameters]; + [[LegacyAppDelegate theDelegate] showRoomWithParameters:parameters]; +} + #pragma mark - - (void)setRoomDataSource:(MXKRoomDataSource *)roomDataSource @@ -294,46 +334,6 @@ [self updateSearch]; } -#pragma mark - Navigation - -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender -{ - [super prepareForSegue:segue sender:sender]; - - if ([[segue identifier] isEqualToString:@"showTimeline"]) - { - // Check whether an event has been selected from messages or files search tab - MXEvent *selectedSearchEvent = messagesSearchViewController.selectedEvent; - MXSession *selectedSearchEventSession = messagesSearchDataSource.mxSession; - if (!selectedSearchEvent) - { - selectedSearchEvent = filesSearchViewController.selectedEvent; - selectedSearchEventSession = filesSearchDataSource.mxSession; - } - - if (selectedSearchEvent) - { - RoomViewController *roomViewController = segue.destinationViewController; - - [RoomDataSource loadRoomDataSourceWithRoomId:selectedSearchEvent.roomId - initialEventId:selectedSearchEvent.eventId - andMatrixSession:selectedSearchEventSession onComplete:^(RoomDataSource *roomDataSource) { - - [roomDataSource finalizeInitialization]; - roomDataSource.markTimelineInitialEvent = YES; - - [roomViewController displayRoom:roomDataSource]; - roomViewController.hasRoomDataSourceOwnership = YES; - - roomViewController.navigationItem.leftItemsSupplementBackButton = YES; - }]; - } - - // Hide back button title - self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; - } -} - #pragma mark - Search // Update search results under the currently selected tab diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.h b/Riot/Modules/Room/Settings/RoomSettingsViewController.h index dd960e8a6..1882a38cb 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.h +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.h @@ -19,6 +19,8 @@ #import "MediaPickerViewController.h" #import "TableViewCellWithCheckBoxes.h" +@class AnalyticsScreenTimer; + /** List the settings fields. Used to preselect/edit a field */ @@ -52,5 +54,10 @@ typedef enum : NSUInteger { */ @property (nonatomic) RoomSettingsViewControllerField selectedRoomSettingsField; +/** + The screen timer used for analytics if they've been enabled. The default value is nil. + */ +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + @end diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index 7ca017b57..aba02068d 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -311,9 +311,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"RoomSettings"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRules:) name:kMXNotificationCenterDidUpdateRules object:nil]; @@ -334,6 +331,8 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti { self.selectedRoomSettingsField = _selectedRoomSettingsField; } + + [self.screenTimer start]; } - (void)viewWillDisappear:(BOOL)animated @@ -351,6 +350,12 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti } } +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [self.screenTimer stop]; +} + // Those methods are called when the viewcontroller is added or removed from a container view controller. - (void)willMoveToParentViewController:(nullable UIViewController *)parent { @@ -424,7 +429,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti if (extraEventsListener) { MXWeakify(self); - [mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) { + [mxRoom liveTimeline:^(id liveTimeline) { MXStrongifyAndReturnIfNil(self); [liveTimeline removeListener:self->extraEventsListener]; @@ -1027,27 +1032,33 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n roomDetailsCopyRoomUrl] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Create a matrix.to permalink to the room - - NSString *permalink = [MXTools permalinkToRoom:roomAliasLabel.text]; - - if (permalink) - { - MXKPasteboardManager.shared.pasteboard.string = permalink; - } - else - { - MXLogDebug(@"[RoomSettingsViewController] Copy room URL failed. Room URL is nil"); - } - } - - }]]; + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + // Create a matrix.to permalink to the room + + NSString *permalink = [MXTools permalinkToRoom:roomAliasLabel.text]; + NSURL *url = [NSURL URLWithString:permalink]; + + if (url) + { + MXKPasteboardManager.shared.pasteboard.URL = url; + [self.view vc_toastWithMessage:VectorL10n.roomEventCopyLinkInfo + image:[UIImage imageNamed:@"link_icon"] + duration:2.0 + position:ToastPositionBottom + additionalMargin:0.0]; + } + else + { + MXLogDebug(@"[RoomSettingsViewController] Copy room URL failed. Room URL is nil"); + } + } + + }]]; // The user can only delete alias they has created, even if the Admin has set it as canonical. // So, let the server answer if it's possible to delete an alias. diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift index 6c0db2280..29192b639 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift @@ -34,6 +34,8 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType { weak var bubbleCellContentView: BubbleCellContentView? + private(set) var theme: Theme? + // Overrides override var bubbleInfoContainer: UIView! { @@ -168,6 +170,10 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType { if let bubbleCellReactionsDisplayable = self as? BubbleCellReactionsDisplayable { bubbleCellReactionsDisplayable.removeReactionsView() } + + if let bubbleCellThreadSummaryDisplayable = self as? BubbleCellThreadSummaryDisplayable { + bubbleCellThreadSummaryDisplayable.removeThreadSummaryView() + } } override func render(_ cellData: MXKCellData!) { @@ -201,6 +207,7 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType { // MARK: - Themable func update(theme: Theme) { + self.theme = theme self.bubbleCellContentView?.update(theme: theme) } @@ -244,6 +251,16 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType { func removeReactionsView() { self.bubbleCellContentView?.removeReactionsView() } + + // MARK: - BubbleCellThreadSummaryDisplayable + + func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) { + self.bubbleCellContentView?.addThreadSummaryView(threadSummaryView) + } + + func removeThreadSummaryView() { + self.bubbleCellContentView?.removeThreadSummaryView() + } // Encryption status diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift index 22597a4f2..7fc615f8b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift @@ -36,6 +36,9 @@ final class BubbleCellContentView: UIView, NibLoadable { @IBOutlet weak var innerContentView: UIView! + @IBOutlet weak var innerContentViewLeadingConstraint: NSLayoutConstraint! + @IBOutlet weak var innerContentViewTrailingConstraint: NSLayoutConstraint! + @IBOutlet weak var encryptionStatusContainerView: UIView! @IBOutlet weak var encryptionImageView: UIImageView! @@ -47,6 +50,8 @@ final class BubbleCellContentView: UIView, NibLoadable { @IBOutlet weak var reactionsContainerView: UIView! @IBOutlet weak var reactionsContentView: UIView! + + @IBOutlet weak var threadSummaryContainerView: UIView! @IBOutlet weak var bubbleOverlayContainer: UIView! @@ -69,6 +74,14 @@ final class BubbleCellContentView: UIView, NibLoadable { self.reactionsContainerView.isHidden = !newValue } } + + private var showThreadSummary: Bool { + get { + return !self.threadSummaryContainerView.isHidden + } set { + self.threadSummaryContainerView.isHidden = !newValue + } + } // MARK: Public @@ -143,3 +156,27 @@ extension BubbleCellContentView: BubbleCellReactionsDisplayable { self.reactionsContentView.vc_removeAllSubviews() } } + +// MARK: - BubbleCellThreadSummaryDisplayable +extension BubbleCellContentView: BubbleCellThreadSummaryDisplayable { + + func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) { + self.threadSummaryContainerView.vc_removeAllSubviews() + self.threadSummaryContainerView.addSubview(threadSummaryView) + NSLayoutConstraint.activate([ + threadSummaryView.leadingAnchor.constraint(equalTo: innerContentView.leadingAnchor), + threadSummaryView.topAnchor.constraint(equalTo: threadSummaryContainerView.topAnchor), + threadSummaryView.heightAnchor.constraint(equalToConstant: RoomBubbleCellLayout.threadSummaryViewHeight), + threadSummaryView.bottomAnchor.constraint(equalTo: threadSummaryContainerView.bottomAnchor), + threadSummaryView.trailingAnchor.constraint(lessThanOrEqualTo: threadSummaryContainerView.trailingAnchor, + constant: -RoomBubbleCellLayout.reactionsViewRightMargin) + ]) + self.showThreadSummary = true + } + + func removeThreadSummaryView() { + self.showThreadSummary = false + self.threadSummaryContainerView.vc_removeAllSubviews() + } + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib index 56c26c56c..999546651 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib @@ -1,9 +1,9 @@ - + - + @@ -140,14 +140,14 @@ - + - + @@ -195,6 +195,10 @@ + @@ -230,6 +234,8 @@ + + @@ -238,10 +244,11 @@ + - + diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellThreadSummaryDisplayable.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellThreadSummaryDisplayable.swift new file mode 100644 index 000000000..6c2848e27 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellThreadSummaryDisplayable.swift @@ -0,0 +1,23 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// `BubbleCellThreadSummaryDisplayable` is a protocol indicating that a cell support displaying a thread summary. +@objc protocol BubbleCellThreadSummaryDisplayable { + func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) + func removeThreadSummaryView() +} diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h similarity index 96% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h index 60cad3248..27efc0c4a 100644 --- a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.h +++ b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h @@ -168,7 +168,7 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction; @property (nonatomic) UIImage *picturePlaceholder; /** - The list of the temporary subviews that should be removed before reusing the cell (nil by default). + The list of the temporary subviews that should be removed before reusing the cell (empty array by default). */ @property (nonatomic) NSMutableArray *tmpSubviews; @@ -216,6 +216,8 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachViewMinHeightConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachViewTopConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachViewBottomConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachViewLeadingConstraint; +@property (weak, nonatomic) NSLayoutConstraint *attachViewTrailingConstraint; /** The constraints which defines the relationship between bubbleInfoContainer and its superview @@ -325,4 +327,10 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction; */ - (void)setupViews; +/// Add temporary subview to `tmpSubviews` property. +- (void)addTemporarySubview:(UIView*)subview; + +/// Called when content view cell is tapped +- (IBAction)onContentViewTap:(UITapGestureRecognizer*)sender; + @end diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m similarity index 99% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m index b550a1ef1..fff04b59a 100644 --- a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomBubbleTableViewCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m @@ -121,6 +121,7 @@ static BOOL _disableLongPressGestureOnEvent; self.readReceiptsAlignment = ReadReceiptAlignmentLeft; _allTextHighlighted = NO; _isAutoAnimatedGif = NO; + _tmpSubviews = [NSMutableArray array]; } - (void)awakeFromNib @@ -1005,14 +1006,11 @@ static BOOL _disableLongPressGestureOnEvent; self.bubbleInfoContainer.hidden = YES; // Remove temporary subviews - if (self.tmpSubviews) + for (UIView *view in self.tmpSubviews) { - for (UIView *view in self.tmpSubviews) - { - [view removeFromSuperview]; - } - self.tmpSubviews = nil; + [view removeFromSuperview]; } + [self.tmpSubviews removeAllObjects]; // Remove potential overlay subviews if (self.bubbleOverlayContainer) @@ -1071,10 +1069,7 @@ static BOOL _disableLongPressGestureOnEvent; - (BOOL)isBubbleDataContainsFileAttachment { - return bubbleData.attachment - && (bubbleData.attachment.type == MXKAttachmentTypeFile || bubbleData.attachment.type == MXKAttachmentTypeAudio || bubbleData.attachment.type == MXKAttachmentTypeVoiceMessage) - && bubbleData.attachment.contentURL - && bubbleData.attachment.contentInfo; + return bubbleData.isAttachment; } - (MXKRoomBubbleComponent*)closestBubbleComponentForGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer locationInView:(UIView*)view @@ -1130,6 +1125,16 @@ static BOOL _disableLongPressGestureOnEvent; [self resetAttachmentViewBottomConstraintConstant]; } +- (void)addTemporarySubview:(UIView*)subview +{ + if (!self.tmpSubviews) + { + self.tmpSubviews = [NSMutableArray new]; + } + + [self.tmpSubviews addObject:subview]; +} + #pragma mark - Attachment progress handling - (void)updateProgressUI:(NSDictionary*)statisticsDict diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSBubbleTableViewCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSBubbleTableViewCell.h rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSBubbleTableViewCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSBubbleTableViewCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSBubbleTableViewCell.m rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSBubbleTableViewCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSOutgoingBubbleTableViewCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.h rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSOutgoingBubbleTableViewCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSOutgoingBubbleTableViewCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.m rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSOutgoingBubbleTableViewCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.xib b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSOutgoingBubbleTableViewCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.xib rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIOSOutgoingBubbleTableViewCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIncomingBubbleTableViewCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingBubbleTableViewCell.h rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIncomingBubbleTableViewCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIncomingBubbleTableViewCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingBubbleTableViewCell.m rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomIncomingBubbleTableViewCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomOutgoingBubbleTableViewCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingBubbleTableViewCell.h rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomOutgoingBubbleTableViewCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomOutgoingBubbleTableViewCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingBubbleTableViewCell.m rename to Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomOutgoingBubbleTableViewCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/EmptyContent/MXKRoomEmptyBubbleTableViewCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.h rename to Riot/Modules/Room/Views/BubbleCells/EmptyContent/MXKRoomEmptyBubbleTableViewCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/EmptyContent/MXKRoomEmptyBubbleTableViewCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.m rename to Riot/Modules/Room/Views/BubbleCells/EmptyContent/MXKRoomEmptyBubbleTableViewCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.xib b/Riot/Modules/Room/Views/BubbleCells/EmptyContent/MXKRoomEmptyBubbleTableViewCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.xib rename to Riot/Modules/Room/Views/BubbleCells/EmptyContent/MXKRoomEmptyBubbleTableViewCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomEmptyBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/EmptyContent/RoomEmptyBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomEmptyBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/EmptyContent/RoomEmptyBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomEmptyBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/EmptyContent/RoomEmptyBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomEmptyBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/EmptyContent/RoomEmptyBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomEmptyBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/EmptyContent/RoomEmptyBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomEmptyBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/EmptyContent/RoomEmptyBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentBubbleCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentBubbleCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Common/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.xib similarity index 98% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.xib index 7d8f2e78f..bdc4ee247 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.xib @@ -1,9 +1,9 @@ - + - + @@ -128,6 +128,7 @@ + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib similarity index 98% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib index c6d5de460..c09b26ed2 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib @@ -1,9 +1,9 @@ - + - + @@ -161,6 +161,7 @@ + diff --git a/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.h new file mode 100644 index 000000000..24c064505 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.h @@ -0,0 +1,24 @@ +/* + Copyright 2015 OpenMarket 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 "RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h" + +/** + `RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell` displays outgoing attachment bubbles and pagination title but no sender name. + */ +@interface RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell : RoomOutgoingAttachmentWithPaginationTitleBubbleCell + +@end diff --git a/Riot/Managers/Analytics/DecryptionFailure.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.m similarity index 53% rename from Riot/Managers/Analytics/DecryptionFailure.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.m index d43b0ec9f..46ea41861 100644 --- a/Riot/Managers/Analytics/DecryptionFailure.m +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.m @@ -1,12 +1,13 @@ /* - Copyright 2018 New Vector Ltd - + Copyright 2015 OpenMarket Ltd + Copyright 2017 Vector Creations 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. @@ -14,25 +15,18 @@ limitations under the License. */ -#import "DecryptionFailure.h" +#import "RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.h" -const struct DecryptionFailureReasonStruct DecryptionFailureReason = { - .unspecified = @"unspecified_error", - .olmKeysNotSent = @"olm_keys_not_sent_error", - .olmIndexError = @"olm_index_error", - .unexpected = @"unexpected_error" -}; +#import "ThemeService.h" +#import "GeneratedInterface-Swift.h" -@implementation DecryptionFailure +@implementation RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell -- (instancetype)init +- (void)customizeTableViewCellRendering { - self = [super init]; - if (self) - { - _ts = [NSDate date].timeIntervalSince1970; - } - return self; + [super customizeTableViewCellRendering]; + + self.messageTextView.tintColor = ThemeService.shared.theme.tintColor; } @end diff --git a/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.xib new file mode 100644 index 000000000..cae2140da --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.xib @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib similarity index 98% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib index e31cd5257..73ca69699 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib @@ -1,9 +1,9 @@ - + - + @@ -95,6 +95,7 @@ + diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.m similarity index 93% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.m index 9a3e1cfe1..84c05d49a 100644 --- a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.m @@ -23,6 +23,8 @@ #import "MXKImageView.h" #import "MXKPieChartView.h" +#import "GeneratedInterface-Swift.h" + @implementation MXKRoomOutgoingAttachmentBubbleCell - (void)dealloc @@ -30,6 +32,15 @@ [self stopAnimating]; } +- (void)setupViews +{ + [super setupViews]; + + RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared]; + + [timelineConfiguration.currentStyle.cellLayoutUpdater setupLayoutForOutgoingFileAttachmentCell:self]; +} + - (void)render:(MXKCellData *)cellData { [super render:cellData]; diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Common/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Location/LocationBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Location/LocationBubbleCell.swift new file mode 100644 index 000000000..a0c375569 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Location/LocationBubbleCell.swift @@ -0,0 +1,71 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable { + + private var locationView: RoomTimelineLocationView! + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard #available(iOS 14.0, *), + let bubbleData = cellData as? RoomBubbleCellData, + let event = bubbleData.events.last, + event.eventType == __MXEventType.roomMessage, + let locationContent = event.location + else { + return + } + + locationView.update(theme: ThemeService.shared().theme) + locationView.locationDescription = locationContent.locationDescription + + let location = CLLocationCoordinate2D(latitude: locationContent.latitude, longitude: locationContent.longitude) + + let mapStyleURL = bubbleData.mxSession.vc_homeserverConfiguration().tileServer.mapStyleURL + + if locationContent.assetType == .user { + let avatarViewData = AvatarViewData(matrixItemId: bubbleData.senderId, + displayName: bubbleData.senderDisplayName, + avatarUrl: bubbleData.senderAvatarUrl, + mediaManager: bubbleData.mxSession.mediaManager, + fallbackImage: .matrixItem(bubbleData.senderId, bubbleData.senderDisplayName)) + + locationView.displayLocation(location, userAvatarData: avatarViewData, mapStyleURL: mapStyleURL) + } else { + locationView.displayLocation(location, mapStyleURL: mapStyleURL) + } + } + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.backgroundColor = .clear + bubbleCellContentView?.showSenderInfo = true + bubbleCellContentView?.showPaginationTitle = false + + guard #available(iOS 14.0, *), + let contentView = bubbleCellContentView?.innerContentView else { + return + } + + locationView = RoomTimelineLocationView.loadFromNib() + + contentView.vc_addSubViewMatchingParent(locationView) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Location/LocationWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Location/LocationWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..e3f6dd84f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Location/LocationWithPaginationTitleBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationWithPaginationTitleBubbleCell: LocationBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Location/LocationWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Location/LocationWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..1329221dd --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Location/LocationWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationWithoutSenderInfoBubbleCell: LocationBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/MessageTyping/RoomTypingBubbleCell.swift similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.swift rename to Riot/Modules/Room/Views/BubbleCells/MessageTyping/RoomTypingBubbleCell.swift diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/MessageTyping/RoomTypingBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomTypingBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/MessageTyping/RoomTypingBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift index 4d3d42e28..c9263a37b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Poll/PollBubbleCell.swift @@ -29,7 +29,7 @@ class PollBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable { let bubbleData = cellData as? RoomBubbleCellData, let event = bubbleData.events.last, event.eventType == __MXEventType.pollStart, - let view = PollTimelineProvider.shared.buildPollTimelineViewForEvent(event) else { + let view = TimelinePollProvider.shared.buildTimelinePollViewForEvent(event) else { return } @@ -49,7 +49,7 @@ class PollBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable { } // The normal flow for tapping on cell content views doesn't work for bubbles without attributed strings - func onContentViewTap(_ sender: UITapGestureRecognizer) { + override func onContentViewTap(_ sender: UITapGestureRecognizer) { guard let event = self.event else { return } @@ -57,3 +57,5 @@ class PollBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable { delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event]) } } + +extension PollBubbleCell: BubbleCellThreadSummaryDisplayable {} diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift b/Riot/Modules/Room/Views/BubbleCells/RoomBubbleCellLayout.swift similarity index 88% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift rename to Riot/Modules/Room/Views/BubbleCells/RoomBubbleCellLayout.swift index b9d1db6e7..641bc0677 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift +++ b/Riot/Modules/Room/Views/BubbleCells/RoomBubbleCellLayout.swift @@ -46,4 +46,10 @@ final class RoomBubbleCellLayout: NSObject { static let encryptedContentLeftMargin: CGFloat = 15.0 static let urlPreviewViewTopMargin: CGFloat = 8.0 + + // Threads + + static let threadSummaryViewTopMargin: CGFloat = 8.0 + static let threadSummaryViewHeight: CGFloat = 40.0 + static let fromAThreadViewTopMargin: CGFloat = 8.0 } diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationCollapsedBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationCollapsedBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomCreationCollapsedBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationCollapsedBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationCollapsedBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationCollapsedBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomCreationCollapsedBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationCollapsedBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationCollapsedBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationCollapsedBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomCreationCollapsedBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationCollapsedBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationWithPaginationCollapsedBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationWithPaginationCollapsedBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomCreationWithPaginationCollapsedBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationWithPaginationCollapsedBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationWithPaginationCollapsedBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationWithPaginationCollapsedBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomCreationWithPaginationCollapsedBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationWithPaginationCollapsedBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomCreationWithPaginationCollapsedBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationWithPaginationCollapsedBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomCreationWithPaginationCollapsedBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomCreation/RoomCreationWithPaginationCollapsedBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipExpandedWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipExpandedWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomMembershipWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomMembershipWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomMembership/RoomMembershipWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomPredecessorBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/RoomPredecessor/RoomPredecessorBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomPredecessorBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/RoomPredecessor/RoomPredecessorBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomPredecessorBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/RoomPredecessor/RoomPredecessorBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomPredecessorBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/RoomPredecessor/RoomPredecessorBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomPredecessorBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/RoomPredecessor/RoomPredecessorBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomPredecessorBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/RoomPredecessor/RoomPredecessorBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h new file mode 100644 index 000000000..13d9e504a --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h @@ -0,0 +1,144 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/// RoomTimelineCellIdentifier represents room timeline cell identifiers. +typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) { + + RoomTimelineCellIdentifierUnknown, + + // - Text message + // -- Incoming + // --- Clear + RoomTimelineCellIdentifierIncomingTextMessage, + RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitle, + RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderName, + RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitleWithoutSenderName, + // --- Encrypted + RoomTimelineCellIdentifierIncomingTextMessageEncrypted, + RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitle, + RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderName, + RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitleWithoutSenderName, + // -- Outgoing + // --- Clear + RoomTimelineCellIdentifierOutgoingTextMessage, + RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle, + RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName, + RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName, + // --- Encrypted + RoomTimelineCellIdentifierOutgoingTextMessageEncrypted, + RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle, + RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName, + RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName, + + // - Attachment + // -- Incoming + // --- Clear + RoomTimelineCellIdentifierIncomingAttachment, + RoomTimelineCellIdentifierIncomingAttachmentWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingAttachmentWithPaginationTitle, + // --- Encrypted + RoomTimelineCellIdentifierIncomingAttachmentEncrypted, + RoomTimelineCellIdentifierIncomingAttachmentEncryptedWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingAttachmentEncryptedWithPaginationTitle, + // -- Outgoing + // --- Clear + RoomTimelineCellIdentifierOutgoingAttachment, + RoomTimelineCellIdentifierOutgoingAttachmentWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingAttachmentWithPaginationTitle, + // --- Encrypted + RoomTimelineCellIdentifierOutgoingAttachmentEncrypted, + RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithPaginationTitle, + + // - Attachment without thumbnail + // --- Clear + RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnail, + RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithPaginationTitle, + // --- Encrypted + RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncrypted, + RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithPaginationTitle, + // -- Outgoing + // --- Clear + RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnail, + RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithPaginationTitle, + // --- Encrypted + RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncrypted, + RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithPaginationTitle, + + // - Room membership + RoomTimelineCellIdentifierMembership, + RoomTimelineCellIdentifierMembershipWithPaginationTitle, + RoomTimelineCellIdentifierMembershipCollapsed, + RoomTimelineCellIdentifierMembershipCollapsedWithPaginationTitle, + RoomTimelineCellIdentifierMembershipExpanded, + RoomTimelineCellIdentifierMembershipExpandedWithPaginationTitle, + + // - Key verification + RoomTimelineCellIdentifierKeyVerificationIncomingRequestApproval, + RoomTimelineCellIdentifierKeyVerificationIncomingRequestApprovalWithPaginationTitle, + RoomTimelineCellIdentifierKeyVerificationRequestStatus, + RoomTimelineCellIdentifierKeyVerificationRequestStatusWithPaginationTitle, + RoomTimelineCellIdentifierKeyVerificationConclusion, + RoomTimelineCellIdentifierKeyVerificationConclusionWithPaginationTitle, + + // - Room creation + RoomTimelineCellIdentifierRoomCreationCollapsed, + RoomTimelineCellIdentifierRoomCreationCollapsedWithPaginationTitle, + + // - Call + RoomTimelineCellIdentifierDirectCallStatus, + RoomTimelineCellIdentifierGroupCallStatus, + + // - Voice message + // -- Incoming + RoomTimelineCellIdentifierIncomingVoiceMessage, + RoomTimelineCellIdentifierIncomingVoiceMessageWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingVoiceMessageWithPaginationTitle, + // -- Outgoing + RoomTimelineCellIdentifierOutgoingVoiceMessage, + RoomTimelineCellIdentifierOutgoingVoiceMessageWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingVoiceMessageWithPaginationTitle, + + // - Poll + RoomTimelineCellIdentifierPoll, + RoomTimelineCellIdentifierPollWithoutSenderInfo, + RoomTimelineCellIdentifierPollWithPaginationTitle, + + // - Location sharing + // -- Incoming + RoomTimelineCellIdentifierIncomingLocation, + RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle, + // -- Outgoing + RoomTimelineCellIdentifierOutgoingLocation, + RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle, + + // - Others + RoomTimelineCellIdentifierEmpty, + RoomTimelineCellIdentifierSelectedSticker, + RoomTimelineCellIdentifierRoomPredecessor, + RoomTimelineCellIdentifierRoomCreationIntro, + RoomTimelineCellIdentifierTyping +}; diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTimelineConfiguration.swift b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineConfiguration.swift new file mode 100644 index 000000000..1cd3b60d7 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineConfiguration.swift @@ -0,0 +1,96 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// RoomTimelineConfiguration enables to manage room timeline appearance configuration +@objcMembers +class RoomTimelineConfiguration: NSObject { + + // MARK: - Constants + + static let shared = RoomTimelineConfiguration() + + // MARK: - Properties + + private(set) var currentStyle: RoomTimelineStyle + + // MARK: - Setup + + init(style: RoomTimelineStyle) { + self.currentStyle = style + + super.init() + + self.registerThemeDidChange() + } + + convenience init(styleIdentifier: RoomTimelineStyleIdentifier) { + + let style = type(of: self).style(for: styleIdentifier) + self.init(style: style) + } + + convenience override init() { + let styleIdentifier = RiotSettings.shared.roomTimelineStyleIdentifier + self.init(styleIdentifier: styleIdentifier) + } + + // MARK: - Public + + func updateStyle(_ roomTimelineStyle: RoomTimelineStyle) { + self.currentStyle = roomTimelineStyle + } + + func updateStyle(withIdentifier identifier: RoomTimelineStyleIdentifier) { + + let style = type(of: self).style(for: identifier) + + self.updateStyle(style) + } + + // MARK: - Private + + private func registerThemeDidChange() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange(notification:)), name: .themeServiceDidChangeTheme, object: nil) + + } + + @objc private func themeDidChange(notification: Notification) { + + guard let themeService = notification.object as? ThemeService else { + return + } + + self.currentStyle.update(theme: themeService.theme) + } + + private class func style(for identifier: RoomTimelineStyleIdentifier) -> RoomTimelineStyle { + + let roomTimelineStyle: RoomTimelineStyle + + let theme = ThemeService.shared().theme + + switch identifier { + case .plain: + roomTimelineStyle = PlainRoomTimelineStyle(theme: theme) + case .bubble: + roomTimelineStyle = BubbleRoomTimelineStyle(theme: theme) + } + + return roomTimelineStyle + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift index d7f00f0d7..9c802f469 100644 --- a/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift @@ -130,6 +130,13 @@ class SizableBaseBubbleCell: BaseBubbleCell, SizableBaseBubbleCellType { let reactionsHeight = self.reactionsViewSizer.height(for: bubbleReactionsViewModel, fittingWidth: reactionWidth) height+=reactionsHeight } + + // Add thread summary view height if needed + if sizingView is BubbleCellThreadSummaryDisplayable, + let roomBubbleCellData = cellData as? RoomBubbleCellData, + roomBubbleCellData.hasThreadRoot { + height += RoomBubbleCellLayout.threadSummaryViewHeight + } return height } diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.h similarity index 93% rename from Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.h index 689b39403..67526a1dd 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.h +++ b/Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.h @@ -27,7 +27,6 @@ @property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *userNameLabelTopConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachViewLeadingConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *descriptionContainerViewBottomConstraint; @end diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.m similarity index 97% rename from Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.m index d5f48404a..cc0506fd2 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.m @@ -121,6 +121,10 @@ } } + RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared]; + + [timelineConfiguration.currentStyle.cellLayoutUpdater updateLayoutForSelectedStickerCell:self]; + // Retrieve the suitable content size for the attachment thumbnail CGSize contentSize = bubbleData.contentSize; // Update image view frame in order to center loading wheel (if any) diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomSelectedStickerBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/Sticker/RoomSelectedStickerBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift new file mode 100644 index 000000000..b4c2a1c80 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift @@ -0,0 +1,389 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@objcMembers +class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating { + + // MARK: - Properties + + private var theme: Theme + + private var incomingColor: UIColor { + return self.theme.roomCellIncomingBubbleBackgroundColor + } + + private var outgoingColor: UIColor { + return self.theme.roomCellOutgoingBubbleBackgroundColor + } + + // MARK: - Setup + + init(theme: Theme) { + self.theme = theme + } + + // MARK: - Public + + func updateLayoutIfNeeded(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) { + + if cellData.isIncoming { + self.updateLayout(forIncomingTextMessageCell: cell, andCellData: cellData) + } else { + self.updateLayout(forOutgoingTextMessageCell: cell, andCellData: cellData) + } + } + + func updateLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) { + + if let messageBubbleBackgroundView = cell.messageBubbleBackgroundView { + + if self.canUseBubbleBackground(forCell: cell, withCellData: cellData) { + + messageBubbleBackgroundView.isHidden = false + + self.updateMessageBubbleBackgroundView(messageBubbleBackgroundView, withCell: cell, andCellData: cellData) + } else { + messageBubbleBackgroundView.isHidden = true + } + } + } + + func updateLayout(forOutgoingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) { + + if let messageBubbleBackgroundView = cell.messageBubbleBackgroundView { + + if self.canUseBubbleBackground(forCell: cell, withCellData: cellData) { + + messageBubbleBackgroundView.isHidden = false + + self.updateMessageBubbleBackgroundView(messageBubbleBackgroundView, withCell: cell, andCellData: cellData) + } else { + messageBubbleBackgroundView.isHidden = true + } + } + } + + func setupLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell) { + + self.setupIncomingMessageTextViewMargins(for: cell) + + self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.incomingColor) + + cell.setNeedsUpdateConstraints() + } + + func setupLayout(forOutgoingTextMessageCell cell: MXKRoomBubbleTableViewCell) { + + self.setupOutgoingMessageTextViewMargins(for: cell) + + // Hide avatar view + cell.pictureView?.isHidden = true + + self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.outgoingColor) + + cell.setNeedsUpdateConstraints() + } + + func setupLayout(forOutgoingFileAttachmentCell cell: MXKRoomBubbleTableViewCell) { + + // Hide avatar view + cell.pictureView?.isHidden = true + + self.setupOutgoingFileAttachViewMargins(for: cell) + } + + func setupLayout(forIncomingFileAttachmentCell cell: MXKRoomBubbleTableViewCell) { + + self.setupIncomingFileAttachViewMargins(for: cell) + } + + func updateLayout(forSelectedStickerCell cell: RoomSelectedStickerBubbleCell) { + + if cell.bubbleData.isIncoming { + self.setupLayout(forIncomingFileAttachmentCell: cell) + } else { + self.setupLayout(forOutgoingFileAttachmentCell: cell) + cell.userNameLabel?.isHidden = true + cell.pictureView?.isHidden = true + } + } + + // MARK: Themable + + func update(theme: Theme) { + self.theme = theme + } + + // MARK: - Private + + // MARK: Bubble background view + + private func createBubbleBackgroundView(with backgroundColor: UIColor) -> RoomMessageBubbleBackgroundView { + + let bubbleBackgroundView = RoomMessageBubbleBackgroundView() + bubbleBackgroundView.backgroundColor = backgroundColor + + return bubbleBackgroundView + } + + private func addBubbleBackgroundViewToCell(_ bubbleCell: MXKRoomBubbleTableViewCell, backgroundColor: UIColor) { + + guard let messageTextView = bubbleCell.messageTextView else { + return + } + + let topMargin: CGFloat = 0.0 + let leftMargin: CGFloat = 5.0 + let rightMargin: CGFloat = 45.0 // Add extra space for timestamp + + let bubbleBackgroundView = self.createBubbleBackgroundView(with: backgroundColor) + + bubbleCell.contentView.insertSubview(bubbleBackgroundView, at: 0) + + let topAnchor = messageTextView.topAnchor + let leadingAnchor = messageTextView.leadingAnchor + let trailingAnchor = messageTextView.trailingAnchor + + bubbleBackgroundView.updateHeight(messageTextView.frame.height) + + NSLayoutConstraint.activate([ + bubbleBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: topMargin), + bubbleBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -leftMargin), + bubbleBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: rightMargin) + ]) + } + + private func canUseBubbleBackground(forCell cell: MXKRoomBubbleTableViewCell, withCellData cellData: MXKRoomBubbleCellData) -> Bool { + + guard let firstComponent = cellData.getFirstBubbleComponentWithDisplay(), let firstEvent = firstComponent.event else { + return false + } + + switch firstEvent.eventType { + case .roomMessage: + if let messageType = firstEvent.messageType { + switch messageType { + case .text, .file: + return true + case .emote: + // Explicitely disable bubble for emotes + return false + default: + break + } + } + default: + break + } + + return false + } + + private func getTextMessageHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? { + + guard let roomBubbleCellData = cellData as? RoomBubbleCellData, + let lastBubbleComponent = cellData.getLastBubbleComponentWithDisplay(), + let firstComponent = roomBubbleCellData.getFirstBubbleComponentWithDisplay() else { + return nil + } + + let bubbleHeight: CGFloat + + let lastEventId = lastBubbleComponent.event.eventId + let lastMessageBottomPosition = cell.bottomPosition(ofEvent: lastEventId) + + let firstEventId = firstComponent.event.eventId + let firstMessageTopPosition = cell.topPosition(ofEvent: firstEventId) + + let additionalContentHeight = roomBubbleCellData.additionalContentHeight + + bubbleHeight = lastMessageBottomPosition - firstMessageTopPosition - additionalContentHeight + + guard bubbleHeight >= 0 else { + return nil + } + + return bubbleHeight + } + + // TODO: Improve text message height calculation + // This method is closer to final result but lack of stability because of extra vertical space not handled here. +// private func getTextMessageHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? { +// +// guard let roomBubbleCellData = cellData as? RoomBubbleCellData, +// let firstComponent = roomBubbleCellData.getFirstBubbleComponentWithDisplay() else { +// return nil +// } +// +// let bubbleHeight: CGFloat +// +// let componentIndex = cellData.bubbleComponentIndex(forEventId: firstComponent.event.eventId) +// +// let componentFrame = cell.componentFrameInContentView(for: componentIndex) +// +// bubbleHeight = componentFrame.height +// +// guard bubbleHeight >= 0 else { +// return nil +// } +// +// return bubbleHeight +// } + + private func getMessageBubbleBackgroundHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? { + + var finalBubbleHeight: CGFloat? + let extraMargin: CGFloat = 4.0 + + if let bubbleHeight = self.getTextMessageHeight(for: cell, andCellData: cellData) { + finalBubbleHeight = bubbleHeight + extraMargin + + } else if let messageTextViewHeight = cell.messageTextView?.frame.height { + + finalBubbleHeight = messageTextViewHeight + extraMargin + } + + return finalBubbleHeight + } + + @discardableResult + private func updateMessageBubbleBackgroundView(_ roomMessageBubbleBackgroundView: RoomMessageBubbleBackgroundView, withCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> Bool { + + if let bubbleHeight = self.getMessageBubbleBackgroundHeight(for: cell, andCellData: cellData) { + return roomMessageBubbleBackgroundView.updateHeight(bubbleHeight) + } else { + return false + } + } + + private func getIncomingMessageTextViewInsets(from bubbleCell: MXKRoomBubbleTableViewCell) -> UIEdgeInsets { + + let messageViewMarginTop: CGFloat + let messageViewMarginBottom: CGFloat = -2.0 + let messageViewMarginLeft: CGFloat = 3.0 + let messageViewMarginRight: CGFloat = 80 + + if bubbleCell.userNameLabel != nil { + messageViewMarginTop = 10.0 + } else { + messageViewMarginTop = 0.0 + } + + let messageViewInsets = UIEdgeInsets(top: messageViewMarginTop, left: messageViewMarginLeft, bottom: messageViewMarginBottom, right: messageViewMarginRight) + + return messageViewInsets + } + + // MARK: Text message + + private func setupIncomingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) { + + guard cell.messageTextView != nil else { + return + } + + let messageViewInsets = self.getIncomingMessageTextViewInsets(from: cell) + + cell.msgTextViewBottomConstraint.constant += messageViewInsets.bottom + cell.msgTextViewTopConstraint.constant += messageViewInsets.top + cell.msgTextViewLeadingConstraint.constant += messageViewInsets.left + cell.msgTextViewTrailingConstraint.constant += messageViewInsets.right + } + + private func setupOutgoingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) { + + guard let messageTextView = cell.messageTextView else { + return + } + + let contentView = cell.contentView + + let leftMargin: CGFloat = 80.0 + let rightMargin: CGFloat = 78.0 + let bottomMargin: CGFloat = -2.0 + + cell.msgTextViewLeadingConstraint.isActive = false + cell.msgTextViewTrailingConstraint.isActive = false + + let leftConstraint = messageTextView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: leftMargin) + + let rightConstraint = messageTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -rightMargin) + + NSLayoutConstraint.activate([ + leftConstraint, + rightConstraint + ]) + + cell.msgTextViewLeadingConstraint = leftConstraint + cell.msgTextViewTrailingConstraint = rightConstraint + + cell.msgTextViewBottomConstraint.constant += bottomMargin + } + + private func setupOutgoingFileAttachViewMargins(for cell: MXKRoomBubbleTableViewCell) { + + guard let attachmentView = cell.attachmentView else { + return + } + + let contentView = cell.contentView + + // TODO: Use constants + // Same as URL preview + let rightMargin: CGFloat = 34.0 + + if let attachViewLeadingConstraint = cell.attachViewLeadingConstraint { + attachViewLeadingConstraint.isActive = false + cell.attachViewLeadingConstraint = nil + } + + let rightConstraint = attachmentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -rightMargin) + + NSLayoutConstraint.activate([ + rightConstraint + ]) + + cell.attachViewTrailingConstraint = rightConstraint + } + + private func setupIncomingFileAttachViewMargins(for cell: MXKRoomBubbleTableViewCell) { + + guard let attachmentView = cell.attachmentView, + cell.attachViewLeadingConstraint == nil || cell.attachViewLeadingConstraint.isActive == false else { + return + } + + if let attachViewTrailingConstraint = cell.attachViewTrailingConstraint { + attachViewTrailingConstraint.isActive = false + cell.attachViewTrailingConstraint = nil + } + + let contentView = cell.contentView + + // TODO: Use constants + let leftMargin: CGFloat = 67 + + let leftConstraint = attachmentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: -leftMargin) + + NSLayoutConstraint.activate([ + leftConstraint + ]) + + cell.attachViewLeadingConstraint = leftConstraint + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift new file mode 100644 index 000000000..b12502d9c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift @@ -0,0 +1,312 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator { + + override func addTimestampLabelIfNeeded(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) { + + guard self.canShowTimestamp(forCellData: cellData) else { + return + } + + self.addTimestampLabel(toCell: cell, cellData: cellData) + } + + override func addTimestampLabel(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) { + + // If cell contains a bubble background, add the timestamp inside of it + if let bubbleBackgroundView = cell.messageBubbleBackgroundView, bubbleBackgroundView.isHidden == false, let timestampLabel = self.createTimestampLabel(for: cellData) { + + self.addTimestampLabel(timestampLabel, + to: cell, + on: bubbleBackgroundView, + constrainingView: bubbleBackgroundView) + + } else if cellData.isAttachmentWithThumbnail { + + if cellData.attachment?.type == .sticker, + let attachmentView = cell.attachmentView, + let timestampLabel = self.createTimestampLabel(for: cellData) { + + // Prevent overlap with send status icon + let bottomMargin: CGFloat = 20.0 + let rightMargin: CGFloat = -27.0 + + self.addTimestampLabel(timestampLabel, + to: cell, + on: cell.contentView, + constrainingView: attachmentView, + rightMargin: rightMargin, + bottomMargin: bottomMargin) + + } else if let attachmentView = cell.attachmentView, let timestampLabel = self.createTimestampLabel(for: cellData, textColor: self.theme.baseIconPrimaryColor) { + // For media with thumbnail cells, add timestamp inside thumbnail + + self.addTimestampLabel(timestampLabel, + to: cell, + on: cell.contentView, + constrainingView: attachmentView) + + } else { + super.addTimestampLabel(toCell: cell, cellData: cellData) + } + } else if let voiceMessageCell = cell as? VoiceMessageBubbleCell, let playbackView = voiceMessageCell.playbackController?.playbackView, let timestampLabel = self.createTimestampLabel(for: cellData) { + + // Add timestamp on cell inherting from VoiceMessageBubbleCell + + self.addTimestampLabel(timestampLabel, + to: cell, + on: cell.contentView, + constrainingView: playbackView) + + } else if let fileWithoutThumbnailCell = cell as? FileWithoutThumbnailBaseBubbleCell, let fileAttachementView = fileWithoutThumbnailCell.fileAttachementView, let timestampLabel = self.createTimestampLabel(for: cellData) { + + // Add timestamp on cell inherting from VoiceMessageBubbleCell + + self.addTimestampLabel(timestampLabel, + to: cell, + on: fileAttachementView, + constrainingView: fileAttachementView) + + } else { + super.addTimestampLabel(toCell: cell, cellData: cellData) + } + } + + override func addReactionView(_ reactionsView: BubbleReactionsView, + toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData, contentViewPositionY: CGFloat, upperDecorationView: UIView?) { + + cell.addTemporarySubview(reactionsView) + + if let reactionsDisplayable = cell as? BubbleCellReactionsDisplayable { + reactionsDisplayable.addReactionsView(reactionsView) + return + } + + reactionsView.translatesAutoresizingMaskIntoConstraints = false + + let cellContentView = cell.contentView + + cellContentView.addSubview(reactionsView) + + // TODO: Use constants + let topMargin: CGFloat = 4.0 + let leftMargin: CGFloat + let rightMargin: CGFloat + + // Incoming message + if cellData.isIncoming { + + var incomingLeftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin + + if cellData.containsBubbleComponentWithEncryptionBadge { + incomingLeftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin + } + + leftMargin = incomingLeftMargin - 6.0 + + // TODO: Use constants + let messageViewMarginRight: CGFloat = 42.0 + + rightMargin = messageViewMarginRight + } else { + // Outgoing message + + reactionsView.alignment = .right + + // TODO: Use constants + var outgoingLeftMargin: CGFloat = 80.0 + + if cellData.containsBubbleComponentWithEncryptionBadge { + outgoingLeftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin + } + + leftMargin = outgoingLeftMargin + + // TODO: Use constants + rightMargin = 33 + } + + let leadingConstraint = reactionsView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin) + + let trailingConstraint = reactionsView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin) + + let topConstraint: NSLayoutConstraint + if let upperDecorationView = upperDecorationView { + topConstraint = reactionsView.topAnchor.constraint(equalTo: upperDecorationView.bottomAnchor, constant: topMargin) + } else { + topConstraint = reactionsView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: contentViewPositionY + topMargin) + } + + NSLayoutConstraint.activate([ + leadingConstraint, + trailingConstraint, + topConstraint + ]) + } + + override func addURLPreviewView(_ urlPreviewView: URLPreviewView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat) { + + cell.addTemporarySubview(urlPreviewView) + + let cellContentView = cell.contentView + + urlPreviewView.translatesAutoresizingMaskIntoConstraints = false + urlPreviewView.availableWidth = cellData.maxTextViewWidth + cellContentView.addSubview(urlPreviewView) + + let leadingOrTrailingConstraint: NSLayoutConstraint + + + // Incoming message + if cellData.isIncoming { + + var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin + if cellData.containsBubbleComponentWithEncryptionBadge { + leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin + } + + leftMargin-=5.0 + + leadingOrTrailingConstraint = urlPreviewView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin) + } else { + // Outgoing message + + // TODO: Use constants + let rightMargin: CGFloat = 34.0 + + leadingOrTrailingConstraint = urlPreviewView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin) + } + + let topMargin = contentViewPositionY + RoomBubbleCellLayout.urlPreviewViewTopMargin + RoomBubbleCellLayout.reactionsViewTopMargin + + // Set the preview view's origin + NSLayoutConstraint.activate([ + leadingOrTrailingConstraint, + urlPreviewView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: topMargin) + ]) + } + + // MARK: - Private + + // MARK: Timestamp management + + private func createTimestampLabel(cellData: MXKRoomBubbleCellData, bubbleComponent: MXKRoomBubbleComponent, viewTag: Int, textColor: UIColor) -> UILabel { + + let timeLabel = UILabel() + + timeLabel.text = cellData.eventFormatter.timeString(from: bubbleComponent.date) + timeLabel.textAlignment = .right + timeLabel.textColor = textColor + timeLabel.font = self.theme.fonts.caption2 + timeLabel.adjustsFontSizeToFitWidth = true + timeLabel.tag = viewTag + timeLabel.accessibilityIdentifier = "timestampLabel" + + return timeLabel + } + + func createTimestampLabel(for cellData: RoomBubbleCellData) -> UILabel? { + return self.createTimestampLabel(for: cellData, textColor: self.theme.textSecondaryColor) + } + + private func createTimestampLabel(for cellData: RoomBubbleCellData, textColor: UIColor) -> UILabel? { + + let componentIndex = cellData.mostRecentComponentIndex + + guard let bubbleComponents = cellData.bubbleComponents, componentIndex < bubbleComponents.count else { + return nil + } + + let component = bubbleComponents[componentIndex] + + return self.createTimestampLabel(cellData: cellData, bubbleComponent: component, viewTag: componentIndex, textColor: textColor) + } + + private func canShowTimestamp(forCellData cellData: MXKRoomBubbleCellData) -> Bool { + + guard cellData.isCollapsableAndCollapsed == false else { + return false + } + + guard let firstComponent = cellData.getFirstBubbleComponentWithDisplay(), let firstEvent = firstComponent.event else { + return false + } + + switch cellData.cellDataTag { + case .location: + return true + default: + break + } + + if let attachmentType = cellData.attachment?.type { + switch attachmentType { + case .voiceMessage, .audio: + return true + default: + break + } + } + + if cellData.isAttachmentWithThumbnail { + return true + } + + switch firstEvent.eventType { + case .roomMessage: + if let messageType = firstEvent.messageType { + switch messageType { + case .text, .emote, .file: + return true + default: + break + } + } + default: + break + } + + return false + } + + private func addTimestampLabel(_ timestampLabel: UILabel, + to cell: MXKRoomBubbleTableViewCell, + on containerView: UIView, + constrainingView: UIView, + rightMargin: CGFloat = 8.0, + bottomMargin: CGFloat = 4.0) { + timestampLabel.translatesAutoresizingMaskIntoConstraints = false + + cell.addTemporarySubview(timestampLabel) + + containerView.addSubview(timestampLabel) + + let trailingConstraint = timestampLabel.trailingAnchor.constraint(equalTo: constrainingView.trailingAnchor, constant: -rightMargin) + + let bottomConstraint = timestampLabel.bottomAnchor.constraint(equalTo: constrainingView.bottomAnchor, constant: -bottomMargin) + + NSLayoutConstraint.activate([ + trailingConstraint, + bottomConstraint + ]) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.h b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.h new file mode 100644 index 000000000..a389242cc --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.h @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "PlainRoomTimelineCellProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface BubbleRoomTimelineCellProvider : PlainRoomTimelineCellProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m new file mode 100644 index 000000000..b387a9b45 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m @@ -0,0 +1,194 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "BubbleRoomTimelineCellProvider.h" + +#pragma mark - Imports + +#pragma mark Text message + +// Outgoing + +// Clear +#import "RoomOutgoingTextMsgBubbleCell.h" +#import "RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h" +#import "RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.h" + +// Encrypted + +#import "RoomOutgoingEncryptedTextMsgBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" + +#pragma mark Attachment + +// Outgoing + +// Clear +#import "RoomOutgoingAttachmentBubbleCell.h" +#import "RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h" +// Encrypted +#import "RoomOutgoingEncryptedAttachmentBubbleCell.h" +#import "RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h" + +#import "GeneratedInterface-Swift.h" + +@implementation BubbleRoomTimelineCellProvider + +#pragma mark - Registration + +- (void)registerCellsForTableView:(UITableView *)tableView +{ + [super registerCellsForTableView:tableView]; + + [self registerFileWithoutThumbnailCellsForTableView:tableView]; +} + +- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView +{ + // Incoming + [tableView registerClass:VoiceMessageIncomingBubbleCell.class forCellReuseIdentifier:VoiceMessageIncomingBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceMessageIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceMessageIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceMessageIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceMessageIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + // Outgoing + [tableView registerClass:VoiceMessageOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceMessageOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceMessageOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceMessageOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +- (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]; +} + +- (void)registerFileWithoutThumbnailCellsForTableView:(UITableView*)tableView +{ + // Incoming + [tableView registerClass:FileWithoutThumbnailIncomingBubbleCell.class forCellReuseIdentifier:FileWithoutThumbnailIncomingBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + + // Outgoing + [tableView registerClass:FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +#pragma mark - Mapping + +- (NSDictionary*)outgoingTextMessageCellsMapping +{ + // Hide sender info and avatar for bubble outgoing messages + return @{ + // Clear + @(RoomTimelineCellIdentifierOutgoingTextMessage) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName) : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierOutgoingTextMessageEncrypted) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + }; +} + +- (NSDictionary*)outgoingAttachmentCellsMapping +{ + // Hide sender info and avatar for bubble outgoing file attachment + return @{ + // Clear + @(RoomTimelineCellIdentifierOutgoingAttachment) : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutSenderInfo) : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithPaginationTitle) : RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierOutgoingAttachmentEncrypted) : RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithPaginationTitle) : RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)incomingAttachmentWithoutThumbnailCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnail) : FileWithoutThumbnailIncomingBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithoutSenderInfo) : FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithPaginationTitle) : FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncrypted) : FileWithoutThumbnailIncomingBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo) : FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithPaginationTitle) : FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)outgoingAttachmentWithoutThumbnailCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnail) : FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithoutSenderInfo) : FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithPaginationTitle) : FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncrypted) : FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo) : FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithPaginationTitle) : FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)voiceMessageCellsMapping +{ + return @{ + // Incoming + @(RoomTimelineCellIdentifierIncomingVoiceMessage) : VoiceMessageIncomingBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceMessageWithoutSenderInfo) : VoiceMessageIncomingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceMessageWithPaginationTitle) : VoiceMessageIncomingWithPaginationTitleBubbleCell.class, + // Outgoing + @(RoomTimelineCellIdentifierOutgoingVoiceMessage) : VoiceMessageOutgoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceMessageWithoutSenderInfo) : VoiceMessageOutgoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceMessageWithPaginationTitle) : VoiceMessageOutgoingWithPaginationTitleBubbleCell.class, + }; +} + +- (NSDictionary*)locationCellsMapping +{ + return @{ + // Incoming + @(RoomTimelineCellIdentifierIncomingLocation) : LocationIncomingBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationIncomingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle) : LocationIncomingWithPaginationTitleBubbleCell.class, + // Outgoing + @(RoomTimelineCellIdentifierOutgoingLocation) : LocationOutgoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationOutgoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationOutgoingWithPaginationTitleBubbleCell.class + }; +} + +@end diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineStyle.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineStyle.swift new file mode 100644 index 000000000..137338e57 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineStyle.swift @@ -0,0 +1,76 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class BubbleRoomTimelineStyle: RoomTimelineStyle { + + // MARK: - Properties + + // MARK: Private + + private var theme: Theme + + // MARK: Public + + let identifier: RoomTimelineStyleIdentifier + + let cellLayoutUpdater: RoomCellLayoutUpdating? + + let cellProvider: RoomTimelineCellProvider + + let cellDecorator: RoomTimelineCellDecorator + + // MARK: - Setup + + init(theme: Theme) { + self.theme = theme + self.identifier = .bubble + self.cellLayoutUpdater = BubbleRoomCellLayoutUpdater(theme: theme) + self.cellProvider = BubbleRoomTimelineCellProvider() + self.cellDecorator = BubbleRoomTimelineCellDecorator() + } + + // MARK: - Public + + func canAddEvent(_ event: MXEvent, and roomState: MXRoomState, to cellData: MXKRoomBubbleCellData) -> Bool { + return false + } + + func applySelectedStyleIfNeeded(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) { + + // Check whether the selected event belongs to this bubble + let selectedComponentIndex = cellData.selectedComponentIndex + if selectedComponentIndex != NSNotFound { + + cell.selectComponent(UInt(selectedComponentIndex), + showEditButton: false, + showTimestamp: false) + + self.cellDecorator.addTimestampLabel(toCell: cell, cellData: cellData) + } else { + cell.blurred = true + } + } + + // MARK: Themable + + func update(theme: Theme) { + self.theme = theme + self.cellLayoutUpdater?.update(theme: theme) + } + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift new file mode 100644 index 000000000..65183f299 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift @@ -0,0 +1,59 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class FileWithoutThumbnailBaseBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable { + + weak var fileAttachementView: FileWithoutThumbnailCellContentView? + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let data = cellData as? RoomBubbleCellData else { + return + } + + self.fileAttachementView?.title = data.attributedTextMessage.string + + self.update(theme: ThemeService.shared().theme) + } + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.backgroundColor = .clear + + guard let contentView = bubbleCellContentView?.innerContentView else { + return + } + + let fileAttachementView = FileWithoutThumbnailCellContentView.instantiate() + + contentView.vc_addSubViewMatchingParent(fileAttachementView) + + self.fileAttachementView = fileAttachementView + } + + override func onContentViewTap(_ sender: UITapGestureRecognizer!) { + + if let bubbleData = self.bubbleData, bubbleData.isAttachment { + self.delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnAttachmentView, userInfo: nil) + } else { + super.onContentViewTap(sender) + } + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift new file mode 100644 index 000000000..14dd3c7fa --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift @@ -0,0 +1,82 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +final class FileWithoutThumbnailCellContentView: UIView, NibLoadable { + + // MARK: - Constants + + private enum Constants { + // TODO: Reuse constants, same as bubble bg + static let cornerRadius: CGFloat = 12.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var iconImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + // MARK: Public + + var badgeImage: UIImage? { + get { + return self.iconImageView.image + } + set { + self.iconImageView.image = newValue + } + } + + var title: String? { + get { + return self.titleLabel.text + } + set { + self.titleLabel.text = newValue + } + } + + // MARK: - Setup + + static func instantiate() -> FileWithoutThumbnailCellContentView { + return FileWithoutThumbnailCellContentView.loadFromNib() + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.layer.masksToBounds = true + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layer.cornerRadius = Constants.cornerRadius + } + + // MARK: - Public + + func update(theme: Theme) { + self.titleLabel.textColor = theme.textPrimaryColor + } +} + diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.xib new file mode 100644 index 000000000..21e33aef7 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift new file mode 100644 index 000000000..bdc06c1ec --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift @@ -0,0 +1,39 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = true + + // TODO: Use constants + let messageViewMarginRight: CGFloat = 80 + let messageLeftMargin: CGFloat = 48 + + bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight + bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin + } + + override func update(theme: Theme) { + super.update(theme: theme) + + self.fileAttachementView?.backgroundColor = theme.roomCellIncomingBubbleBackgroundColor + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..518f4fc95 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,26 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class FileWithoutThumbnailIncomingWithPaginationTitleBubbleCell: FileWithoutThumbnailIncomingBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..1db1510ca --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,26 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class FileWithoutThumbnailIncomingWithoutSenderInfoBubbleCell: FileWithoutThumbnailIncomingBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..0cf7acb6c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,26 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell: FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..67cb6ac3a --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,40 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbnailBaseBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + + // TODO: Use constants + // Same as outgoing message + let rightMargin: CGFloat = 34.0 + let leftMargin: CGFloat = 80.0 + + bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin + bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin + } + + override func update(theme: Theme) { + super.update(theme: theme) + + self.fileAttachementView?.backgroundColor = theme.roomCellOutgoingBubbleBackgroundColor + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift new file mode 100644 index 000000000..b0818f550 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift @@ -0,0 +1,31 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationIncomingBubbleCell: LocationBubbleCell { + + override func setupViews() { + super.setupViews() + + // TODO: Use constants + let messageViewMarginRight: CGFloat = 80 + let messageLeftMargin: CGFloat = 48 + + bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight + bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..77b1e17e1 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationIncomingWithPaginationTitleBubbleCell: LocationIncomingBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..880e7c286 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationIncomingWithoutSenderInfoBubbleCell: LocationIncomingBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..f1158043f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationOutgoingWithPaginationTitleBubbleCell: LocationOutgoingWithoutSenderInfoBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..8c46e21de --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,33 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class LocationOutgoingWithoutSenderInfoBubbleCell: LocationBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + + // TODO: Use constants + // Same as outgoing message + let rightMargin: CGFloat = 34.0 + let leftMargin: CGFloat = 80.0 + + bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin + bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift new file mode 100644 index 000000000..f86756e9e --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift @@ -0,0 +1,43 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell { + + override func setupViews() { + super.setupViews() + + // TODO: Use constants + let messageViewMarginRight: CGFloat = 80 + let messageLeftMargin: CGFloat = 48 + let playbackViewRightMargin: CGFloat = 40 + + bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight + bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin + + playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin + } + + override func update(theme: Theme) { + + guard let playbackController = playbackController else { + return + } + + playbackController.playbackView.customBackgroundViewColor = theme.roomCellIncomingBubbleBackgroundColor + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..9e8e6513f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class VoiceMessageIncomingWithPaginationTitleBubbleCell: VoiceMessageIncomingBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..f0d5385e6 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class VoiceMessageIncomingWithoutSenderInfoBubbleCell: VoiceMessageIncomingBubbleCell { + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..9f77e7843 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,26 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class VoiceMessageOutgoingWithPaginationTitleBubbleCell: VoiceMessageOutgoingWithoutSenderInfoBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showPaginationTitle = true + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..1b18d861c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,46 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell { + + override func setupViews() { + super.setupViews() + + bubbleCellContentView?.showSenderInfo = false + + // TODO: Use constants + // Same as outgoing message + let rightMargin: CGFloat = 34.0 + let leftMargin: CGFloat = 80.0 + let playbackViewRightMargin: CGFloat = 40 + + bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin + bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin + + playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin + } + + override func update(theme: Theme) { + + guard let playbackController = playbackController else { + return + } + + playbackController.playbackView.customBackgroundViewColor = theme.roomCellOutgoingBubbleBackgroundColor + } +} diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/ViewModel/UserSuggestionViewModelProtocol.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/MXKRoomBubbleTableViewCell+BubbleStyle.swift similarity index 57% rename from RiotSwiftUI/Modules/Room/UserSuggestion/ViewModel/UserSuggestionViewModelProtocol.swift rename to Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/MXKRoomBubbleTableViewCell+BubbleStyle.swift index 10207210c..a0fd3f527 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/ViewModel/UserSuggestionViewModelProtocol.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/MXKRoomBubbleTableViewCell+BubbleStyle.swift @@ -1,5 +1,3 @@ -// File created from SimpleUserProfileExample -// $ createScreen.sh Room/UserSuggestion UserSuggestion // // Copyright 2021 New Vector Ltd // @@ -18,12 +16,15 @@ import Foundation -@available(iOS 14, *) -protocol UserSuggestionViewModelProtocol { +extension MXKRoomBubbleTableViewCell { - static func makeUserSuggestionViewModel(userSuggestionService: UserSuggestionServiceProtocol) -> UserSuggestionViewModelProtocol - - var context: UserSuggestionViewModelType.Context { get } - - var completion: ((UserSuggestionViewModelResult) -> Void)? { get set } + // Enables to get existing bubble background view + // This used while there is no dedicated cell classes for bubble style + var messageBubbleBackgroundView: RoomMessageBubbleBackgroundView? { + + let foundView = self.contentView.subviews.first { view in + return view is RoomMessageBubbleBackgroundView + } + return foundView as? RoomMessageBubbleBackgroundView + } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift new file mode 100644 index 000000000..1408b48c7 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift @@ -0,0 +1,74 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class RoomMessageBubbleBackgroundView: UIView { + + // MARK: - Constant + + private enum Constants { + static let cornerRadius: CGFloat = 12.0 + } + + // MARK: - Properties + + private var heightConstraint: NSLayoutConstraint? + + // MARK: - Setup + + convenience init() { + self.init(frame: CGRect.zero) + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.commonInit() + } + + private func commonInit() { + self.translatesAutoresizingMaskIntoConstraints = false + self.layer.masksToBounds = true + self.layer.cornerRadius = Constants.cornerRadius + } + + // MARK: - Public + + @discardableResult + func updateHeight(_ height: CGFloat) -> Bool { + if let heightConstraint = self.heightConstraint { + + guard heightConstraint.constant != height else { + return false + } + + heightConstraint.constant = height + + return true + } else { + let heightConstraint = self.heightAnchor.constraint(equalToConstant: height) + heightConstraint.isActive = true + self.heightConstraint = heightConstraint + + return true + } + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellDecorator.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellDecorator.swift new file mode 100644 index 000000000..a4e4bb8ae --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellDecorator.swift @@ -0,0 +1,206 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@objcMembers +class PlainRoomTimelineCellDecorator: RoomTimelineCellDecorator { + + // MARK: - Properties + + // TODO: Conforms to Themable and don't use ThemeService + var theme: Theme { + return ThemeService.shared().theme + } + + // MARK: - RoomTimelineCellDecorator + + func addTimestampLabelIfNeeded(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) { + + guard cellData.containsLastMessage && cellData.isCollapsableAndCollapsed == false else { + return + } + + // Display timestamp of the last message + self.addTimestampLabel(toCell: cell, cellData: cellData) + } + + func addTimestampLabel(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) { + cell.addTimestampLabel(forComponent: UInt(cellData.mostRecentComponentIndex)) + } + + func addURLPreviewView(_ urlPreviewView: URLPreviewView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat) { + cell.addTemporarySubview(urlPreviewView) + + let cellContentView = cell.contentView + + urlPreviewView.translatesAutoresizingMaskIntoConstraints = false + urlPreviewView.availableWidth = cellData.maxTextViewWidth + cellContentView.addSubview(urlPreviewView) + + var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin + if cellData.containsBubbleComponentWithEncryptionBadge { + leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin + } + + let topMargin = contentViewPositionY + RoomBubbleCellLayout.urlPreviewViewTopMargin + RoomBubbleCellLayout.reactionsViewTopMargin + + // Set the preview view's origin + NSLayoutConstraint.activate([ + urlPreviewView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin), + urlPreviewView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: topMargin) + ]) + } + + func addReactionView(_ reactionsView: BubbleReactionsView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat, + upperDecorationView: UIView?) { + + cell.addTemporarySubview(reactionsView) + + if let reactionsDisplayable = cell as? BubbleCellReactionsDisplayable { + reactionsDisplayable.addReactionsView(reactionsView) + } else { + reactionsView.translatesAutoresizingMaskIntoConstraints = false + + let cellContentView = cell.contentView + + cellContentView.addSubview(reactionsView) + + var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin + + if cellData.containsBubbleComponentWithEncryptionBadge { + leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin + } + + let rightMargin = RoomBubbleCellLayout.reactionsViewRightMargin + let topMargin = RoomBubbleCellLayout.reactionsViewTopMargin + + // The top constraint may need to include the URL preview view + let topConstraint: NSLayoutConstraint + if let upperDecorationView = upperDecorationView { + topConstraint = reactionsView.topAnchor.constraint(equalTo: upperDecorationView.bottomAnchor, constant: topMargin) + } else { + topConstraint = reactionsView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: contentViewPositionY + topMargin) + } + + NSLayoutConstraint.activate([ + reactionsView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin), + reactionsView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin), + topConstraint + ]) + } + } + + func addReadReceiptsView(_ readReceiptsView: MXKReceiptSendersContainer, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat, + upperDecorationView: UIView?) { + + cell.addTemporarySubview(readReceiptsView) + + if let readReceiptsDisplayable = cell as? BubbleCellReadReceiptsDisplayable { + readReceiptsDisplayable.addReadReceiptsView(readReceiptsView) + } else { + + let cellContentView = cell.contentView + + cellContentView.addSubview(readReceiptsView) + + // Force receipts container size + let widthConstraint = readReceiptsView.widthAnchor.constraint(equalToConstant: RoomBubbleCellLayout.readReceiptsViewWidth) + let heightConstraint = readReceiptsView.heightAnchor.constraint(equalToConstant: RoomBubbleCellLayout.readReceiptsViewHeight) + + // Force receipts container position + let trailingConstraint = readReceiptsView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -RoomBubbleCellLayout.readReceiptsViewRightMargin) + + let topMargin = RoomBubbleCellLayout.readReceiptsViewTopMargin + + let topConstraint: NSLayoutConstraint + if let upperDecorationView = upperDecorationView { + topConstraint = readReceiptsView.topAnchor.constraint(equalTo: upperDecorationView.bottomAnchor, constant: topMargin) + } else { + topConstraint = readReceiptsView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: contentViewPositionY + topMargin) + } + + NSLayoutConstraint.activate([ + widthConstraint, + heightConstraint, + trailingConstraint, + topConstraint + ]) + } + } + + func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat, + upperDecorationView: UIView?) { + + cell.addTemporarySubview(threadSummaryView) + + if let threadSummaryDisplayable = cell as? BubbleCellThreadSummaryDisplayable { + threadSummaryDisplayable.addThreadSummaryView(threadSummaryView) + } else { + threadSummaryView.translatesAutoresizingMaskIntoConstraints = false + + let cellContentView = cell.contentView + + cellContentView.addSubview(threadSummaryView) + + var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin + + if cellData.containsBubbleComponentWithEncryptionBadge { + leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin + } + + let rightMargin = RoomBubbleCellLayout.reactionsViewRightMargin + let topMargin = RoomBubbleCellLayout.threadSummaryViewTopMargin + let height = ThreadSummaryView.contentViewHeight(forThread: threadSummaryView.thread, + fitting: cellData.maxTextViewWidth) + + // The top constraint may need to include the URL preview view + let topConstraint: NSLayoutConstraint + if let upperDecorationView = upperDecorationView { + topConstraint = threadSummaryView.topAnchor.constraint(equalTo: upperDecorationView.bottomAnchor, + constant: topMargin) + } else { + topConstraint = threadSummaryView.topAnchor.constraint(equalTo: cellContentView.topAnchor, + constant: contentViewPositionY + topMargin) + } + + NSLayoutConstraint.activate([ + threadSummaryView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, + constant: leftMargin), + threadSummaryView.trailingAnchor.constraint(lessThanOrEqualTo: cellContentView.trailingAnchor, + constant: -rightMargin), + threadSummaryView.heightAnchor.constraint(equalToConstant: height), + topConstraint + ]) + } + } + + func addSendStatusView(toCell cell: MXKRoomBubbleTableViewCell, withFailedEventIds failedEventIds: Set) { + cell.updateTickView(withFailedEventIds: failedEventIds) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h new file mode 100644 index 000000000..8b51f9dea --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h @@ -0,0 +1,45 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RoomTimelineCellProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PlainRoomTimelineCellProvider: NSObject + +#pragma mark - Registration + +- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView; + +- (void)registerLocationCellsForTableView:(UITableView*)tableView; + +#pragma mark - Mapping + +- (NSDictionary*)outgoingTextMessageCellsMapping; + +- (NSDictionary*)outgoingAttachmentCellsMapping; + +- (NSDictionary*)incomingAttachmentWithoutThumbnailCellsMapping; + +- (NSDictionary*)outgoingAttachmentWithoutThumbnailCellsMapping; + +- (NSDictionary*)voiceMessageCellsMapping; + +- (NSDictionary*)locationCellsMapping; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m new file mode 100644 index 000000000..b80dfcc74 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m @@ -0,0 +1,497 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "PlainRoomTimelineCellProvider.h" + +#import "MXKRoomBubbleTableViewCell+Riot.h" + +#import "RoomEmptyBubbleCell.h" + +#import "RoomIncomingTextMsgBubbleCell.h" +#import "RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h" +#import "RoomIncomingTextMsgWithPaginationTitleBubbleCell.h" +#import "RoomIncomingTextMsgWithoutSenderNameBubbleCell.h" +#import "RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" +#import "RoomIncomingAttachmentBubbleCell.h" +#import "RoomIncomingAttachmentWithoutSenderInfoBubbleCell.h" +#import "RoomIncomingAttachmentWithPaginationTitleBubbleCell.h" + +#import "RoomIncomingEncryptedTextMsgBubbleCell.h" +#import "RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h" +#import "RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h" +#import "RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h" +#import "RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" +#import "RoomIncomingEncryptedAttachmentBubbleCell.h" +#import "RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.h" +#import "RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.h" + +#import "RoomOutgoingTextMsgBubbleCell.h" +#import "RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h" +#import "RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingAttachmentBubbleCell.h" +#import "RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingAttachmentWithPaginationTitleBubbleCell.h" +#import "RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.h" + +#import "RoomOutgoingEncryptedTextMsgBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h" +#import "RoomOutgoingEncryptedAttachmentBubbleCell.h" +#import "RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.h" +#import "RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.h" + +#import "RoomMembershipBubbleCell.h" +#import "RoomMembershipWithPaginationTitleBubbleCell.h" +#import "RoomMembershipCollapsedBubbleCell.h" +#import "RoomMembershipCollapsedWithPaginationTitleBubbleCell.h" +#import "RoomMembershipExpandedBubbleCell.h" +#import "RoomMembershipExpandedWithPaginationTitleBubbleCell.h" +#import "RoomCreationWithPaginationCollapsedBubbleCell.h" +#import "RoomCreationCollapsedBubbleCell.h" + +#import "RoomSelectedStickerBubbleCell.h" +#import "RoomPredecessorBubbleCell.h" + +#import "GeneratedInterface-Swift.h" + +@interface PlainRoomTimelineCellProvider() + +@property (nonatomic, strong) NSDictionary* cellClasses; + +@end + +@implementation PlainRoomTimelineCellProvider + +#pragma mark - Public + +- (void)registerCellsForTableView:(UITableView*)tableView +{ + // Text message + + [self registerIncomingTextMessageCellsForTableView:tableView]; + + [self registerOutgoingTextMessageCellsForTableView:tableView]; + + // Attachment cells + + [self registerIncomingAttachmentCellsForTableView:tableView]; + + [self registerOutgoingAttachmentCellsForTableView:tableView]; + + // Other cells + + [self registerMembershipCellsForTableView:tableView]; + + [self registerKeyVerificationCellsForTableView:tableView]; + + [self registerRoomCreationCellsForTableView:tableView]; + + [self registerCallCellsForTableView:tableView]; + + [self registerVoiceMessageCellsForTableView:tableView]; + + [self registerPollCellsForTableView:tableView]; + + [self registerLocationCellsForTableView:tableView]; + + [tableView registerClass:RoomEmptyBubbleCell.class forCellReuseIdentifier:RoomEmptyBubbleCell.defaultReuseIdentifier]; + + [tableView registerClass:RoomSelectedStickerBubbleCell.class forCellReuseIdentifier:RoomSelectedStickerBubbleCell.defaultReuseIdentifier]; + + [tableView registerClass:RoomPredecessorBubbleCell.class forCellReuseIdentifier:RoomPredecessorBubbleCell.defaultReuseIdentifier]; + + [tableView registerClass:RoomCreationIntroCell.class forCellReuseIdentifier:RoomCreationIntroCell.defaultReuseIdentifier]; + + [tableView registerNib:RoomTypingBubbleCell.nib forCellReuseIdentifier:RoomTypingBubbleCell.defaultReuseIdentifier]; +} + +- (Class)cellViewClassForCellIdentifier:(RoomTimelineCellIdentifier)identifier +{ + if (self.cellClasses == nil) + { + self.cellClasses = [self buildCellClasses]; + } + + Class cellViewClass = self.cellClasses[@(identifier)]; + + + return cellViewClass; +} + +#pragma mark - Private + +#pragma mark Cell registration + +- (void)registerIncomingTextMessageCellsForTableView:(UITableView*)tableView +{ + // Clear + + [tableView registerClass:RoomIncomingTextMsgBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + + // Encrypted + + [tableView registerClass:RoomIncomingEncryptedTextMsgBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerOutgoingTextMessageCellsForTableView:(UITableView*)tableView +{ + // Clear + + [tableView registerClass:RoomOutgoingTextMsgBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + + // Encrypted + + [tableView registerClass:RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingEncryptedTextMsgBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerIncomingAttachmentCellsForTableView:(UITableView*)tableView +{ + // Clear + + [tableView registerClass:RoomIncomingAttachmentBubbleCell.class forCellReuseIdentifier:RoomIncomingAttachmentBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + + // Encrypted + + [tableView registerClass:RoomIncomingEncryptedAttachmentBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedAttachmentBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerOutgoingAttachmentCellsForTableView:(UITableView*)tableView +{ + // Clear + + [tableView registerClass:RoomOutgoingAttachmentBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:RoomOutgoingAttachmentWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier]; + + // Encrypted + + [tableView registerClass:RoomOutgoingEncryptedAttachmentBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedAttachmentBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerMembershipCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:RoomMembershipBubbleCell.class forCellReuseIdentifier:RoomMembershipBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomMembershipWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomMembershipCollapsedBubbleCell.class forCellReuseIdentifier:RoomMembershipCollapsedBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomMembershipCollapsedWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipCollapsedWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomMembershipExpandedBubbleCell.class forCellReuseIdentifier:RoomMembershipExpandedBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomMembershipExpandedWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipExpandedWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerKeyVerificationCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:KeyVerificationIncomingRequestApprovalBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:KeyVerificationRequestStatusBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:KeyVerificationConclusionBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:KeyVerificationConclusionWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerRoomCreationCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:RoomCreationCollapsedBubbleCell.class forCellReuseIdentifier:RoomCreationCollapsedBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomCreationWithPaginationCollapsedBubbleCell.class forCellReuseIdentifier:RoomCreationWithPaginationCollapsedBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerCallCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:RoomDirectCallStatusBubbleCell.class forCellReuseIdentifier:RoomDirectCallStatusBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:RoomGroupCallStatusBubbleCell.class forCellReuseIdentifier:RoomGroupCallStatusBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:VoiceMessageBubbleCell.class forCellReuseIdentifier:VoiceMessageBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceMessageWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceMessageWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceMessageWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceMessageWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerPollCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:PollBubbleCell.class forCellReuseIdentifier:PollBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:PollWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:PollWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:PollWithPaginationTitleBubbleCell.class forCellReuseIdentifier:PollWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +- (void)registerLocationCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:LocationBubbleCell.class forCellReuseIdentifier:LocationBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:LocationWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:LocationWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:LocationWithPaginationTitleBubbleCell.class forCellReuseIdentifier:LocationWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + +#pragma mark Cell class association + +- (NSDictionary*)buildCellClasses +{ + NSMutableDictionary* cellClasses = [NSMutableDictionary dictionary]; + + // Text message + + NSDictionary *incomingTextMessageCellsMapping = [self incomingTextMessageCellsMapping]; + [cellClasses addEntriesFromDictionary:incomingTextMessageCellsMapping]; + + NSDictionary *outgoingTextMessageCellsMapping = [self outgoingTextMessageCellsMapping]; + [cellClasses addEntriesFromDictionary:outgoingTextMessageCellsMapping]; + + // Attachment + + NSDictionary *incomingAttachmentCellsMapping = [self incomingAttachmentCellsMapping]; + [cellClasses addEntriesFromDictionary:incomingAttachmentCellsMapping]; + + NSDictionary *outgoingAttachmentCellsMapping = [self outgoingAttachmentCellsMapping]; + [cellClasses addEntriesFromDictionary:outgoingAttachmentCellsMapping]; + + NSDictionary *outgoingAttachmentWithoutThumbnailCellsMapping = [self outgoingAttachmentWithoutThumbnailCellsMapping]; + [cellClasses addEntriesFromDictionary:outgoingAttachmentWithoutThumbnailCellsMapping]; + + NSDictionary *incomingAttachmentWithoutThumbnailCellsMapping = [self incomingAttachmentWithoutThumbnailCellsMapping]; + [cellClasses addEntriesFromDictionary:incomingAttachmentWithoutThumbnailCellsMapping]; + + // Other cells + + NSDictionary *roomMembershipCellsMapping = [self membershipCellsMapping]; + [cellClasses addEntriesFromDictionary:roomMembershipCellsMapping]; + + NSDictionary *keyVerificationCellsMapping = [self keyVerificationCellsMapping]; + [cellClasses addEntriesFromDictionary:keyVerificationCellsMapping]; + + NSDictionary *roomCreationCellsMapping = [self roomCreationCellsMapping]; + [cellClasses addEntriesFromDictionary:roomCreationCellsMapping]; + + NSDictionary *callCellsMapping = [self callCellsMapping]; + [cellClasses addEntriesFromDictionary:callCellsMapping]; + + NSDictionary *voiceMessageCellsMapping = [self voiceMessageCellsMapping]; + [cellClasses addEntriesFromDictionary:voiceMessageCellsMapping]; + + NSDictionary *pollCellsMapping = [self pollCellsMapping]; + [cellClasses addEntriesFromDictionary:pollCellsMapping]; + + NSDictionary *locationCellsMapping = [self locationCellsMapping]; + [cellClasses addEntriesFromDictionary:locationCellsMapping]; + + NSDictionary *othersCells = @{ + @(RoomTimelineCellIdentifierEmpty) : RoomEmptyBubbleCell.class, + @(RoomTimelineCellIdentifierSelectedSticker) : RoomSelectedStickerBubbleCell.class, + @(RoomTimelineCellIdentifierRoomPredecessor) : RoomPredecessorBubbleCell.class, + @(RoomTimelineCellIdentifierRoomCreationIntro) : RoomCreationIntroCell.class, + @(RoomTimelineCellIdentifierTyping) : RoomTypingBubbleCell.class, + }; + [cellClasses addEntriesFromDictionary:othersCells]; + + return [cellClasses copy]; +} + +- (NSDictionary*)incomingTextMessageCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierIncomingTextMessage) : RoomIncomingTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderInfo) : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitle) : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderName) : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitleWithoutSenderName) : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierIncomingTextMessageEncrypted) : RoomIncomingEncryptedTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderInfo) : RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitle) : RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderName) : RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + }; +} + +- (NSDictionary*)outgoingTextMessageCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierOutgoingTextMessage) : RoomOutgoingTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName) : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierOutgoingTextMessageEncrypted) : RoomOutgoingEncryptedTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class, + }; +} + +- (NSDictionary*)incomingAttachmentCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierIncomingAttachment) : RoomIncomingAttachmentBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutSenderInfo) : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithPaginationTitle) : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierIncomingAttachmentEncrypted) : RoomIncomingEncryptedAttachmentBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentEncryptedWithoutSenderInfo) : RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentEncryptedWithPaginationTitle) : RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)outgoingAttachmentCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierOutgoingAttachment) : RoomOutgoingAttachmentBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutSenderInfo) : RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithPaginationTitle) : RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierOutgoingAttachmentEncrypted) : RoomOutgoingEncryptedAttachmentBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentEncryptedWithPaginationTitle) : RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)incomingAttachmentWithoutThumbnailCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnail) : RoomIncomingTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithoutSenderInfo) : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailWithPaginationTitle) : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncrypted) : RoomIncomingEncryptedTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo) : RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingAttachmentWithoutThumbnailEncryptedWithPaginationTitle) : RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)outgoingAttachmentWithoutThumbnailCellsMapping +{ + return @{ + // Clear + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnail) : RoomOutgoingTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class, + // Encrypted + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncrypted) : RoomOutgoingEncryptedTextMsgBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingAttachmentWithoutThumbnailEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)membershipCellsMapping +{ + return @{ + @(RoomTimelineCellIdentifierMembership) : RoomMembershipBubbleCell.class, + @(RoomTimelineCellIdentifierMembershipWithPaginationTitle) : RoomMembershipWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierMembershipCollapsed) : RoomMembershipCollapsedBubbleCell.class, + @(RoomTimelineCellIdentifierMembershipCollapsedWithPaginationTitle) : RoomMembershipCollapsedWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierMembershipExpanded) : RoomMembershipExpandedBubbleCell.class, + @(RoomTimelineCellIdentifierMembershipExpandedWithPaginationTitle) : RoomMembershipExpandedWithPaginationTitleBubbleCell.class, + }; +} + +- (NSDictionary*)keyVerificationCellsMapping +{ + return @{ + @(RoomTimelineCellIdentifierKeyVerificationIncomingRequestApproval) : KeyVerificationIncomingRequestApprovalBubbleCell.class, + @(RoomTimelineCellIdentifierKeyVerificationIncomingRequestApprovalWithPaginationTitle) : KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierKeyVerificationRequestStatus) : KeyVerificationRequestStatusBubbleCell.class, + @(RoomTimelineCellIdentifierKeyVerificationRequestStatusWithPaginationTitle) : KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class, + @(RoomTimelineCellIdentifierKeyVerificationConclusion) : KeyVerificationConclusionBubbleCell.class, + @(RoomTimelineCellIdentifierKeyVerificationConclusionWithPaginationTitle) : KeyVerificationConclusionWithPaginationTitleBubbleCell.class, + }; +} + +- (NSDictionary*)roomCreationCellsMapping +{ + return @{ + @(RoomTimelineCellIdentifierRoomCreationCollapsed) : RoomCreationCollapsedBubbleCell.class, + @(RoomTimelineCellIdentifierRoomCreationCollapsedWithPaginationTitle) : RoomCreationWithPaginationCollapsedBubbleCell.class, + }; +} + +- (NSDictionary*)callCellsMapping +{ + return @{ + @(RoomTimelineCellIdentifierDirectCallStatus) : RoomDirectCallStatusBubbleCell.class, + @(RoomTimelineCellIdentifierGroupCallStatus) : RoomGroupCallStatusBubbleCell.class, + }; +} + +- (NSDictionary*)voiceMessageCellsMapping +{ + return @{ + // Incoming + @(RoomTimelineCellIdentifierIncomingVoiceMessage) : VoiceMessageBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceMessageWithoutSenderInfo) : VoiceMessageWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceMessageWithPaginationTitle) : VoiceMessageWithPaginationTitleBubbleCell.class, + // Outoing + @(RoomTimelineCellIdentifierOutgoingVoiceMessage) : VoiceMessageBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceMessageWithoutSenderInfo) : VoiceMessageWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceMessageWithPaginationTitle) : VoiceMessageWithPaginationTitleBubbleCell.class + }; +} + +- (NSDictionary*)pollCellsMapping +{ + return @{ + @(RoomTimelineCellIdentifierPoll) : PollBubbleCell.class, + @(RoomTimelineCellIdentifierPollWithoutSenderInfo) : PollWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierPollWithPaginationTitle) : PollWithPaginationTitleBubbleCell.class, + }; +} + +- (NSDictionary*)locationCellsMapping +{ + return @{ + // Incoming + @(RoomTimelineCellIdentifierIncomingLocation) : LocationBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingLocationWithoutSenderInfo) : LocationWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingLocationWithPaginationTitle) : LocationWithPaginationTitleBubbleCell.class, + // Outgoing + @(RoomTimelineCellIdentifierOutgoingLocation) : LocationBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo) : LocationWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle) : LocationWithPaginationTitleBubbleCell.class + }; +} + + +@end diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift new file mode 100644 index 000000000..f3fba9c4e --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift @@ -0,0 +1,74 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class PlainRoomTimelineStyle: RoomTimelineStyle { + + // MARK: - Properties + + // MARK: Private + + private var theme: Theme + + // MARK: Public + + let identifier: RoomTimelineStyleIdentifier + + let cellLayoutUpdater: RoomCellLayoutUpdating? + + let cellProvider: RoomTimelineCellProvider + + let cellDecorator: RoomTimelineCellDecorator + + // MARK: - Setup + + init(theme: Theme) { + self.theme = theme + self.identifier = .plain + self.cellLayoutUpdater = nil + self.cellProvider = PlainRoomTimelineCellProvider() + self.cellDecorator = PlainRoomTimelineCellDecorator() + } + + // MARK: - Methods + + func canAddEvent(_ event: MXEvent, and roomState: MXRoomState, to cellData: MXKRoomBubbleCellData) -> Bool { + return true + } + + func applySelectedStyleIfNeeded(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) { + + // Check whether the selected event belongs to this bubble + let selectedComponentIndex = cellData.selectedComponentIndex + if selectedComponentIndex != NSNotFound { + + let showTimestamp = cellData.showTimestampForSelectedComponent + + cell.selectComponent(UInt(selectedComponentIndex), + showEditButton: false, + showTimestamp: showTimestamp) + } else { + cell.blurred = true + } + } + + // MARK: Themable + + func update(theme: Theme) { + self.theme = theme + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift new file mode 100644 index 000000000..78f5330f1 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift @@ -0,0 +1,32 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// Enables to setup or update a room timeline cell view +@objc +protocol RoomCellLayoutUpdating: Themable { + + func updateLayoutIfNeeded(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) + + func setupLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell) + + func setupLayout(forOutgoingTextMessageCell cell: MXKRoomBubbleTableViewCell) + + func setupLayout(forOutgoingFileAttachmentCell cell: MXKRoomBubbleTableViewCell) + + func updateLayout(forSelectedStickerCell cell: RoomSelectedStickerBubbleCell) +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineCellDecorator.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineCellDecorator.swift new file mode 100644 index 000000000..ec60bcbf4 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineCellDecorator.swift @@ -0,0 +1,53 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +/// RoomTimelineCellDecorator enables to add decoration on a cell (reactions, read receipts, timestamp, URL preview). +@objc +protocol RoomTimelineCellDecorator { + + func addTimestampLabelIfNeeded(toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData) + + func addTimestampLabel(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) + + func addURLPreviewView(_ urlPreviewView: URLPreviewView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat) + + func addReactionView(_ reactionsView: BubbleReactionsView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat, + upperDecorationView: UIView?) + + func addReadReceiptsView(_ readReceiptsView: MXKReceiptSendersContainer, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat, + upperDecorationView: UIView?) + + func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView, + toCell cell: MXKRoomBubbleTableViewCell, + cellData: RoomBubbleCellData, + contentViewPositionY: CGFloat, + upperDecorationView: UIView?) + + func addSendStatusView(toCell cell: MXKRoomBubbleTableViewCell, + withFailedEventIds failedEventIds: Set) +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineCellProvider.h b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineCellProvider.h new file mode 100644 index 000000000..40037c3a2 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineCellProvider.h @@ -0,0 +1,36 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "RoomTimelineCellIdentifier.h" +#import "MXKCellRendering.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Enables to register and provide room timeline cells +@protocol RoomTimelineCellProvider + +/// Register timeline cells for the given table view +- (void)registerCellsForTableView:(UITableView*)tableView; + +/// Get timeline cell class from cell identifier +- (Class)cellViewClassForCellIdentifier:(RoomTimelineCellIdentifier)identifier; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyle.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyle.swift new file mode 100644 index 000000000..b430171b5 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyle.swift @@ -0,0 +1,45 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK + +/// RoomTimelineStyle describes a room timeline style used to customize timeline appearance +@objc +protocol RoomTimelineStyle: Themable { + + // MARK: - Properties + + /// Style identifier + var identifier: RoomTimelineStyleIdentifier { get } + + /// Update layout if needed for cells provided by the cell provider + var cellLayoutUpdater: RoomCellLayoutUpdating? { get } + + /// Register and provide timeline cells + var cellProvider: RoomTimelineCellProvider { get } + + /// Handle cell decorations (reactions, read receipts, URL preview, …) + var cellDecorator: RoomTimelineCellDecorator { get } + + // MARK: - Methods + + /// Indicate to merge or not event in timeline + func canAddEvent(_ event: MXEvent, and roomState: MXRoomState, to cellData: MXKRoomBubbleCellData) -> Bool + + /// Apply selected or blurred style on cell + func applySelectedStyleIfNeeded(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyleIdentifier.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyleIdentifier.swift new file mode 100644 index 000000000..a285fe8e0 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomTimelineStyleIdentifier.swift @@ -0,0 +1,24 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// Represents the room timeline style identifiers available +@objc +enum RoomTimelineStyleIdentifier: Int { + case plain + case bubble +} diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.m similarity index 69% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.m index 253e7141f..f4cfc616c 100644 --- a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.m @@ -16,6 +16,17 @@ #import "MXKRoomIncomingTextMsgBubbleCell.h" +#import "GeneratedInterface-Swift.h" + @implementation MXKRoomIncomingTextMsgBubbleCell +- (void)setupViews +{ + [super setupViews]; + + RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared]; + + [timelineConfiguration.currentStyle.cellLayoutUpdater setupLayoutForIncomingTextMessageCell:self]; +} + @end diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Common/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.m similarity index 69% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.m index 232813182..bf6c70ae8 100644 --- a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.m @@ -16,6 +16,18 @@ #import "MXKRoomOutgoingTextMsgBubbleCell.h" +#import "GeneratedInterface-Swift.h" + @implementation MXKRoomOutgoingTextMsgBubbleCell -@end \ No newline at end of file +- (void)setupViews +{ + [super setupViews]; + + RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared]; + + [timelineConfiguration.currentStyle.cellLayoutUpdater setupLayoutForOutgoingTextMessageCell:self]; +} + + +@end diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgBubbleCell.xib diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Common/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.h diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.xib similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.xib rename to Riot/Modules/Room/Views/BubbleCells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/VoiceMessage/VoiceMessageBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/VoiceMessage/VoiceMessageBubbleCell.swift index a389e3eb2..55c7f2487 100644 --- a/Riot/Modules/Room/Views/BubbleCells/VoiceMessage/VoiceMessageBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/VoiceMessage/VoiceMessageBubbleCell.swift @@ -18,7 +18,7 @@ import Foundation class VoiceMessageBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplayable { - private var playbackController: VoiceMessagePlaybackController! + private(set) var playbackController: VoiceMessagePlaybackController! override func render(_ cellData: MXKCellData!) { super.render(cellData) @@ -27,13 +27,15 @@ class VoiceMessageBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplaya return } - guard data.attachment.type == MXKAttachmentTypeVoiceMessage || data.attachment.type == MXKAttachmentTypeAudio else { + guard data.attachment.type == .voiceMessage || data.attachment.type == .audio else { fatalError("Invalid attachment type passed to a voice message cell.") } if playbackController.attachment != data.attachment { playbackController.attachment = data.attachment } + + self.update(theme: ThemeService.shared().theme) } override func setupViews() { @@ -52,4 +54,15 @@ class VoiceMessageBubbleCell: SizableBaseBubbleCell, BubbleCellReactionsDisplaya contentView.vc_addSubViewMatchingParent(playbackController.playbackView) } + + override func update(theme: Theme) { + + super.update(theme: theme) + + guard let playbackController = playbackController else { + return + } + + playbackController.playbackView.update(theme: theme) + } } diff --git a/Riot/Modules/Room/Views/Threads/From/FromAThreadView.swift b/Riot/Modules/Room/Views/Threads/From/FromAThreadView.swift new file mode 100644 index 000000000..2ffd27f21 --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/From/FromAThreadView.swift @@ -0,0 +1,54 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +@objcMembers +class FromAThreadView: UIView { + + private enum Constants { + static let viewHeight: CGFloat = 18 + } + + @IBOutlet private weak var iconView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + static func contentViewHeight(forEvent event: MXEvent, + fitting maxWidth: CGFloat) -> CGFloat { + return Constants.viewHeight + } + + static func instantiate() -> FromAThreadView { + let view = FromAThreadView.loadFromNib() + view.update(theme: ThemeService.shared().theme) + view.titleLabel.text = VectorL10n.messageFromAThread + return view + } + +} + +extension FromAThreadView: NibLoadable {} + +extension FromAThreadView: Themable { + + func update(theme: Theme) { + backgroundColor = .clear + iconView.tintColor = theme.colors.secondaryContent + titleLabel.textColor = theme.colors.secondaryContent + } + +} diff --git a/Riot/Modules/Room/Views/Threads/From/FromAThreadView.xib b/Riot/Modules/Room/Views/Threads/From/FromAThreadView.xib new file mode 100644 index 000000000..d1f306129 --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/From/FromAThreadView.xib @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryModel.swift b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryModel.swift new file mode 100644 index 000000000..59419d6ac --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryModel.swift @@ -0,0 +1,23 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct ThreadSummaryModel { + let numberOfReplies: Int + let lastMessageSenderAvatar: AvatarViewDataProtocol? + let lastMessageText: NSAttributedString? +} diff --git a/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.swift b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.swift new file mode 100644 index 000000000..c1da2d9dd --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.swift @@ -0,0 +1,149 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +@objc +protocol ThreadSummaryViewDelegate: AnyObject { + func threadSummaryViewTapped(_ summaryView: ThreadSummaryView) +} + +/// A view to display a summary for an `MXThread` generated by the `MXThreadingService`. +@objcMembers +class ThreadSummaryView: UIView { + + private enum Constants { + static let viewDefaultWidth: CGFloat = 320 + static let cornerRadius: CGFloat = 8 + static let lastMessageFont: UIFont = .systemFont(ofSize: 13) + } + + @IBOutlet private weak var iconView: UIImageView! + @IBOutlet private weak var numberOfRepliesLabel: UILabel! + @IBOutlet private weak var lastMessageAvatarView: UserAvatarView! + @IBOutlet private weak var lastMessageContentLabel: UILabel! + + private var theme: Theme = ThemeService.shared().theme + private(set) var thread: MXThread? + + private lazy var tapGestureRecognizer: UITapGestureRecognizer = { + return UITapGestureRecognizer(target: self, action: #selector(tapped(_:))) + }() + + weak var delegate: ThreadSummaryViewDelegate? + + // MARK: - Setup + + init(withThread thread: MXThread) { + self.thread = thread + super.init(frame: CGRect(origin: .zero, + size: CGSize(width: Constants.viewDefaultWidth, + height: RoomBubbleCellLayout.threadSummaryViewHeight))) + loadNibContent() + update(theme: ThemeService.shared().theme) + configure() + translatesAutoresizingMaskIntoConstraints = false + } + + static func contentViewHeight(forThread thread: MXThread?, fitting maxWidth: CGFloat) -> CGFloat { + return RoomBubbleCellLayout.threadSummaryViewHeight + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + loadNibContent() + } + + @nonobjc func configure(withModel model: ThreadSummaryModel) { + numberOfRepliesLabel.text = String(model.numberOfReplies) + if let avatar = model.lastMessageSenderAvatar { + lastMessageAvatarView.fill(with: avatar) + } else { + lastMessageAvatarView.avatarImageView.image = nil + } + if let lastMessage = model.lastMessageText { + let mutable = NSMutableAttributedString(attributedString: lastMessage) + mutable.setAttributes([ + .font: Constants.lastMessageFont + ], range: NSRange(location: 0, length: mutable.length)) + lastMessageContentLabel.attributedText = mutable + } else { + lastMessageContentLabel.attributedText = nil + } + } + + private func configure() { + clipsToBounds = true + layer.cornerRadius = Constants.cornerRadius + addGestureRecognizer(tapGestureRecognizer) + + guard let thread = thread, + let lastMessage = thread.lastMessage, + let session = thread.session, + let eventFormatter = session.roomSummaryUpdateDelegate as? MXKEventFormatter, + let room = session.room(withRoomId: lastMessage.roomId) else { + lastMessageAvatarView.avatarImageView.image = nil + lastMessageContentLabel.text = nil + return + } + let lastMessageSender = session.user(withUserId: lastMessage.sender) + + let fallbackImage = AvatarFallbackImage.matrixItem(lastMessage.sender, + lastMessageSender?.displayname) + let avatarViewData = AvatarViewData(matrixItemId: lastMessage.sender, + displayName: lastMessageSender?.displayname, + avatarUrl: lastMessageSender?.avatarUrl, + mediaManager: session.mediaManager, + fallbackImage: fallbackImage) + + room.state { [weak self] roomState in + guard let self = self else { return } + let formatterError = UnsafeMutablePointer.allocate(capacity: 1) + let lastMessageText = eventFormatter.attributedString(from: lastMessage, + with: roomState, + error: formatterError) + + let model = ThreadSummaryModel(numberOfReplies: thread.numberOfReplies, + lastMessageSenderAvatar: avatarViewData, + lastMessageText: lastMessageText) + self.configure(withModel: model) + } + } + + // MARK: - Action + + @objc + private func tapped(_ sender: UITapGestureRecognizer) { + guard thread != nil else { return } + delegate?.threadSummaryViewTapped(self) + } +} + +extension ThreadSummaryView: NibOwnerLoadable {} + +extension ThreadSummaryView: Themable { + + func update(theme: Theme) { + self.theme = theme + + backgroundColor = theme.colors.system + iconView.tintColor = theme.colors.secondaryContent + numberOfRepliesLabel.textColor = theme.colors.secondaryContent + lastMessageContentLabel.textColor = theme.colors.secondaryContent + } + +} diff --git a/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.xib b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.xib new file mode 100644 index 000000000..885f29293 --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.xib @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleModel.swift b/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleModel.swift new file mode 100644 index 000000000..059d99f5e --- /dev/null +++ b/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleModel.swift @@ -0,0 +1,27 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct ThreadRoomTitleModel { + let roomAvatar: AvatarViewDataProtocol? + let roomEncryptionBadge: UIImage? + let roomDisplayName: String? + + static let empty = ThreadRoomTitleModel(roomAvatar: nil, + roomEncryptionBadge: nil, + roomDisplayName: nil) +} diff --git a/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleView.swift b/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleView.swift new file mode 100644 index 000000000..59f4571f4 --- /dev/null +++ b/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleView.swift @@ -0,0 +1,133 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +enum ThreadRoomTitleViewMode { + case allThreads + case specificThread(threadId: String) +} + +@objcMembers +class ThreadRoomTitleView: RoomTitleView { + + var mode: ThreadRoomTitleViewMode = .allThreads { + didSet { + update() + } + } + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var roomAvatarView: RoomAvatarView! + @IBOutlet private weak var roomEncryptionBadgeView: UIImageView! + @IBOutlet private weak var roomNameLabel: UILabel! + + // MARK: - Methods + + func configure(withModel model: ThreadRoomTitleModel) { + if let avatarViewData = model.roomAvatar { + roomAvatarView.fill(with: avatarViewData) + } else { + roomAvatarView.avatarImageView.image = nil + } + roomEncryptionBadgeView.image = model.roomEncryptionBadge + roomEncryptionBadgeView.isHidden = model.roomEncryptionBadge == nil + roomNameLabel.text = model.roomDisplayName + } + + // MARK: - Overrides + + override var mxRoom: MXRoom! { + didSet { + update() + } + } + + override class func nib() -> UINib! { + return self.nib + } + + override func refreshDisplay() { + guard let room = mxRoom else { + // room not initialized yet + return + } + + let avatarViewData = AvatarViewData(matrixItemId: room.matrixItemId, + displayName: room.displayName, + avatarUrl: room.mxContentUri, + mediaManager: room.mxSession.mediaManager, + fallbackImage: AvatarFallbackImage.matrixItem(room.matrixItemId, + room.displayName)) + + let encrpytionBadge: UIImage? + if let summary = room.summary, summary.isEncrypted, room.mxSession.crypto != nil { + encrpytionBadge = EncryptionTrustLevelBadgeImageHelper.roomBadgeImage(for: summary.roomEncryptionTrustLevel()) + } else { + encrpytionBadge = nil + } + + let model = ThreadRoomTitleModel(roomAvatar: avatarViewData, + roomEncryptionBadge: encrpytionBadge, + roomDisplayName: room.displayName) + configure(withModel: model) + } + + override func awakeFromNib() { + super.awakeFromNib() + + update(theme: ThemeService.shared().theme) + registerThemeServiceDidChangeThemeNotification() + } + + // MARK: - Private + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, + selector: #selector(themeDidChange), + name: .themeServiceDidChangeTheme, + object: nil) + } + + private func update() { + switch mode { + case .allThreads: + titleLabel.text = VectorL10n.threadsTitle + case .specificThread: + titleLabel.text = VectorL10n.roomThreadTitle + } + } + + // MARK: - Actions + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + +} + +extension ThreadRoomTitleView: NibLoadable {} + +extension ThreadRoomTitleView: Themable { + + func update(theme: Theme) { + roomAvatarView.backgroundColor = .clear + titleLabel.textColor = theme.colors.primaryContent + roomNameLabel.textColor = theme.colors.secondaryContent + } + +} diff --git a/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleView.xib b/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleView.xib new file mode 100644 index 000000000..08667bfdc --- /dev/null +++ b/Riot/Modules/Room/Views/Title/Thread/ThreadRoomTitleView.xib @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAttachmentCacheManager.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAttachmentCacheManager.swift index 14d466af6..6c6200298 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAttachmentCacheManager.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAttachmentCacheManager.swift @@ -85,7 +85,7 @@ class VoiceMessageAttachmentCacheManager { } func loadAttachment(_ attachment: MXKAttachment, numberOfSamples: Int, completion: @escaping (Result) -> Void) { - guard attachment.type == MXKAttachmentTypeVoiceMessage || attachment.type == MXKAttachmentTypeAudio else { + guard attachment.type == .voiceMessage || attachment.type == .audio else { completion(Result.failure(VoiceMessageAttachmentCacheManagerError.invalidAttachmentType)) MXLog.error("[VoiceMessageAttachmentCacheManager] Invalid attachment type, ignoring request.") return diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioConverter.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioConverter.swift index 0c521b1b8..9b3f85cec 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioConverter.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioConverter.swift @@ -35,7 +35,7 @@ struct VoiceMessageAudioConverter { static func mediaDurationAt(_ sourceURL: URL, completion: @escaping (Result) -> Void) { FFprobeKit.getMediaInformationAsync(sourceURL.path) { session in - guard let session = session as? MediaInformationSession else { + guard let session = session else { completion(.failure(.generic("Invalid session"))) return } @@ -46,14 +46,14 @@ struct VoiceMessageAudioConverter { } DispatchQueue.main.async { - if returnCode.isSuccess() { + if returnCode.isValueSuccess() { let mediaInfo = session.getMediaInformation() if let duration = try? TimeInterval(value: mediaInfo?.getDuration() ?? "0") { completion(.success(duration)) } else { completion(.failure(.generic("Failed to get media duration"))) } - } else if returnCode.isCancel() { + } else if returnCode.isValueCancel() { completion(.failure(.cancelled)) } else { completion(.failure(.generic(String(returnCode.getValue())))) @@ -82,9 +82,9 @@ struct VoiceMessageAudioConverter { } DispatchQueue.main.async { - if returnCode.isSuccess() { + if returnCode.isValueSuccess() { completion(.success(())) - } else if returnCode.isCancel() { + } else if returnCode.isValueCancel() { completion(.failure(.cancelled)) } else { completion(.failure(.generic(String(returnCode.getValue())))) diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift index 8c13de2d3..c3abeb01f 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift @@ -49,6 +49,7 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable { @IBOutlet private var playButton: UIButton! @IBOutlet private var elapsedTimeLabel: UILabel! @IBOutlet private var waveformContainerView: UIView! + @IBOutlet private (set)var stackViewTrailingContraint: NSLayoutConstraint! private var longPressGestureRecognizer: UILongPressGestureRecognizer! private var panGestureRecognizer: UIPanGestureRecognizer! @@ -61,6 +62,16 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable { return _waveformView } + /// Define the `backgroundView.backgroundColor`. + /// By setting this value the theme color will not be applyied to `backgroundView` in `update(theme: Theme)` method. + var customBackgroundViewColor: UIColor? { + didSet { + if let theme = currentTheme { + self.update(theme: theme) + } + } + } + override var bounds: CGRect { didSet { if oldValue.width != bounds.width { @@ -128,7 +139,10 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable { self.backgroundColor = theme.colors.background playButton.backgroundColor = theme.colors.background playButton.tintColor = theme.colors.secondaryContent - backgroundView.backgroundColor = theme.colors.quinaryContent + + let backgroundViewColor = self.customBackgroundViewColor ?? theme.colors.quinaryContent + + backgroundView.backgroundColor = backgroundViewColor _waveformView.primaryLineColor = theme.colors.quarterlyContent _waveformView.secondaryLineColor = theme.colors.secondaryContent elapsedTimeLabel.textColor = theme.colors.tertiaryContent diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib index 1bbca0184..943832e99 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib @@ -1,9 +1,9 @@ - + - + @@ -78,6 +78,7 @@ + diff --git a/Riot/Modules/Rooms/DirectoryPicker/DirectoryServerPickerViewController.m b/Riot/Modules/Rooms/DirectoryPicker/DirectoryServerPickerViewController.m index d04214673..5064dcb15 100644 --- a/Riot/Modules/Rooms/DirectoryPicker/DirectoryServerPickerViewController.m +++ b/Riot/Modules/Rooms/DirectoryPicker/DirectoryServerPickerViewController.m @@ -38,6 +38,9 @@ // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; } + +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + @end @implementation DirectoryServerPickerViewController @@ -49,6 +52,8 @@ // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; + + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSwitchDirectory]; } - (void)destroy @@ -145,9 +150,6 @@ { [super viewWillAppear:animated]; - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"DirectoryServerPicker"]; - // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -158,6 +160,12 @@ [dataSource loadData]; } +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + [self.screenTimer start]; +} + - (void)viewWillDisappear:(BOOL)animated { if (kAppDelegateDidTapStatusBarNotificationObserver) @@ -169,6 +177,12 @@ [super viewWillDisappear:animated]; } +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [self.screenTimer stop]; +} + - (void)displayWithDataSource:(MXKDirectoryServersDataSource*)theDataSource onComplete:(void (^)(id cellData))onComplete; { diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m index 25ae25ce9..71975e939 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -25,6 +25,8 @@ RecentsDataSource *recentsDataSource; } +@property (nonatomic, strong) MXThrottler *tableViewPaginationThrottler; + @end @implementation RoomsViewController @@ -40,7 +42,8 @@ { [super finalizeInit]; - self.screenName = @"Rooms"; + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenRooms]; + self.tableViewPaginationThrottler = [[MXThrottler alloc] initWithMinimumDelay:0.1]; } - (void)viewDidLoad @@ -112,6 +115,24 @@ return [super tableView:tableView heightForHeaderInSection:section]; } +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath +{ + if ([super respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) + { + [super tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath]; + } + + [self.tableViewPaginationThrottler throttle:^{ + NSInteger section = indexPath.section; + NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section]; + if (tableView.numberOfSections > section + && indexPath.row == numberOfRowsInSection - 1) + { + [self->recentsDataSource paginateInSection:section]; + } + }]; +} + #pragma mark - - (void)scrollToNextRoomWithMissedNotifications diff --git a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinator.swift b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinator.swift index 75a8365a7..6ab5e2517 100644 --- a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinator.swift +++ b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryCoordinator.swift @@ -63,6 +63,7 @@ final class ShowDirectoryCoordinator: ShowDirectoryCoordinatorType { private func createDirectoryServerPickerViewController() -> DirectoryServerPickerViewController { let controller = DirectoryServerPickerViewController() + controller.finalizeInit() let dataSource: MXKDirectoryServersDataSource = MXKDirectoryServersDataSource(matrixSession: session) dataSource.finalizeInitialization() dataSource.roomDirectoryServers = BuildSettings.publicRoomsDirectoryServers diff --git a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewController.swift b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewController.swift index 1e2cf5daf..ce22a3440 100644 --- a/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewController.swift +++ b/Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewController.swift @@ -68,6 +68,8 @@ final class ShowDirectoryViewController: UIViewController { }() private var sections: [ShowDirectorySection] = [] + + private let screenTimer = AnalyticsScreenTimer(screen: .roomDirectory) // MARK: - Setup @@ -104,10 +106,17 @@ final class ShowDirectoryViewController: UIViewController { self.keyboardAvoider?.startAvoiding() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + screenTimer.start() + } + override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.keyboardAvoider?.stopAvoiding() + + screenTimer.stop() } override var preferredStatusBarStyle: UIStatusBarStyle { diff --git a/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinator.swift b/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinator.swift index 4b05e230c..b4c04c610 100644 --- a/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinator.swift +++ b/Riot/Modules/ServiceTerms/Modal/ServiceTermsModalCoordinator.swift @@ -107,7 +107,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) { if serviceTerms.serviceType == MXServiceTypeIdentityService { - Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted) + Analytics.shared.trackIdentityServerAccepted(true) } self.delegate?.serviceTermsModalCoordinatorDidAccept(self) @@ -119,7 +119,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType) { if serviceTerms.serviceType == MXServiceTypeIdentityService { - Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted) + Analytics.shared.trackIdentityServerAccepted(false) disableIdentityServer() } @@ -131,7 +131,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega extension ServiceTermsModalCoordinator: UIAdaptivePresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { if serviceTerms.serviceType == MXServiceTypeIdentityService { - Analytics.sharedInstance().trackValue(0, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted) + Analytics.shared.trackIdentityServerAccepted(false) } self.delegate?.serviceTermsModalCoordinatorDidDismissInteractively(self) diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m index 3e4b4297e..0efbe7bdc 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m @@ -47,6 +47,8 @@ static CGFloat const kTextFontSize = 15.0; @property (weak, nonatomic) id themeDidChangeNotificationObserver; +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + @end #pragma mark - Implementation @@ -62,6 +64,12 @@ static CGFloat const kTextFontSize = 15.0; return viewController; } +- (void)finalizeInit +{ + [super finalizeInit]; + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenDeactivateAccount]; +} + - (void)destroy { id notificationObserver = self.themeDidChangeNotificationObserver; @@ -95,9 +103,12 @@ static CGFloat const kTextFontSize = 15.0; [super viewWillAppear:animated]; [self userInterfaceThemeDidChange]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"DeactivateAccount"]; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + [self.screenTimer start]; } - (void)viewDidLayoutSubviews @@ -107,6 +118,12 @@ static CGFloat const kTextFontSize = 15.0; [self.deactivateAcccountButton.layer setCornerRadius:kButtonCornerRadius]; } +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [self.screenTimer stop]; +} + - (UIStatusBarStyle)preferredStatusBarStyle { return ThemeService.shared.theme.statusBarStyle; diff --git a/Riot/Modules/Settings/Language/LanguagePickerViewController.m b/Riot/Modules/Settings/Language/LanguagePickerViewController.m index aac54c076..7c4c69055 100644 --- a/Riot/Modules/Settings/Language/LanguagePickerViewController.m +++ b/Riot/Modules/Settings/Language/LanguagePickerViewController.m @@ -106,14 +106,6 @@ } } -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"CountryPicker"]; -} - - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; { cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; diff --git a/Riot/Modules/Settings/PhoneCountry/CountryPickerViewController.m b/Riot/Modules/Settings/PhoneCountry/CountryPickerViewController.m index 0ab2fe0d8..bdbc14ee1 100644 --- a/Riot/Modules/Settings/PhoneCountry/CountryPickerViewController.m +++ b/Riot/Modules/Settings/PhoneCountry/CountryPickerViewController.m @@ -95,14 +95,6 @@ return ThemeService.shared.theme.statusBarStyle; } -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"CountryPicker"]; -} - - (void)destroy { [super destroy]; diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index b9b9ac474..42920094f 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -161,9 +161,6 @@ enum { { [super viewWillAppear:animated]; - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"ManageSession"]; - // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index 5c6d93aab..99b7f7c93 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -119,6 +119,8 @@ TableViewSectionsDelegate> @property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter; @property (nonatomic, strong) CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter; +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + @end @implementation SecurityViewController @@ -142,6 +144,8 @@ TableViewSectionsDelegate> // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; + + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSettingsSecurity]; } - (void)viewDidLoad @@ -250,9 +254,6 @@ TableViewSectionsDelegate> { [super viewWillAppear:animated]; - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"Security"]; - // Release the potential pushed view controller [self releasePushedViewController]; @@ -268,6 +269,12 @@ TableViewSectionsDelegate> [self loadCrossSigning]; } +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + [self.screenTimer start]; +} + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -279,6 +286,12 @@ TableViewSectionsDelegate> } } +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [self.screenTimer stop]; +} + #pragma mark - Internal methods - (void)updateSections diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 1993ab9f7..d009b7b6e 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -47,7 +47,7 @@ NSString* const kSettingsViewControllerPhoneBookCountryCellId = @"kSettingsViewControllerPhoneBookCountryCellId"; -enum +typedef NS_ENUM(NSUInteger, SECTION_TAG) { SECTION_TAG_SIGN_OUT = 0, SECTION_TAG_USER_SETTINGS, @@ -69,7 +69,7 @@ enum SECTION_TAG_DEACTIVATE_ACCOUNT }; -enum +typedef NS_ENUM(NSUInteger, USER_SETTINGS_INDEX) { USER_SETTINGS_PROFILE_PICTURE_INDEX = 0, USER_SETTINGS_DISPLAYNAME_INDEX, @@ -80,24 +80,24 @@ enum USER_SETTINGS_ADD_PHONENUMBER_INDEX }; -enum +typedef NS_ENUM(NSUInteger, USER_SETTINGS_OFFSET) { USER_SETTINGS_EMAILS_OFFSET = 2000, USER_SETTINGS_PHONENUMBERS_OFFSET = 1000 }; -enum +typedef NS_ENUM(NSUInteger, SENDING_MEDIA) { SENDING_MEDIA_CONFIRM_SIZE = 0 }; -enum +typedef NS_ENUM(NSUInteger, LINKS_SHOW_URL_PREVIEWS) { LINKS_SHOW_URL_PREVIEWS_INDEX = 0, LINKS_SHOW_URL_PREVIEWS_DESCRIPTION_INDEX }; -enum +typedef NS_ENUM(NSUInteger, NOTIFICATION_SETTINGS) { NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX = 0, NOTIFICATION_SETTINGS_SYSTEM_SETTINGS, @@ -109,33 +109,35 @@ enum NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX, }; -enum +typedef NS_ENUM(NSUInteger, CALLS_ENABLE_STUN_SERVER) { CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX = 0 }; -enum +typedef NS_ENUM(NSUInteger, INTEGRATIONS) { INTEGRATIONS_INDEX }; -enum { +typedef NS_ENUM(NSUInteger, LOCAL_CONTACTS) +{ LOCAL_CONTACTS_SYNC_INDEX, LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX }; -enum +typedef NS_ENUM(NSUInteger, USER_INTERFACE) { USER_INTERFACE_LANGUAGE_INDEX = 0, - USER_INTERFACE_THEME_INDEX + USER_INTERFACE_THEME_INDEX, + USER_INTERFACE_TIMELINE_STYLE_INDEX }; -enum +typedef NS_ENUM(NSUInteger, IDENTITY_SERVER) { IDENTITY_SERVER_INDEX }; -enum +typedef NS_ENUM(NSUInteger, ADVANCED) { ADVANCED_SHOW_NSFW_ROOMS_INDEX = 0, ADVANCED_CRASH_REPORT_INDEX, @@ -145,7 +147,7 @@ enum ADVANCED_REPORT_BUG_INDEX, }; -enum +typedef NS_ENUM(NSUInteger, ABOUT) { ABOUT_COPYRIGHT_INDEX = 0, ABOUT_TERM_CONDITIONS_INDEX, @@ -155,11 +157,11 @@ enum typedef NS_ENUM(NSUInteger, LABS_ENABLE) { - LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX, - LABS_ENABLE_POLLS + LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0, + LABS_ENABLE_THREADS_INDEX }; -enum +typedef NS_ENUM(NSUInteger, SECURITY) { SECURITY_BUTTON_INDEX = 0, }; @@ -283,6 +285,8 @@ TableViewSectionsDelegate> @property (nonatomic) BOOL isPreparingIdentityService; @property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter; +@property (nonatomic) AnalyticsScreenTimer *screenTimer; + @end @implementation SettingsViewController @@ -315,6 +319,8 @@ TableViewSectionsDelegate> isSavingInProgress = NO; isResetPwdInProgress = NO; is3PIDBindingInProgress = NO; + + self.screenTimer = [[AnalyticsScreenTimer alloc] initWithScreen:AnalyticsScreenSettings]; } - (void)updateSections @@ -369,7 +375,7 @@ TableViewSectionsDelegate> sectionUserSettings.headerTitle = [VectorL10n settingsUserSettings]; [tmpSections addObject:sectionUserSettings]; - + if (BuildSettings.settingsScreenShowConfirmMediaSize) { Section *sectionMedia = [Section sectionWithTag:SECTION_TAG_SENDING_MEDIA]; @@ -495,9 +501,16 @@ TableViewSectionsDelegate> } Section *sectionUserInterface = [Section sectionWithTag:SECTION_TAG_USER_INTERFACE]; + sectionUserInterface.headerTitle = [VectorL10n settingsUserInterface]; + [sectionUserInterface addRowWithTag:USER_INTERFACE_LANGUAGE_INDEX]; [sectionUserInterface addRowWithTag:USER_INTERFACE_THEME_INDEX]; - sectionUserInterface.headerTitle = [VectorL10n settingsUserInterface]; + + if (BuildSettings.roomScreenAllowTimelineStyleConfiguration) + { + [sectionUserInterface addRowWithTag:USER_INTERFACE_TIMELINE_STYLE_INDEX]; + } + [tmpSections addObject: sectionUserInterface]; Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED]; @@ -552,8 +565,7 @@ TableViewSectionsDelegate> { Section *sectionLabs = [Section sectionWithTag:SECTION_TAG_LABS]; [sectionLabs addRowWithTag:LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX]; - [sectionLabs addRowWithTag:LABS_ENABLE_POLLS]; - + [sectionLabs addRowWithTag:LABS_ENABLE_THREADS_INDEX]; sectionLabs.headerTitle = [VectorL10n settingsLabs]; if (sectionLabs.hasAnyRows) { @@ -776,9 +788,6 @@ TableViewSectionsDelegate> - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - // Screen tracking - [[Analytics sharedInstance] trackScreen:@"Settings"]; // Refresh display [self refreshSettings]; @@ -808,6 +817,8 @@ TableViewSectionsDelegate> [self releasePushedViewController]; [self.settingsDiscoveryTableViewSection reload]; + + [self.screenTimer start]; } - (void)viewWillDisappear:(BOOL)animated @@ -851,6 +862,12 @@ TableViewSectionsDelegate> } } +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + [self.screenTimer stop]; +} + #pragma mark - Internal methods - (void)pushViewController:(UIViewController*)viewController @@ -2187,6 +2204,19 @@ TableViewSectionsDelegate> [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; } + else if (row == USER_INTERFACE_TIMELINE_STYLE_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsEnableRoomMessageBubbles]; + + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenEnableMessageBubbles; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableRoomMessageBubbles:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } } else if (section == SECTION_TAG_IGNORED_USERS) { @@ -2251,11 +2281,11 @@ TableViewSectionsDelegate> { MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - sendCrashReportCell.mxkLabel.text = [VectorL10n settingsSendCrashReport]; - sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableCrashReport; + sendCrashReportCell.mxkLabel.text = VectorL10n.settingsAnalyticsAndCrashData; + sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableAnalytics; sendCrashReportCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; sendCrashReportCell.mxkSwitch.enabled = YES; - [sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleSendCrashReport:) forControlEvents:UIControlEventTouchUpInside]; + [sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleAnalytics:) forControlEvents:UIControlEventTouchUpInside]; cell = sendCrashReportCell; } @@ -2404,16 +2434,15 @@ TableViewSectionsDelegate> cell = labelAndSwitchCell; } - - if (row == LABS_ENABLE_POLLS && BuildSettings.pollsEnabled) + else if (row == LABS_ENABLE_THREADS_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnabledPolls]; - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenAllowPollsAction; + labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableThreads]; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableThreads; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnablePolls:) forControlEvents:UIControlEventValueChanged]; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableThreads:) forControlEvents:UIControlEventValueChanged]; cell = labelAndSwitchCell; } @@ -3115,27 +3144,20 @@ TableViewSectionsDelegate> [[MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession] reset]; } -- (void)toggleSendCrashReport:(id)sender +- (void)toggleAnalytics:(UISwitch *)sender { - BOOL enable = RiotSettings.shared.enableCrashReport; - if (enable) + if (sender.isOn) { - MXLogDebug(@"[SettingsViewController] disable automatic crash report and analytics sending"); - - RiotSettings.shared.enableCrashReport = NO; - - [[Analytics sharedInstance] stop]; - - // Remove potential crash file. - [MXLogger deleteCrashLog]; + MXLogDebug(@"[SettingsViewController] enable automatic crash report and analytics sending"); + [Analytics.shared optInWith:self.mainSession]; } else { - MXLogDebug(@"[SettingsViewController] enable automatic crash report and analytics sending"); + MXLogDebug(@"[SettingsViewController] disable automatic crash report and analytics sending"); + [Analytics.shared optOut]; - RiotSettings.shared.enableCrashReport = YES; - - [[Analytics sharedInstance] start]; + // Remove potential crash file. + [MXLogger deleteCrashLog]; } } @@ -3151,9 +3173,12 @@ TableViewSectionsDelegate> RiotSettings.shared.enableRingingForGroupCalls = sender.isOn; } -- (void)toggleEnablePolls:(UISwitch *)sender +- (void)toggleEnableThreads:(UISwitch *)sender { - RiotSettings.shared.roomScreenAllowPollsAction = sender.isOn; + RiotSettings.shared.enableThreads = sender.isOn; + MXSDKOptions.sharedInstance.enableThreads = sender.isOn; + [[MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession] reset]; + [[AppDelegate theDelegate] restoreEmptyDetailsViewController]; } - (void)togglePinRoomsWithMissedNotif:(UISwitch *)sender @@ -3837,6 +3862,18 @@ TableViewSectionsDelegate> RiotSettings.shared.showNSFWPublicRooms = sender.isOn; } +- (void)toggleEnableRoomMessageBubbles:(UISwitch *)sender +{ + RiotSettings.shared.roomScreenEnableMessageBubbles = sender.isOn; + + [[RoomTimelineConfiguration shared] updateStyleWithIdentifier:RiotSettings.shared.roomTimelineStyleIdentifier]; + + // Close all room data sources + // Be sure to use new room timeline style configurations + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; + [roomDataSourceManager reset]; +} + #pragma mark - TextField listener - (IBAction)textFieldDidChange:(id)sender diff --git a/Riot/Modules/SideMenu/SideMenuCoordinator.swift b/Riot/Modules/SideMenu/SideMenuCoordinator.swift index 797e20818..e4e3fedee 100644 --- a/Riot/Modules/SideMenu/SideMenuCoordinator.swift +++ b/Riot/Modules/SideMenu/SideMenuCoordinator.swift @@ -64,6 +64,7 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { private var exploreRoomCoordinator: ExploreRoomCoordinator? private var membersCoordinator: SpaceMembersCoordinator? + private var createSpaceCoordinator: SpaceCreationCoordinator? // MARK: Public @@ -257,6 +258,36 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { self.spaceDetailPresenter.present(forSpaceWithId: spaceId, from: self.sideMenuViewController, sourceView: sourceView, session: session, animated: true) } + @available(iOS 14.0, *) + private func showCreateSpace() { + guard let session = self.parameters.userSessionsService.mainUserSession?.matrixSession else { + return + } + + let coordinator = SpaceCreationCoordinator(parameters: SpaceCreationCoordinatorParameters(session: session)) + let presentable = coordinator.toPresentable() + presentable.presentationController?.delegate = self + self.sideMenuViewController.present(presentable, animated: true, completion: nil) + coordinator.callback = { [weak self] result in + guard let self = self else { + return + } + + self.createSpaceCoordinator?.toPresentable().dismiss(animated: true) { + self.createSpaceCoordinator = nil + switch result { + case .cancel: + break + case .done(let spaceId): + self.select(spaceWithId: spaceId) + } + } + } + coordinator.start() + + self.createSpaceCoordinator = coordinator + } + // MARK: UserSessions management private func registerUserSessionsServiceNotifications() { @@ -310,7 +341,7 @@ extension SideMenuCoordinator: SideMenuNavigationControllerDelegate { // MARK: - SideMenuNavigationControllerDelegate extension SideMenuCoordinator: SpaceListCoordinatorDelegate { - func spaceListCoordinatorDidSelectHomeSpace(_ coordinator: SpaceListCoordinatorType) { + func spaceListCoordinatorDidSelectHomeSpace(_ coordinator: SpaceListCoordinatorType) { self.parameters.appNavigator.sideMenu.dismiss(animated: true) { } @@ -331,6 +362,12 @@ extension SideMenuCoordinator: SpaceListCoordinatorDelegate { func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView) { self.showMenu(forSpaceWithId: spaceId, from: sourceView) } + + func spaceListCoordinatorDidSelectCreateSpace(_ coordinator: SpaceListCoordinatorType) { + if #available(iOS 14.0, *) { + self.showCreateSpace() + } + } } // MARK: - SpaceMenuPresenterDelegate @@ -342,6 +379,12 @@ extension SideMenuCoordinator: SpaceMenuPresenterDelegate { self.showExploreRooms(spaceId: spaceId, session: session) case .exploreMembers: self.showMembers(spaceId: spaceId, session: session) + case .addRoom: + AppDelegate.theDelegate().showAlert(withTitle: VectorL10n.spacesAddRoom, message: VectorL10n.spacesComingSoonDetail(AppInfo.current.displayName)) + case .addSpace: + AppDelegate.theDelegate().showAlert(withTitle: VectorL10n.spacesAddSpace, message: VectorL10n.spacesComingSoonDetail(AppInfo.current.displayName)) + case .settings: + AppDelegate.theDelegate().showAlert(withTitle: VectorL10n.sideMenuActionSettings, message: VectorL10n.spacesComingSoonDetail(AppInfo.current.displayName)) } } } @@ -386,5 +429,6 @@ extension SideMenuCoordinator: UIAdaptivePresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { self.exploreRoomCoordinator = nil self.membersCoordinator = nil + self.createSpaceCoordinator = nil } } diff --git a/Riot/Modules/SideMenu/SideMenuViewController.swift b/Riot/Modules/SideMenu/SideMenuViewController.swift index f7fecb60c..cec36f497 100644 --- a/Riot/Modules/SideMenu/SideMenuViewController.swift +++ b/Riot/Modules/SideMenu/SideMenuViewController.swift @@ -48,6 +48,7 @@ final class SideMenuViewController: UIViewController { private var keyboardAvoider: KeyboardAvoider? private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! + private var screenTimer = AnalyticsScreenTimer(screen: .sidebar) private var sideMenuActionViews: [SideMenuActionView] = [] private weak var sideMenuVersionView: SideMenuVersionView? @@ -86,8 +87,14 @@ final class SideMenuViewController: UIViewController { navigationController?.setNavigationBarHidden(true, animated: animated) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + screenTimer.start() + } + override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) + screenTimer.stop() } override var preferredStatusBarStyle: UIStatusBarStyle { diff --git a/Riot/Modules/SideMenu/SideMenuViewModel.swift b/Riot/Modules/SideMenu/SideMenuViewModel.swift index c8baac060..d1b013f97 100644 --- a/Riot/Modules/SideMenu/SideMenuViewModel.swift +++ b/Riot/Modules/SideMenu/SideMenuViewModel.swift @@ -97,10 +97,14 @@ final class SideMenuViewModel: SideMenuViewModelType { return } - let sideMenuItems: [SideMenuItem] = [ - .inviteFriends, + var sideMenuItems: [SideMenuItem] = [] + + if BuildSettings.sideMenuShowInviteFriends { + sideMenuItems += [.inviteFriends] + } + + sideMenuItems += [ .settings, - .help, .feedback ] diff --git a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift index bca282949..60c942176 100644 --- a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift +++ b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.swift @@ -186,7 +186,7 @@ class SpaceDetailViewController: UIViewController { private func setup(button: UIButton, withTitle title: String) { button.layer.masksToBounds = true button.layer.cornerRadius = 8.0 - button.setTitle(title.uppercased(), for: .normal) + button.setTitle(title, for: .normal) } private func render(viewState: SpaceDetailViewState) { diff --git a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewModel.swift b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewModel.swift index ac4583718..46bb2b1f1 100644 --- a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewModel.swift +++ b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewModel.swift @@ -27,6 +27,7 @@ class SpaceDetailViewModel: SpaceDetailViewModelType { private let session: MXSession private let spaceId: String private let publicRoom: MXPublicRoom? + private var spaceGraphObserver: Any? // MARK: - Setup @@ -42,6 +43,12 @@ class SpaceDetailViewModel: SpaceDetailViewModelType { self.spaceId = publicRoom.roomId } + deinit { + if let spaceGraphObserver = spaceGraphObserver { + NotificationCenter.default.removeObserver(spaceGraphObserver) + } + } + // MARK: - Public func process(viewAction: SpaceDetailViewAction) { @@ -108,7 +115,14 @@ class SpaceDetailViewModel: SpaceDetailViewModelType { guard let self = self else { return } switch response { case .success: - self.coordinatorDelegate?.spaceDetailViewModelDidJoin(self) + self.spaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] notification in + guard let self = self else { return } + + if let spaceGraphObserver = self.spaceGraphObserver { + NotificationCenter.default.removeObserver(spaceGraphObserver) + } + self.coordinatorDelegate?.spaceDetailViewModelDidJoin(self) + } case .failure(let error): self.update(viewState: .error(error)) } diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListCoordinator.swift b/Riot/Modules/Spaces/SpaceList/SpaceListCoordinator.swift index 5d6924347..e8cfc5db5 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListCoordinator.swift @@ -85,4 +85,9 @@ extension SpaceListCoordinator: SpaceListViewModelCoordinatorDelegate { func spaceListViewModel(_ viewModel: SpaceListViewModelType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView) { self.delegate?.spaceListCoordinator(self, didPressMoreForSpaceWithId: spaceId, from: sourceView) } + + func spaceListViewModelDidSelectCreateSpace(_ viewModel: SpaceListViewModelType) { + self.delegate?.spaceListCoordinatorDidSelectCreateSpace(self) + } + } diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListCoordinatorType.swift b/Riot/Modules/Spaces/SpaceList/SpaceListCoordinatorType.swift index 8ab4815bc..03383216e 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListCoordinatorType.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListCoordinatorType.swift @@ -23,6 +23,7 @@ protocol SpaceListCoordinatorDelegate: AnyObject { func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didSelectSpaceWithId spaceId: String) func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didSelectInviteWithId spaceId: String, from sourceView: UIView?) func spaceListCoordinator(_ coordinator: SpaceListCoordinatorType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView) + func spaceListCoordinatorDidSelectCreateSpace(_ coordinator: SpaceListCoordinatorType) } /// `SpaceListCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListSection.swift b/Riot/Modules/Spaces/SpaceList/SpaceListSection.swift index f4cc0c399..8877dbac2 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListSection.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListSection.swift @@ -20,4 +20,5 @@ import Foundation enum SpaceListSection { case home(_ viewData: SpaceListItemViewData) case spaces(_ viewDataList: [SpaceListItemViewData]) + case addSpace(_ viewData: SpaceListItemViewData) } diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.swift b/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.swift index eaf3a5e09..b952b336c 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.swift @@ -58,7 +58,7 @@ final class SpaceListViewCell: UITableViewCell, Themable, NibReusable { func fill(with viewData: SpaceListItemViewData) { self.avatarView.fill(with: viewData.avatarViewData) self.titleLabel.text = viewData.title - self.moreButton.isHidden = viewData.isInvite + self.moreButton.isHidden = viewData.spaceId == SpaceListViewModel.Constants.addSpaceId || viewData.isInvite if viewData.isInvite { self.isBadgeAlert = true self.badgeLabel.isHidden = false @@ -68,7 +68,7 @@ final class SpaceListViewCell: UITableViewCell, Themable, NibReusable { self.badgeLabel.text = "!" } else { self.isBadgeAlert = viewData.highlightedNotificationCount > 0 - let notificationCount = viewData.notificationCount + viewData.highlightedNotificationCount + let notificationCount = viewData.notificationCount self.badgeLabel.isHidden = notificationCount == 0 if let theme = self.theme { self.badgeLabel.badgeColor = viewData.highlightedNotificationCount == 0 ? theme.colors.tertiaryContent : theme.colors.alert diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.xib b/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.xib index facf89a8c..d1e088c42 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.xib +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewCell.xib @@ -22,7 +22,7 @@ - + diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewController.storyboard b/Riot/Modules/Spaces/SpaceList/SpaceListViewController.storyboard index 3d14f2100..e1b7c94e9 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewController.storyboard +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewController.storyboard @@ -18,7 +18,7 @@ + + - + + diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewController.swift b/Riot/Modules/Spaces/SpaceList/SpaceListViewController.swift index 79f5f2dba..2e3586048 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewController.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewController.swift @@ -32,13 +32,13 @@ final class SpaceListViewController: UIViewController { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! // MARK: Private private var viewModel: SpaceListViewModelType! private var theme: Theme! private var errorPresenter: MXKErrorPresentation! - private var activityPresenter: ActivityIndicatorPresenter! private var sections: [SpaceListSection] = [] @@ -59,7 +59,6 @@ final class SpaceListViewController: UIViewController { // Do any additional setup after loading the view. self.setupViews() - self.activityPresenter = ActivityIndicatorPresenter() self.errorPresenter = MXKErrorAlertPresentation() self.registerThemeServiceDidChangeThemeNotification() @@ -86,6 +85,8 @@ final class SpaceListViewController: UIViewController { self.titleLabel.textColor = theme.colors.primaryContent self.titleLabel.font = theme.fonts.bodySB + + self.activityIndicator.color = theme.colors.secondaryContent } private func registerThemeServiceDidChangeThemeNotification() { @@ -124,14 +125,11 @@ final class SpaceListViewController: UIViewController { } private func renderLoading() { - self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) - if let selectedRow = self.tableView.indexPathForSelectedRow { - self.tableView.deselectRow(at: selectedRow, animated: true) - } + self.activityIndicator.startAnimating() } private func renderLoaded(sections: [SpaceListSection]) { - self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.activityIndicator.stopAnimating() self.sections = sections self.tableView.reloadData() } @@ -141,7 +139,6 @@ final class SpaceListViewController: UIViewController { } private func render(error: Error) { - self.activityPresenter.removeCurrentActivityIndicator(animated: true) self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) } } @@ -172,8 +169,10 @@ extension SpaceListViewController: UITableViewDataSource { numberOfRows = 1 case .spaces(let viewDataList): numberOfRows = viewDataList.count + case .addSpace: + numberOfRows = 1 } - + return numberOfRows } @@ -189,6 +188,8 @@ extension SpaceListViewController: UITableViewDataSource { viewData = spaceViewData case .spaces(let viewDataList): viewData = viewDataList[indexPath.row] + case .addSpace(let spaceViewData): + viewData = spaceViewData } cell.update(theme: self.theme) diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift b/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift index 62770e05e..f1127657b 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift @@ -24,6 +24,7 @@ final class SpaceListViewModel: SpaceListViewModelType { enum Constants { static let homeSpaceId: String = "home" + static let addSpaceId: String = "add_space" } // MARK: - Properties @@ -55,6 +56,8 @@ final class SpaceListViewModel: SpaceListViewModelType { NotificationCenter.default.addObserver(self, selector: #selector(self.sessionDidSync(notification:)), name: MXSpaceService.didBuildSpaceGraph, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.counterDidUpdateNotificationCount(notification:)), name: MXSpaceNotificationCounter.didUpdateNotificationCount, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(self.loadData), name: NSNotification.Name.themeServiceDidChangeTheme, object: nil) } @@ -87,12 +90,16 @@ final class SpaceListViewModel: SpaceListViewModelType { self.selectedIndexPath = indexPath self.update(viewState: .selectionChanged(indexPath)) } + case .addSpace: + self.update(viewState: .selectionChanged(self.selectedIndexPath)) + addSpace() } case .moreAction(at: let indexPath, from: let sourceView): let section = self.sections[indexPath.section] switch section { case .home: self.coordinatorDelegate?.spaceListViewModel(self, didPressMoreForSpaceWithId: Constants.homeSpaceId, from: sourceView) + case .addSpace: break case .spaces(let viewDataList): let spaceViewData = viewDataList[indexPath.row] self.coordinatorDelegate?.spaceListViewModel(self, didPressMoreForSpaceWithId: spaceViewData.spaceId, from: sourceView) @@ -108,6 +115,7 @@ final class SpaceListViewModel: SpaceListViewModelType { for (sectionIndex, section) in self.sections.enumerated() { switch section { case .home: break + case .addSpace: break case .spaces(let viewDataList): for (row, itemViewData) in viewDataList.enumerated() where itemViewData.spaceId == spaceId { let indexPath = IndexPath(row: row, section: sectionIndex) @@ -129,7 +137,7 @@ final class SpaceListViewModel: SpaceListViewModelType { loadData() } - private func loadData() { + @objc private func loadData() { guard let session = self.userSessionsService.mainUserSession?.matrixSession else { // If there is no main session, reset current selection and give an empty section list // It can happen when the user make a clear cache or logout @@ -142,7 +150,7 @@ final class SpaceListViewModel: SpaceListViewModelType { let homeViewData = self.createHomeViewData(session: session) let viewDataList = getSpacesViewData(session: session) - let sections: [SpaceListSection] = viewDataList.invites.isEmpty ? [ + var sections: [SpaceListSection] = viewDataList.invites.isEmpty ? [ .home(homeViewData), .spaces(viewDataList.spaces) ] @@ -152,6 +160,12 @@ final class SpaceListViewModel: SpaceListViewModelType { .home(homeViewData), .spaces(viewDataList.spaces) ] + + let spacesSectionIndex = sections.count - 1 + if #available(iOS 14.0, *) { + let addSpaceViewData = self.createAddSpaceViewData(session: session) + sections.append(.addSpace(addSpaceViewData)) + } self.sections = sections let homeIndexPath = viewDataList.invites.isEmpty ? IndexPath(row: 0, section: 0) : IndexPath(row: 0, section: 1) @@ -159,10 +173,9 @@ final class SpaceListViewModel: SpaceListViewModelType { self.selectedIndexPath = homeIndexPath } else if self.selectedItemId != self.itemId(with: self.selectedIndexPath) { var newSelection: IndexPath? - let section = sections.last + let section = sections[spacesSectionIndex] switch section { - case .home: - break + case .home, .addSpace: break case .spaces(let viewDataList): var index = 0 for itemViewData in viewDataList { @@ -171,8 +184,6 @@ final class SpaceListViewModel: SpaceListViewModelType { } index += 1 } - case .none: - break } if let selection = newSelection { @@ -191,6 +202,10 @@ final class SpaceListViewModel: SpaceListViewModelType { self.coordinatorDelegate?.spaceListViewModelDidSelectHomeSpace(self) } + private func addSpace() { + self.coordinatorDelegate?.spaceListViewModelDidSelectCreateSpace(self) + } + private func selectSpace(with spaceId: String) { self.coordinatorDelegate?.spaceListViewModel(self, didSelectSpaceWithId: spaceId) } @@ -200,21 +215,44 @@ final class SpaceListViewModel: SpaceListViewModelType { } private func createHomeViewData(session: MXSession) -> SpaceListItemViewData { - let avatarViewData = AvatarViewData(matrixItemId: Constants.homeSpaceId, displayName: nil, avatarUrl: nil, mediaManager: session.mediaManager, fallbackImage: .image(Asset.Images.spaceHomeIcon.image, .center)) + let defaultAsset = ThemeService.shared().isCurrentThemeDark() ? Asset.Images.spaceHomeIconDark : Asset.Images.spaceHomeIconLight + let avatarViewData = AvatarViewData(matrixItemId: Constants.homeSpaceId, displayName: nil, avatarUrl: nil, mediaManager: session.mediaManager, fallbackImage: .image(defaultAsset.image, .center)) let homeNotificationState = session.spaceService.notificationCounter.homeNotificationState let homeViewData = SpaceListItemViewData(spaceId: Constants.homeSpaceId, - title: VectorL10n.spacesHomeSpaceTitle, avatarViewData: avatarViewData, isInvite: false, notificationCount: homeNotificationState.allCount, highlightedNotificationCount: homeNotificationState.allHighlightCount) + title: VectorL10n.spacesHomeSpaceTitle, + avatarViewData: avatarViewData, + isInvite: false, + notificationCount: homeNotificationState.allCount, + highlightedNotificationCount: homeNotificationState.allHighlightCount) return homeViewData } + private func createAddSpaceViewData(session: MXSession) -> SpaceListItemViewData { + let defaultAsset = ThemeService.shared().isCurrentThemeDark() ? Asset.Images.spacesAddSpaceDark : Asset.Images.spacesAddSpaceLight + let avatarViewData = AvatarViewData(matrixItemId: Constants.addSpaceId, displayName: nil, avatarUrl: nil, mediaManager: session.mediaManager, fallbackImage: .image(defaultAsset.image, .center)) + + let homeViewData = SpaceListItemViewData(spaceId: Constants.addSpaceId, + title: VectorL10n.spacesAddSpaceTitle, + avatarViewData: avatarViewData, + isInvite: false, + notificationCount: 0, + highlightedNotificationCount: 0) + return homeViewData + } + private func getSpacesViewData(session: MXSession) -> (invites: [SpaceListItemViewData], spaces: [SpaceListItemViewData]) { var invites: [SpaceListItemViewData] = [] var spaces: [SpaceListItemViewData] = [] session.spaceService.rootSpaceSummaries.forEach { summary in let avatarViewData = AvatarViewData(matrixItemId: summary.roomId, displayName: summary.displayname, avatarUrl: summary.avatar, mediaManager: session.mediaManager, fallbackImage: .matrixItem(summary.roomId, summary.displayname)) let notificationState = session.spaceService.notificationCounter.notificationState(forSpaceWithId: summary.roomId) - let viewData = SpaceListItemViewData(spaceId: summary.roomId, title: summary.displayname, avatarViewData: avatarViewData, isInvite: summary.membership == .invite, notificationCount: notificationState?.groupMissedDiscussionsCount ?? 0, highlightedNotificationCount: notificationState?.groupMissedDiscussionsHighlightedCount ?? 0) + let viewData = SpaceListItemViewData(spaceId: summary.roomId, + title: summary.displayname, + avatarViewData: avatarViewData, + isInvite: summary.membership == .invite, + notificationCount: notificationState?.groupMissedDiscussionsCount ?? 0, + highlightedNotificationCount: notificationState?.groupMissedDiscussionsHighlightedCount ?? 0) if viewData.isInvite { invites.append(viewData) } else { @@ -244,6 +282,8 @@ final class SpaceListViewModel: SpaceListViewModelType { case .spaces(let viewDataList): let spaceViewData = viewDataList[self.selectedIndexPath.row] return spaceViewData.spaceId + case .addSpace: + return Constants.addSpaceId } } diff --git a/Riot/Modules/Spaces/SpaceList/SpaceListViewModelType.swift b/Riot/Modules/Spaces/SpaceList/SpaceListViewModelType.swift index 00066a70d..cb706a8c2 100644 --- a/Riot/Modules/Spaces/SpaceList/SpaceListViewModelType.swift +++ b/Riot/Modules/Spaces/SpaceList/SpaceListViewModelType.swift @@ -27,6 +27,7 @@ protocol SpaceListViewModelCoordinatorDelegate: AnyObject { func spaceListViewModel(_ viewModel: SpaceListViewModelType, didSelectSpaceWithId spaceId: String) func spaceListViewModel(_ viewModel: SpaceListViewModelType, didSelectInviteWithId spaceId: String, from sourceView: UIView?) func spaceListViewModel(_ viewModel: SpaceListViewModelType, didPressMoreForSpaceWithId spaceId: String, from sourceView: UIView) + func spaceListViewModelDidSelectCreateSpace(_ viewModel: SpaceListViewModelType) } /// Protocol describing the view model used by `SpaceListViewController` diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/AddItemHeaderView.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/AddItemHeaderView.swift new file mode 100644 index 000000000..41b207a17 --- /dev/null +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/AddItemHeaderView.swift @@ -0,0 +1,90 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import Reusable + +@objc +protocol AddItemHeaderViewDelegate: AnyObject { + func addItemHeaderView(_ headerView: AddItemHeaderView, didTapButton button: UIButton) +} + +/// `AddItemHeaderView` is a generic view used as a header view for UITableView. +/// With this view we can add an extra action cell with icon and text as for SpaceMemberList and SpaceExploreRooms +@objcMembers +final class AddItemHeaderView: UIView, NibLoadable, Themable { + + // MARK: - Constants + + private enum Constants { + static let buttonHighlightedAlpha: CGFloat = 0.2 + } + + // MARK: - Properties + + @IBOutlet private weak var button: UIButton! + @IBOutlet private weak var iconBackgroundView: UIView! + @IBOutlet private weak var iconView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + weak var delegate: AddItemHeaderViewDelegate? + + private var title: String? { + didSet { + titleLabel.text = title + } + } + private var icon: UIImage? { + didSet { + iconView.image = icon + } + } + + // MARK: - Setup + + static func instantiate(title: String?, icon: UIImage?) -> AddItemHeaderView { + let view = AddItemHeaderView.loadFromNib() + view.icon = icon + view.title = title + view.update(theme: ThemeService.shared().theme) + return view + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside) + iconBackgroundView.layer.masksToBounds = true + iconBackgroundView.layer.cornerRadius = iconBackgroundView.bounds.width / 2 + } + + // MARK: - Public + + func update(theme: Theme) { + iconBackgroundView.layer.backgroundColor = theme.colors.quinaryContent.cgColor + iconView.tintColor = theme.colors.secondaryContent + titleLabel.textColor = theme.colors.primaryContent + titleLabel.font = theme.fonts.headline + } + + // MARK: - Action + + @objc private func buttonAction(_ sender: UIButton) { + delegate?.addItemHeaderView(self, didTapButton: button) + } +} diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/AddItemHeaderView.xib b/Riot/Modules/Spaces/SpaceMembers/MemberList/AddItemHeaderView.xib new file mode 100644 index 000000000..0fb31911b --- /dev/null +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/AddItemHeaderView.xib @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinator.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinator.swift index 77ec12f62..76ae70b78 100644 --- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinator.swift @@ -70,4 +70,8 @@ extension SpaceMemberListCoordinator: SpaceMemberListViewModelCoordinatorDelegat func spaceMemberListViewModelDidCancel(_ viewModel: SpaceMemberListViewModelType) { self.delegate?.spaceMemberListCoordinatorDidCancel(self) } + + func spaceMemberListViewModelShowInvite(_ viewModel: SpaceMemberListViewModelType) { + self.delegate?.spaceMemberListCoordinatorShowInvite(self) + } } diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinatorType.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinatorType.swift index 43225f787..d933457bd 100644 --- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinatorType.swift +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListCoordinatorType.swift @@ -21,6 +21,7 @@ import Foundation protocol SpaceMemberListCoordinatorDelegate: AnyObject { func spaceMemberListCoordinator(_ coordinator: SpaceMemberListCoordinatorType, didSelect member: MXRoomMember, from sourceView: UIView?) func spaceMemberListCoordinatorDidCancel(_ coordinator: SpaceMemberListCoordinatorType) + func spaceMemberListCoordinatorShowInvite(_ coordinator: SpaceMemberListCoordinatorType) } /// `SpaceMemberListCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewAction.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewAction.swift index 83118ce29..71b899a25 100644 --- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewAction.swift +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewAction.swift @@ -23,4 +23,5 @@ enum SpaceMemberListViewAction { case loadData case complete(_ selectedMember: MXRoomMember, _ sourceView: UIView?) case cancel + case invite } diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift index d05076758..559a761c5 100644 --- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewController.swift @@ -35,20 +35,16 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { private var errorPresenter: MXKErrorPresentation! private var activityPresenter: ActivityIndicatorPresenter! private var titleView: MainTitleView! - private var emptyView: SearchEmptyView! + private let inviteHeaderView = AddItemHeaderView.instantiate(title: VectorL10n.spacesInvitePeople, icon: Asset.Images.spaceInviteUser.image) - private var emptyViewArtwork: UIImage { - return ThemeService.shared().isCurrentThemeDark() ? Asset.Images.peopleEmptyScreenArtworkDark.image : Asset.Images.peopleEmptyScreenArtwork.image - } - // MARK: - Setup class func instantiate(with viewModel: SpaceMemberListViewModelType) -> SpaceMemberListViewController { let viewController = SpaceMemberListViewController() viewController.viewModel = viewModel viewController.showParticipantCustomAccessoryView = false + viewController.showInviteUserFab = false viewController.theme = ThemeService.shared().theme - viewController.emptyView = SearchEmptyView() return viewController } @@ -71,14 +67,21 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { self.viewModel.process(viewAction: .loadData) self.title = "" + + self.setupTableViewHeader() } - + override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle } // MARK: - Private - + + private func setupTableViewHeader() { + inviteHeaderView.delegate = self + tableView.tableHeaderView = inviteHeaderView + } + private func update(theme: Theme) { self.theme = theme @@ -90,7 +93,8 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { theme.applyStyle(onSearchBar: self.searchBarView) self.titleView.update(theme: theme) - self.emptyView.update(theme: theme) + + self.inviteHeaderView.update(theme: theme) } private func registerThemeServiceDidChangeThemeNotification() { @@ -111,11 +115,6 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { self.titleView = MainTitleView() self.titleView.titleLabel.text = VectorL10n.roomDetailsPeople self.navigationItem.titleView = self.titleView - - self.emptyView.frame = CGRect(x: Constants.emptySearchViewMargin, y: self.searchBarView.frame.maxY + 2 * Constants.emptySearchViewMargin, width: self.view.bounds.width - 2 * Constants.emptySearchViewMargin, height: 0) - self.emptyView.autoresizingMask = [.flexibleHeight, .flexibleWidth] - self.emptyView.alpha = 0 - self.view.insertSubview(self.emptyView, at: 0) } private func render(viewState: SpaceMemberListViewState) { @@ -137,9 +136,6 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { self.activityPresenter.removeCurrentActivityIndicator(animated: true) self.mxRoom = space.room self.titleView.subtitleLabel.text = space.summary?.displayname - self.emptyView.titleLabel.text = VectorL10n.spacesNoResultFoundTitle - self.emptyView.detailLabel.text = VectorL10n.spacesNoMemberFoundDetail(space.summary?.displayname ?? "") - self.emptyView.layoutIfNeeded() } private func render(error: Error) { @@ -154,7 +150,7 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { // MARK: - Actions @objc private func onAddParticipantButtonPressed() { - self.errorPresenter.presentError(from: self, title: VectorL10n.spacesInvitesComingSoonTitle, message: VectorL10n.spacesComingSoonDetail, animated: true, handler: nil) + self.viewModel.process(viewAction: .invite) } private func cancelButtonAction() { @@ -173,22 +169,17 @@ final class SpaceMemberListViewController: RoomParticipantsViewController { override func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { super.searchBar(searchBar, textDidChange: searchText) - - UIView.animate(withDuration: 0.2) { - self.emptyView.alpha = self.tableView.numberOfSections == 0 ? 1 : 0 - self.tableView.alpha = self.tableView.numberOfSections == 0 ? 0 : 1 - } } // MARK: - MXKRoomMemberDetailsViewControllerDelegate override func roomMemberDetailsViewController(_ roomMemberDetailsViewController: MXKRoomMemberDetailsViewController!, startChatWithMemberId matrixId: String!, completion: (() -> Void)!) { completion() - self.errorPresenter.presentError(from: self, title: VectorL10n.spacesComingSoonTitle, message: VectorL10n.spacesComingSoonDetail, animated: true, handler: nil) + self.errorPresenter.presentError(from: self, title: VectorL10n.spacesComingSoonTitle, message: VectorL10n.spacesComingSoonDetail(AppInfo.current.displayName), animated: true, handler: nil) } override func roomMemberDetailsViewController(_ roomMemberDetailsViewController: MXKRoomMemberDetailsViewController!, placeVoipCallWithMemberId matrixId: String!, andVideo isVideoCall: Bool) { - self.errorPresenter.presentError(from: self, title: VectorL10n.spacesComingSoonTitle, message: VectorL10n.spacesComingSoonDetail, animated: true, handler: nil) + self.errorPresenter.presentError(from: self, title: VectorL10n.spacesComingSoonTitle, message: VectorL10n.spacesComingSoonDetail(AppInfo.current.displayName), animated: true, handler: nil) } } @@ -200,3 +191,10 @@ extension SpaceMemberListViewController: SpaceMemberListViewModelViewDelegate { self.render(viewState: viewSate) } } + +// MARK: - SpaceMemberListViewModelViewDelegate +extension SpaceMemberListViewController: AddItemHeaderViewDelegate { + func addItemHeaderView(_ headerView: AddItemHeaderView, didTapButton button: UIButton) { + self.viewModel.process(viewAction: .invite) + } +} diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift index c8d7ec1fd..91b555928 100644 --- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModel.swift @@ -57,6 +57,8 @@ final class SpaceMemberListViewModel: SpaceMemberListViewModelType { case .cancel: self.cancelOperations() self.coordinatorDelegate?.spaceMemberListViewModelDidCancel(self) + case .invite: + self.coordinatorDelegate?.spaceMemberListViewModelShowInvite(self) } } diff --git a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift index a07ceb55e..4b4a0e21c 100644 --- a/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift +++ b/Riot/Modules/Spaces/SpaceMembers/MemberList/SpaceMemberListViewModelType.swift @@ -25,6 +25,7 @@ protocol SpaceMemberListViewModelViewDelegate: AnyObject { protocol SpaceMemberListViewModelCoordinatorDelegate: AnyObject { func spaceMemberListViewModel(_ viewModel: SpaceMemberListViewModelType, didSelect member: MXRoomMember, from sourceView: UIView?) func spaceMemberListViewModelDidCancel(_ viewModel: SpaceMemberListViewModelType) + func spaceMemberListViewModelShowInvite(_ viewModel: SpaceMemberListViewModelType) } /// Protocol describing the view model used by `SpaceMemberListViewController` diff --git a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift index 9a0c44ac9..844d9c539 100644 --- a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift @@ -131,8 +131,51 @@ extension SpaceMembersCoordinator: SpaceMemberListCoordinatorDelegate { func spaceMemberListCoordinatorDidCancel(_ coordinator: SpaceMemberListCoordinatorType) { self.delegate?.spaceMembersCoordinatorDidCancel(self) } + + func spaceMemberListCoordinatorShowInvite(_ coordinator: SpaceMemberListCoordinatorType) { + guard let space = parameters.session.spaceService.getSpace(withId: parameters.spaceId), let spaceRoom = space.room else { + MXLog.error("[SpaceMembersCoordinator] spaceMemberListCoordinatorShowInvite: failed to find space with id \(parameters.spaceId)") + return + } + + spaceRoom.state { [weak self] roomState in + guard let self = self else { return } + + guard let powerLevels = roomState?.powerLevels, let userId = self.parameters.session.myUserId else { + MXLog.error("[SpaceMembersCoordinator] spaceMemberListCoordinatorShowInvite: failed to find powerLevels for room") + return + } + let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + + guard userPowerLevel >= powerLevels.invite else { + let alert = UIAlertController(title: VectorL10n.spacesInvitePeople, message: VectorL10n.spaceInviteNotEnoughPermission, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil)) + self.navigationRouter.present(alert, animated: true) + return + } + + let coordinator = ContactsPickerCoordinator(session: self.parameters.session, room: spaceRoom, initialSearchText: nil, actualParticipants: nil, invitedParticipants: nil, userParticipant: nil, navigationRouter: self.navigationRouter) + coordinator.delegate = self + coordinator.start() + self.childCoordinators.append(coordinator) + } + } } +// MARK: - ContactsPickerCoordinatorDelegate +extension SpaceMembersCoordinator: ContactsPickerCoordinatorDelegate { + func contactsPickerCoordinatorDidStartLoading(_ coordinator: ContactsPickerCoordinatorProtocol) { + } + + func contactsPickerCoordinatorDidEndLoading(_ coordinator: ContactsPickerCoordinatorProtocol) { + } + + func contactsPickerCoordinatorDidClose(_ coordinator: ContactsPickerCoordinatorProtocol) { + remove(childCoordinator: coordinator) + } +} + +// MARK: - SpaceMemberDetailCoordinatorDelegate extension SpaceMembersCoordinator: SpaceMemberDetailCoordinatorDelegate { func spaceMemberDetailCoordinator(_ coordinator: SpaceMemberDetailCoordinatorType, showRoomWithId roomId: String) { if !UIDevice.current.isPhone, let memberDetailCoordinator = self.memberDetailCoordinator { diff --git a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListItemViewData.swift b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListItemViewData.swift index 6f8b7378f..f9964201d 100644 --- a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListItemViewData.swift +++ b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListItemViewData.swift @@ -21,6 +21,9 @@ enum SpaceMenuListItemAction { case showAllRoomsInHomeSpace case exploreSpaceMembers case exploreSpaceRooms + case addRoom + case addSpace + case settings case leaveSpace } @@ -42,6 +45,8 @@ class SpaceMenuListItemViewData { let style: SpaceMenuListItemStyle let title: String? let icon: UIImage? + let isBeta: Bool + /// Any value related to the type of data (e.g. `Bool` for `boolean` style, `nil` for `normal` and `destructive` style) var value: Any? { didSet { @@ -50,11 +55,12 @@ class SpaceMenuListItemViewData { } weak var delegate: SpaceMenuListItemViewDataDelegate? - init(action: SpaceMenuListItemAction, style: SpaceMenuListItemStyle, title: String?, icon: UIImage?, value: Any?) { + init(action: SpaceMenuListItemAction, style: SpaceMenuListItemStyle, title: String?, icon: UIImage?, value: Any?, isBeta: Bool = false) { self.action = action self.style = style self.title = title self.icon = icon self.value = value + self.isBeta = isBeta } } diff --git a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.swift b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.swift index bc8243a9a..1ce0d2427 100644 --- a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.swift +++ b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.swift @@ -23,6 +23,8 @@ class SpaceMenuListViewCell: UITableViewCell, SpaceMenuCell, NibReusable { @IBOutlet private weak var iconView: UIImageView! @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var betaView: UIView! + @IBOutlet private weak var betaLabel: UILabel! @IBOutlet private weak var selectionView: UIView! // MARK: - Private @@ -64,6 +66,10 @@ class SpaceMenuListViewCell: UITableViewCell, SpaceMenuCell, NibReusable { self.titleLabel.textColor = theme.colors.primaryContent self.iconView.tintColor = theme.colors.secondaryContent } + + self.betaView.layer.masksToBounds = true + self.betaView.layer.cornerRadius = 4 + self.betaView.isHidden = !viewData.isBeta } func update(theme: Theme) { @@ -73,5 +79,8 @@ class SpaceMenuListViewCell: UITableViewCell, SpaceMenuCell, NibReusable { self.titleLabel.textColor = theme.colors.primaryContent self.titleLabel.font = theme.fonts.body self.selectionView.backgroundColor = theme.colors.separator + self.betaLabel.font = theme.fonts.caption2SB + self.betaLabel.textColor = theme.colors.secondaryContent + self.betaView.backgroundColor = theme.colors.quinaryContent } } diff --git a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.xib b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.xib index 0d71ef2c6..b6825d18e 100644 --- a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.xib +++ b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuListViewCell.xib @@ -25,12 +25,30 @@ -