diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml index ce75b7a57c..d64d579520 100644 --- a/.github/actions/prepare-build/action.yml +++ b/.github/actions/prepare-build/action.yml @@ -19,7 +19,7 @@ inputs: description: "Cache path" cabal_ver: required: false - default: 3.10.1.0 + default: 3.10.2.0 description: "GHC version to install" runs: using: "composite" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4814895c6..d9209b35b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,26 +99,43 @@ jobs: # ========================= build-linux: - name: "ubuntu-${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" needs: [maybe-release, variables] - runs-on: ubuntu-${{ matrix.os }} + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - os: 22.04 + os_underscore: 22_04 + arch: x86_64 + runner: "ubuntu-22.04" ghc: "8.10.7" should_run: ${{ !(github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} - os: 22.04 - ghc: ${{ needs.variables.outputs.GHC_VER }} - cli_asset_name: simplex-chat-ubuntu-22_04-x86-64 - desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb + os_underscore: 22_04 + arch: x86_64 + runner: "ubuntu-22.04" should_run: true + ghc: ${{ needs.variables.outputs.GHC_VER }} - os: 24.04 - ghc: ${{ needs.variables.outputs.GHC_VER }} - cli_asset_name: simplex-chat-ubuntu-24_04-x86-64 - desktop_asset_name: simplex-desktop-ubuntu-24_04-x86_64.deb + os_underscore: 24_04 + arch: x86_64 + runner: "ubuntu-24.04" should_run: true + ghc: ${{ needs.variables.outputs.GHC_VER }} + - os: 22.04 + os_underscore: 22_04 + arch: aarch64 + runner: "ubuntu-22.04-arm" + should_run: true + ghc: ${{ needs.variables.outputs.GHC_VER }} + - os: 24.04 + os_underscore: 24_04 + arch: aarch64 + runner: "ubuntu-24.04-arm" + should_run: true + ghc: ${{ needs.variables.outputs.GHC_VER }} steps: - name: Checkout Code if: matrix.should_run == true @@ -143,7 +160,7 @@ jobs: path: | ~/.cabal/store dist-newstyle - key: ubuntu-${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} + key: ubuntu-${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} - name: Set up Docker Buildx if: matrix.should_run == true @@ -215,17 +232,17 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | - docker cp builder:/out/simplex-chat ./${{ matrix.cli_asset_name }} - path="${{ github.workspace }}/${{ matrix.cli_asset_name }}" + docker cp builder:/out/simplex-chat ./simplex-chat-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }} + path="${{ github.workspace }}/simplex-chat-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}" echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(simplex-chat-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload CLI if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.linux_cli_prepare.outputs.bin_path }} - bin_name: ${{ matrix.cli_asset_name }} + bin_name: simplex-chat-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }} bin_hash: ${{ steps.linux_cli_prepare.outputs.bin_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -234,25 +251,23 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | - scripts/desktop/build-lib-linux.sh - cd apps/multiplatform - ./gradlew packageDeb + scripts/desktop/make-deb-linux.sh - name: Prepare Desktop id: linux_desktop_build if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | - path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/deb/simplex_*_amd64.deb ) + path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/deb/simplex_${{ matrix.arch }}.deb ) echo "package_path=$path" >> $GITHUB_OUTPUT - echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(simplex-desktop-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}.deb\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop uses: ./.github/actions/prepare-release if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true with: bin_path: ${{ steps.linux_desktop_build.outputs.package_path }} - bin_name: ${{ matrix.desktop_asset_name }} + bin_name: simplex-desktop-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}.deb bin_hash: ${{ steps.linux_desktop_build.outputs.package_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -270,14 +285,14 @@ jobs: run: | path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/*imple*.AppImage) echo "appimage_path=$path" >> $GITHUB_OUTPUT - echo "appimage_hash=$(echo SHA2-256\(simplex-desktop-x86_64.AppImage\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "appimage_hash=$(echo SHA2-256\(simplex-desktop-${{ matrix.arch }}.AppImage\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload AppImage if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.linux_appimage_build.outputs.appimage_path }} - bin_name: "simplex-desktop-x86_64.AppImage" + bin_name: "simplex-desktop-${{ matrix.arch }}.AppImage" bin_hash: ${{ steps.linux_appimage_build.outputs.appimage_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -290,7 +305,7 @@ jobs: sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal - name: Run tests - if: matrix.should_run == true + if: matrix.should_run == true && matrix.arch == 'x86_64' timeout-minutes: 120 shell: bash run: | diff --git a/.github/workflows/reproduce-schedule.yml b/.github/workflows/reproduce-schedule.yml index 7de44addc7..7d28d6f70c 100644 --- a/.github/workflows/reproduce-schedule.yml +++ b/.github/workflows/reproduce-schedule.yml @@ -25,7 +25,7 @@ jobs: - name: Execute reproduce script run: | - ${GITHUB_WORKSPACE}/scripts/reproduce-builds.sh "$TAG" + ${GITHUB_WORKSPACE}/scripts/simplex-chat-reproduce-builds.sh "$TAG" || : - name: Check if build has been reproduced env: @@ -33,7 +33,7 @@ jobs: user: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_USER }} pass: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_PASS }} run: | - if [ -f "${GITHUB_WORKSPACE}/$TAG/_sha256sums" ]; then + if [ -f "${GITHUB_WORKSPACE}/${TAG}-simplex-chat/_sha256sums" ]; then exit 0 else curl --proto '=https' --tlsv1.2 -sSf \ diff --git a/.gitignore b/.gitignore index 645b55ec9d..4560272980 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,4 @@ website/package-lock.json website/.cache website/test/stubs-layout-cache/_includes/*.js apps/android/app/release +apps/multiplatform/.kotlin/sessions diff --git a/Dockerfile.build b/Dockerfile.build index 76bb1127f2..3c841cfb25 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -5,7 +5,7 @@ FROM ubuntu:${TAG} AS build ### Build stage ARG GHC=9.6.3 -ARG CABAL=3.10.1.0 +ARG CABAL=3.10.2.0 ARG JAVA=17 ENV TZ=Etc/UTC \ @@ -16,6 +16,7 @@ RUN apt-get update && \ apt-get install -y curl \ libpq-dev \ git \ + strip-nondeterminism \ sqlite3 \ libsqlite3-dev \ build-essential \ diff --git a/README.md b/README.md index 554c6068d9..6ed444c7b5 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,9 @@ You must: Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment. -You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FiBkJE72asZX1NUZaYFIeKRVk6oVjb-iv%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAinqu3j74AMjODLoIRR487ZW6ysip_dlpD6Zxk18SPFY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22groupLinkId%22%3A%223wAFGCLygQHR5AwynZOHlQ%3D%3D%22%7D) +You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM) -There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FvYCRjIflKNMGYlfTkuHe4B40qSlQ0439%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAHNdcqNbzXZhyMoSBjT2R0-Eb1EPaLyUg3KZjn-kmM1w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22PD20tcXjw7IpkkMCfR6HLA%3D%3D%22%7D) for developers who build on SimpleX platform: +There is also a group [#simplex-devs](https://smp6.simplex.im/g#Drx3efC-n418AuSpzTspw9SER0iJwrQTmKBafQHwkKM) for developers who build on SimpleX platform: - chat bots and automations - integrations with other apps @@ -83,7 +83,7 @@ There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=s There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users: -[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FmfiivxDKWFuowXrQOp11jsY8TuP__rBL%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAiz3pKNwvKudckFYMUfgoT0s96B0jfZ7ALHAu7rtE9HQ%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jZeJpXGrRXQJU_-MSJ_v2A%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FJ5ES83pJimY2BRklS8fvy_iQwIU37xra%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA0F0STP6UqN_12_k2cjjTrIjFgBGeWhOAmbY1qlk3pnM%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22VmUU0fqmYdCRmVCyvStvHA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FxCHBE_6PBRMqNEpm4UQDHXb9cz-mN7dd%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAetqlcM7zTCRw-iatnwCrvpJSto7lq5Yv6AsBMWv7GSM%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22foO5Xw4hhjOa_x7zET7otw%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FVXQTB0J2lLjYkgjWByhl6-1qmb5fgZHh%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAI6JaEWezfSwvcoTEkk6au-gkjrXR2ew2OqZYMYBvayk%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22ORH9OEe8Duissh-hslfeVg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FqpHu0psOUdYfc11yQCzSyq5JhijrBzZT%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEACZ_7fbwlM45wl6cGif8cY47oPQ_AMdP0ATqOYLA6zHY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%229uRQRTir3ealdcSfB0zsrw%3D%3D%22%7D) (Italian-speaking). +[\#SimpleX-DE](https://smp6.simplex.im/g#V6tQ-lJqsdgJJdJiLPtP326oQFKHvwinIbgruZ9K2oU) (German-speaking), [\#SimpleX-ES](https://smp5.simplex.im/g#xJ5kwDLq2305O5FmpUzvgRIXXAcAJ9S5BItCd2Wmloc) (Spanish-speaking), [\#SimpleX-FR](https://smp6.simplex.im/g#cVOpB0CKd6hEf2aWQ6sJ22E2DVgQLtdHoiSdKxXeKqk) (French-speaking), [\#SimpleX-RU](https://smp5.simplex.im/g#vwXRdfG5SgtaG6aVcITiUGd--Ux0rY1IuH4QXYxlq3U) (Russian-speaking), [\#SimpleX-IT](https://smp5.simplex.im/g#BtRcjsl29ULFNBSE2OPhp1UwZfW7PW9gUYFQTKHdjqU) (Italian-speaking). You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code. @@ -114,9 +114,8 @@ Read about the app features and settings in the new [User guide](./docs/guide/RE We would love to have you join the development! You can help us with: -- [share the color theme](./docs/THEMES.md) you use in Android app! -- writing a tutorial or recipes about hosting servers, chat bot automations, etc. -- contributing to SimpleX Chat knowledge-base. +- [develop a chat bot](#develop-a-chat-bot) for SimpleX Chat! +- writing a tutorial or recipes about hosting servers, chat bots, etc. - developing features - please connect to us via chat so we can help you get started. ## Help translating SimpleX Chat @@ -194,6 +193,7 @@ SimpleX Chat founder - [SimpleX Platform design](#simplex-platform-design) - [Privacy and security: technical details and limitations](#privacy-and-security-technical-details-and-limitations) - [For developers](#for-developers) +- [Develop a chat bot](#develop-a-chat-bot) - [Roadmap](#roadmap) - [Disclaimers, Security contact, License](#disclaimers) @@ -235,6 +235,10 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Jul 29, 2025 SimpleX Chat v6.4.1: welcome your contacts, review members to protect groups, and more.](./blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.md) + +[Jul 3, 2025 SimpleX network: new experience of connecting with people — available in SimpleX Chat v6.4-beta.4](./blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.md) + [Mar 8, 2025. SimpleX Chat v6.3: new user experience and safety in public groups](./blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md) [Jan 14, 2025. SimpleX network: large groups and privacy-preserving content moderation](./blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md) @@ -323,15 +327,23 @@ We plan to add: You can: +- [create chat bots and services](#develop-a-chat-bot). +- run [simplex-chat terminal CLI](./docs/CLI.md) to execute individual chat commands, e.g. to send messages as part of shell script execution. - use SimpleX Chat library to integrate chat functionality into your mobile apps. - create chat bots and services in Haskell - see [simple](./apps/simplex-bot/) and more [advanced chat bot example](./apps/simplex-bot-advanced/). -- create chat bots and services in any language running SimpleX Chat terminal CLI as a local WebSocket server. See [TypeScript SimpleX Chat client](./packages/simplex-chat-client/) and [JavaScript chat bot example](./packages/simplex-chat-client/typescript/examples/squaring-bot.js). -- run [simplex-chat terminal CLI](./docs/CLI.md) to execute individual chat commands, e.g. to send messages as part of shell script execution. If you are considering developing with SimpleX platform please get in touch for any advice and support. Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F6eHqy7uAbZPOcA6qBtrQgQquVlt4Ll91%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAqV_pg3FF00L98aCXp4D3bOs4Sxv_UmSd-gb0juVoQVs%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22XonlixcHBIb2ijCehbZoiw%3D%3D%22%7D) group to ask any questions and share your success stories. +## Develop a chat bot + +You can create a chat bot or any chat-based service in any language running SimpleX Chat terminal CLI as a local WebSocket server. + +See [our new bot API reference](./bots/README.md). Most of it is automatically generated from core library types, so it stays up to date. + +Also see [TypeScript SimpleX Chat client](./packages/simplex-chat-client/) and [JavaScript chat bot example](./packages/simplex-chat-client/typescript/examples/squaring-bot.js). + ## Roadmap - ✅ Easy to deploy SimpleX server with in-memory message storage, without any dependencies. @@ -430,7 +442,7 @@ Please do NOT report security vulnerabilities via GitHub issues. ## License -[AGPL v3](./LICENSE) +This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, and associated branding materials are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) file. [iOS app](https://apps.apple.com/us/app/simplex-chat/id1605771084)   diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 2ad8d546f2..7adf7a0435 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -45,21 +45,10 @@ struct ContentView: View { @State private var showChooseLAMode = false @State private var showSetPasscode = false @State private var waitingForOrPassedAuth = true - @State private var chatListActionSheet: ChatListActionSheet? = nil @State private var chatListUserPickerSheet: UserPickerSheet? = nil private let callTopPadding: CGFloat = 40 - private enum ChatListActionSheet: Identifiable { - case planAndConnectSheet(sheet: PlanAndConnectActionSheet) - - var id: String { - switch self { - case let .planAndConnectSheet(sheet): return sheet.id - } - } - } - private var accessAuthenticated: Bool { chatModel.contentViewAccessAuthenticated || contentAccessAuthenticationExtended } @@ -181,11 +170,6 @@ struct ContentView: View { if case .onboardingComplete = step, chatModel.currentUser != nil { mainView() - .actionSheet(item: $chatListActionSheet) { sheet in - switch sheet { - case let .planAndConnectSheet(sheet): return planAndConnectActionSheet(sheet, dismiss: false) - } - } } else { OnboardingView(onboarding: step) } @@ -446,21 +430,27 @@ struct ContentView: View { let m = ChatModel.shared if let url = m.appOpenUrl { m.appOpenUrl = nil - dismissAllSheets() { - var path = url.path - if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") { - path.removeFirst() - let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") - planAndConnect( - link, - showAlert: showPlanAndConnectAlert, - showActionSheet: { chatListActionSheet = .planAndConnectSheet(sheet: $0) }, - dismiss: false, - incognito: nil - ) - } else { - AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid"))) - } + connectViaUrl_(url) + } else if let url = m.appOpenUrlLater, AppChatState.shared.value == .active, scenePhase == .active { + // correcting branch in case .onChange(of: scenePhase) in SimpleXApp doesn't trigger and transfer appOpenUrlLater into appOpenUrl + m.appOpenUrlLater = nil + connectViaUrl_(url) + } + } + + func connectViaUrl_(_ url: URL) { + dismissAllSheets() { + var path = url.path + if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") { + path.removeFirst() + let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") + planAndConnect( + link, + theme: theme, + dismiss: false + ) + } else { + AlertManager.shared.showAlert(Alert(title: Text("Error: URL is invalid"))) } } } @@ -479,10 +469,6 @@ struct ContentView: View { } } } - - private func showPlanAndConnectAlert(_ alert: PlanAndConnectAlert) { - AlertManager.shared.showAlert(planAndConnectAlert(alert, dismiss: false)) - } } final class AlertManager: ObservableObject { diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 3bf4cb7b56..35b9bf033e 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -18,6 +18,7 @@ enum ChatCommand: ChatCmdProtocol { case setAllContactReceipts(enable: Bool) case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) case apiSetUserGroupReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) + case apiSetUserAutoAcceptMemberContacts(userId: Int64, enable: Bool) case apiHideUser(userId: Int64, viewPwd: String) case apiUnhideUser(userId: Int64, viewPwd: String) case apiMuteUser(userId: Int64) @@ -39,9 +40,9 @@ enum ChatCommand: ChatCmdProtocol { case apiGetSettings(settings: AppSettings) case apiGetChatTags(userId: Int64) case apiGetChats(userId: Int64) - case apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String) - case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) - case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + case apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag?, pagination: ChatPagination, search: String) + case apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64) + case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) case apiCreateChatTag(tag: ChatTagData) case apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64]) case apiDeleteChatTag(tagId: Int64) @@ -49,15 +50,15 @@ enum ChatCommand: ChatCmdProtocol { case apiReorderChatTags(tagIds: [Int64]) case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) case apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String) - case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool) - case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) + case apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool) + case apiDeleteChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64], mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) case apiArchiveReceivedReports(groupId: Int64) case apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode) - case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) + case apiChatItemReaction(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, add: Bool, reaction: MsgReaction) case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction) - case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) - case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) + case apiPlanForwardChatItems(fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64]) + case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64], ttl: Int?) case apiGetNtfToken case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiVerifyToken(token: DeviceToken, nonce: String, code: String) @@ -68,18 +69,22 @@ enum ChatCommand: ChatCmdProtocol { case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) case apiJoinGroup(groupId: Int64) + case apiAcceptMember(groupId: Int64, groupMemberId: Int64, memberRole: GroupMemberRole) + case apiDeleteMemberSupportChat(groupId: Int64, groupMemberId: Int64) case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole) case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool) case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool) case apiLeaveGroup(groupId: Int64) case apiListMembers(groupId: Int64) case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) - case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole, short: Bool) + case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole) case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) case apiDeleteGroupLink(groupId: Int64) case apiGetGroupLink(groupId: Int64) + case apiAddGroupShortLink(groupId: Int64) case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) + case apiAcceptMemberContact(contactId: Int64) case apiTestProtoServer(userId: Int64, server: String) case apiGetServerOperators case apiSetServerOperators(operators: [ServerOperator]) @@ -113,10 +118,16 @@ enum ChatCommand: ChatCmdProtocol { case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) case apiVerifyContact(contactId: Int64, connectionCode: String?) case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) - case apiAddContact(userId: Int64, short: Bool, incognito: Bool) + case apiAddContact(userId: Int64, incognito: Bool) case apiSetConnectionIncognito(connId: Int64, incognito: Bool) case apiChangeConnectionUser(connId: Int64, userId: Int64) case apiConnectPlan(userId: Int64, connLink: String) + case apiPrepareContact(userId: Int64, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData) + case apiPrepareGroup(userId: Int64, connLink: CreatedConnLink, groupShortLinkData: GroupShortLinkData) + case apiChangePreparedContactUser(contactId: Int64, newUserId: Int64) + case apiChangePreparedGroupUser(groupId: Int64, newUserId: Int64) + case apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) + case apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink) case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) @@ -129,11 +140,12 @@ enum ChatCommand: ChatCmdProtocol { case apiSetConnectionAlias(connId: Int64, localAlias: String) case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?) - case apiCreateMyAddress(userId: Int64, short: Bool) + case apiCreateMyAddress(userId: Int64) case apiDeleteMyAddress(userId: Int64) case apiShowMyAddress(userId: Int64) + case apiAddMyAddressShortLink(userId: Int64) case apiSetProfileAddress(userId: Int64, on: Bool) - case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) + case apiSetAddressSettings(userId: Int64, addressSettings: AddressSettings) case apiAcceptContact(incognito: Bool, contactReqId: Int64) case apiRejectContact(contactReqId: Int64) // WebRTC calls @@ -147,8 +159,8 @@ enum ChatCommand: ChatCmdProtocol { case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) // WebRTC calls / case apiGetNetworkStatuses - case apiChatRead(type: ChatType, id: Int64) - case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) + case apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?) + case apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) @@ -188,6 +200,8 @@ enum ChatCommand: ChatCmdProtocol { case let .apiSetUserGroupReceipts(userId, userMsgReceiptSettings): let umrs = userMsgReceiptSettings return "/_set receipts groups \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" + case let .apiSetUserAutoAcceptMemberContacts(userId, enable): + return "/_set accept member contacts \(userId) \(onOff(enable))" case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))" case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))" case let .apiMuteUser(userId): return "/_mute user \(userId)" @@ -209,15 +223,16 @@ enum ChatCommand: ChatCmdProtocol { case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))" case let .apiGetChatTags(userId): return "/_get tags \(userId)" case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" - case let .apiGetChat(chatId, pagination, search): return "/_get chat \(chatId) \(pagination.cmdString)" + - (search == "" ? "" : " search=\(search)") - case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" - case let .apiSendMessages(type, id, live, ttl, composedMessages): + case let .apiGetChat(chatId, scope, contentTag, pagination, search): + let tag = contentTag != nil ? " content=\(contentTag!.rawValue)" : "" + return "/_get chat \(chatId)\(scopeRef(scope: scope))\(tag) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)") + case let .apiGetChatItemInfo(type, id, scope, itemId): return "/_get item info \(ref(type, id, scope: scope)) \(itemId)" + case let .apiSendMessages(type, id, scope, live, ttl, composedMessages): let msgs = encodeJSON(composedMessages) let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + return "/_send \(ref(type, id, scope: scope)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" case let .apiCreateChatTag(tag): return "/_create tag \(encodeJSON(tag))" - case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id)) \(tagIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id, scope: nil)) \(tagIds.map({ "\($0)" }).joined(separator: ","))" case let .apiDeleteChatTag(tagId): return "/_delete tag \(tagId)" case let .apiUpdateChatTag(tagId, tagData): return "/_update tag \(tagId) \(encodeJSON(tagData))" case let .apiReorderChatTags(tagIds): return "/_reorder tags \(tagIds.map({ "\($0)" }).joined(separator: ","))" @@ -226,17 +241,17 @@ enum ChatCommand: ChatCmdProtocol { return "/_create *\(noteFolderId) json \(msgs)" case let .apiReportMessage(groupId, chatItemId, reportReason, reportText): return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)" - case let .apiUpdateChatItem(type, id, itemId, um, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(um.cmdString)" - case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" + case let .apiUpdateChatItem(type, id, scope, itemId, um, live): return "/_update item \(ref(type, id, scope: scope)) \(itemId) live=\(onOff(live)) \(um.cmdString)" + case let .apiDeleteChatItem(type, id, scope, itemIds, mode): return "/_delete item \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiArchiveReceivedReports(groupId): return "/_archive reports #\(groupId)" case let .apiDeleteReceivedReports(groupId, itemIds, mode): return "/_delete reports #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" - case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" + case let .apiChatItemReaction(type, id, scope, itemId, add, reaction): return "/_reaction \(ref(type, id, scope: scope)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))" - case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" - case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): + case let .apiPlanForwardChatItems(type, id, scope, itemIds): return "/_forward plan \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiForwardChatItems(toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl): let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" + return "/_forward \(ref(toChatType, toChatId, scope: toScope)) \(ref(fromChatType, fromChatId, scope: fromScope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" case .apiGetNtfToken: return "/_ntf get " case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" @@ -247,18 +262,22 @@ enum ChatCommand: ChatCmdProtocol { case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))" case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)" case let .apiJoinGroup(groupId): return "/_join #\(groupId)" + case let .apiAcceptMember(groupId, groupMemberId, memberRole): return "/_accept member #\(groupId) \(groupMemberId) \(memberRole.rawValue)" + case let .apiDeleteMemberSupportChat(groupId, groupMemberId): return "/_delete member chat #\(groupId) \(groupMemberId)" case let .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)" case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))" case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))" case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" case let .apiListMembers(groupId): return "/_members #\(groupId)" case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" - case let .apiCreateGroupLink(groupId, memberRole, short): return "/_create link #\(groupId) \(memberRole) short=\(onOff(short))" + case let .apiCreateGroupLink(groupId, memberRole): return "/_create link #\(groupId) \(memberRole)" case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" + case let .apiAddGroupShortLink(groupId): return "/_short link #\(groupId)" case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" + case let .apiAcceptMemberContact(contactId): return "/_accept member contact @\(contactId)" case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" case .apiGetServerOperators: return "/_operators" case let .apiSetServerOperators(operators): return "/_operators \(encodeJSON(operators))" @@ -270,13 +289,13 @@ enum ChatCommand: ChatCmdProtocol { case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))" case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" - case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id)) \(chatItemTTLStr(seconds: seconds))" + case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id, scope: nil)) \(chatItemTTLStr(seconds: seconds))" case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" case .apiGetNetworkConfig: return "/network" case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))" case .reconnectAllServers: return "/reconnect" case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)" - case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))" + case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id, scope: nil)) \(encodeJSON(chatSettings))" case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))" case let .apiContactInfo(contactId): return "/_info @\(contactId)" case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)" @@ -302,14 +321,20 @@ enum ChatCommand: ChatCmdProtocol { case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)" case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)" case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" - case let .apiAddContact(userId, short, incognito): return "/_connect \(userId) short=\(onOff(short)) incognito=\(onOff(incognito))" + case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))" case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)" + case let .apiPrepareContact(userId, connLink, contactShortLinkData): return "/_prepare contact \(userId) \(connLink.connFullLink) \(connLink.connShortLink ?? "") \(encodeJSON(contactShortLinkData))" + case let .apiPrepareGroup(userId, connLink, groupShortLinkData): return "/_prepare group \(userId) \(connLink.connFullLink) \(connLink.connShortLink ?? "") \(encodeJSON(groupShortLinkData))" + case let .apiChangePreparedContactUser(contactId, newUserId): return "/_set contact user @\(contactId) \(newUserId)" + case let .apiChangePreparedGroupUser(groupId, newUserId): return "/_set group user #\(groupId) \(newUserId)" + case let .apiConnectPreparedContact(contactId, incognito, mc): return "/_connect contact @\(contactId) incognito=\(onOff(incognito))\(maybeContent(mc))" + case let .apiConnectPreparedGroup(groupId, incognito, mc): return "/_connect group #\(groupId) incognito=\(onOff(incognito))\(maybeContent(mc))" case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")" case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" - case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)" - case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" + case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id, scope: nil)) \(chatDeleteMode.cmdString)" + case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id, scope: nil))" case let .apiListContacts(userId): return "/_contacts \(userId)" case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))" @@ -318,11 +343,12 @@ enum ChatCommand: ChatCmdProtocol { case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")" case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")" - case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" + case let .apiCreateMyAddress(userId): return "/_address \(userId)" case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiAddMyAddressShortLink(userId): return "/_short_link_address \(userId)" case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" - case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" + case let .apiSetAddressSettings(userId, addressSettings): return "/_address_settings \(userId) \(encodeJSON(addressSettings))" case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))" @@ -334,9 +360,9 @@ enum ChatCommand: ChatCmdProtocol { case .apiGetCallInvitations: return "/_call get" case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" case .apiGetNetworkStatuses: return "/_network_statuses" - case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))" - case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" - case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" + case let .apiChatRead(type, id, scope): return "/_read chat \(ref(type, id, scope: scope))" + case let .apiChatItemsRead(type, id, scope, itemIds): return "/_read chat items \(ref(type, id, scope: scope)) \(joinedIds(itemIds))" + case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id, scope: nil)) \(onOff(unreadChat))" case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" case let .cancelFile(fileId): return "/fcancel \(fileId)" @@ -370,6 +396,7 @@ enum ChatCommand: ChatCmdProtocol { case .setAllContactReceipts: return "setAllContactReceipts" case .apiSetUserContactReceipts: return "apiSetUserContactReceipts" case .apiSetUserGroupReceipts: return "apiSetUserGroupReceipts" + case .apiSetUserAutoAcceptMemberContacts: return "apiSetUserAutoAcceptMemberContacts" case .apiHideUser: return "apiHideUser" case .apiUnhideUser: return "apiUnhideUser" case .apiMuteUser: return "apiMuteUser" @@ -421,6 +448,8 @@ enum ChatCommand: ChatCmdProtocol { case .apiNewGroup: return "apiNewGroup" case .apiAddMember: return "apiAddMember" case .apiJoinGroup: return "apiJoinGroup" + case .apiAcceptMember: return "apiAcceptMember" + case .apiDeleteMemberSupportChat: return "apiDeleteMemberSupportChat" case .apiMembersRole: return "apiMembersRole" case .apiBlockMembersForAll: return "apiBlockMembersForAll" case .apiRemoveMembers: return "apiRemoveMembers" @@ -431,8 +460,10 @@ enum ChatCommand: ChatCmdProtocol { case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" case .apiDeleteGroupLink: return "apiDeleteGroupLink" case .apiGetGroupLink: return "apiGetGroupLink" + case .apiAddGroupShortLink: return "apiAddGroupShortLink" case .apiCreateMemberContact: return "apiCreateMemberContact" case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" + case .apiAcceptMemberContact: return "apiAcceptMemberContact" case .apiTestProtoServer: return "apiTestProtoServer" case .apiGetServerOperators: return "apiGetServerOperators" case .apiSetServerOperators: return "apiSetServerOperators" @@ -470,6 +501,12 @@ enum ChatCommand: ChatCmdProtocol { case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" case .apiChangeConnectionUser: return "apiChangeConnectionUser" case .apiConnectPlan: return "apiConnectPlan" + case .apiPrepareContact: return "apiPrepareContact" + case .apiPrepareGroup: return "apiPrepareGroup" + case .apiChangePreparedContactUser: return "apiChangePreparedContactUser" + case .apiChangePreparedGroupUser: return "apiChangePreparedGroupUser" + case .apiConnectPreparedContact: return "apiConnectPreparedContact" + case .apiConnectPreparedGroup: return "apiConnectPreparedGroup" case .apiConnect: return "apiConnect" case .apiDeleteChat: return "apiDeleteChat" case .apiClearChat: return "apiClearChat" @@ -484,8 +521,9 @@ enum ChatCommand: ChatCmdProtocol { case .apiCreateMyAddress: return "apiCreateMyAddress" case .apiDeleteMyAddress: return "apiDeleteMyAddress" case .apiShowMyAddress: return "apiShowMyAddress" + case .apiAddMyAddressShortLink: return "apiAddMyAddressShortLink" case .apiSetProfileAddress: return "apiSetProfileAddress" - case .apiAddressAutoAccept: return "apiAddressAutoAccept" + case .apiSetAddressSettings: return "apiSetAddressSettings" case .apiAcceptContact: return "apiAcceptContact" case .apiRejectContact: return "apiRejectContact" case .apiSendCallInvitation: return "apiSendCallInvitation" @@ -523,8 +561,22 @@ enum ChatCommand: ChatCmdProtocol { } } - func ref(_ type: ChatType, _ id: Int64) -> String { - "\(type.rawValue)\(id)" + func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String { + "\(type.rawValue)\(id)\(scopeRef(scope: scope))" + } + + func scopeRef(scope: GroupChatScope?) -> String { + switch (scope) { + case .none: "" + case let .memberSupport(groupMemberId_): + if let groupMemberId = groupMemberId_ { + "(_support:\(groupMemberId))" + } else { + "(_support)" + } + case .reports: + "(reports, prohibited)" // can't use surrogate Reports scope + } } func joinedIds(_ ids: [Int64]) -> String { @@ -578,6 +630,16 @@ enum ChatCommand: ChatCmdProtocol { private func maybePwd(_ pwd: String?) -> String { pwd == "" || pwd == nil ? "" : " " + encodeJSON(pwd) } + + private func maybeContent(_ mc: MsgContent?) -> String { + if case let .text(s) = mc, s.isEmpty { + "" + } else if let mc { + " " + mc.cmdString + } else { + "" + } + } } // ChatResponse is split to three enums to reduce stack size used when parsing it, parsing large enums is very inefficient. @@ -645,7 +707,7 @@ enum ChatResponse0: Decodable, ChatAPIResult { case .tagsUpdated: "tagsUpdated" } } - + var details: String { switch self { case let .activeUser(user): return String(describing: user) @@ -704,13 +766,19 @@ enum ChatResponse1: Decodable, ChatAPIResult { case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan) + case newPreparedChat(user: UserRef, chat: ChatData) + case contactUserChanged(user: UserRef, fromContact: Contact, newUser: UserRef, toContact: Contact) + case groupUserChanged(user: UserRef, fromGroup: GroupInfo, newUser: UserRef, toGroup: GroupInfo) case sentConfirmation(user: UserRef, connection: PendingContactConnection) case sentInvitation(user: UserRef, connection: PendingContactConnection) + case startedConnectionToContact(user: UserRef, contact: Contact) + case startedConnectionToGroup(user: UserRef, groupInfo: GroupInfo) case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) case contactAlreadyExists(user: UserRef, contact: Contact) case contactDeleted(user: UserRef, contact: Contact) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) + case itemsReadForChat(user: UserRef, chatInfo: ChatInfo) case chatCleared(user: UserRef, chatInfo: ChatInfo) case userProfileNoChange(user: User) case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) @@ -724,7 +792,7 @@ enum ChatResponse1: Decodable, ChatAPIResult { case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink) case userContactLinkDeleted(user: User) case acceptingContactRequest(user: UserRef, contact: Contact) - case contactRequestRejected(user: UserRef) + case contactRequestRejected(user: UserRef, contactRequest: UserContactRequest, contact_: Contact?) case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) case newChatItems(user: UserRef, chatItems: [AChatItem]) case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) @@ -742,13 +810,19 @@ enum ChatResponse1: Decodable, ChatAPIResult { case .connectionIncognitoUpdated: "connectionIncognitoUpdated" case .connectionUserChanged: "connectionUserChanged" case .connectionPlan: "connectionPlan" + case .newPreparedChat: "newPreparedChat" + case .contactUserChanged: "contactUserChanged" + case .groupUserChanged: "groupUserChanged" case .sentConfirmation: "sentConfirmation" case .sentInvitation: "sentInvitation" + case .startedConnectionToContact: "startedConnectionToContact" + case .startedConnectionToGroup: "startedConnectionToGroup" case .sentInvitationToContact: "sentInvitationToContact" case .contactAlreadyExists: "contactAlreadyExists" case .contactDeleted: "contactDeleted" case .contactConnectionDeleted: "contactConnectionDeleted" case .groupDeletedUser: "groupDeletedUser" + case .itemsReadForChat: "itemsReadForChat" case .chatCleared: "chatCleared" case .userProfileNoChange: "userProfileNoChange" case .userProfileUpdated: "userProfileUpdated" @@ -775,12 +849,13 @@ enum ChatResponse1: Decodable, ChatAPIResult { case .contactsList: "contactsList" } } - + var details: String { switch self { case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .itemsReadForChat(u, chatInfo): return withUser(u, String(describing: chatInfo)) case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) case .userProfileNoChange: return noDetails case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) @@ -789,12 +864,12 @@ enum ChatResponse1: Decodable, ChatAPIResult { case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") - case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLink(u, contactLink): return withUser(u, String(describing: contactLink)) + case let .userContactLinkUpdated(u, contactLink): return withUser(u, String(describing: contactLink)) case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) case .userContactLinkDeleted: return noDetails case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact)) - case .contactRequestRejected: return noDetails + case let .contactRequestRejected(u, contactRequest, contact_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\ncontact_: \(String(describing: contact_))") case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") @@ -815,8 +890,13 @@ enum ChatResponse1: Decodable, ChatAPIResult { case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") + case let .newPreparedChat(u, chat): return withUser(u, String(describing: chat)) + case let .contactUserChanged(u, fromContact, newUser, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\nnewUserId: \(String(describing: newUser.userId))\ntoContact: \(String(describing: toContact))") + case let .groupUserChanged(u, fromGroup, newUser, toGroup): return withUser(u, "fromGroup: \(String(describing: fromGroup))\nnewUserId: \(String(describing: newUser.userId))\ntoGroup: \(String(describing: toGroup))") case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) + case let .startedConnectionToContact(u, contact): return withUser(u, String(describing: contact)) + case let .startedConnectionToGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) } @@ -831,14 +911,18 @@ enum ChatResponse2: Decodable, ChatAPIResult { case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) case leftMemberUser(user: UserRef, groupInfo: GroupInfo) case groupMembers(user: UserRef, group: SimpleXChat.Group) + case memberAccepted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case memberSupportChatRead(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case memberSupportChatDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) case groupUpdated(user: UserRef, toGroup: GroupInfo) - case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) - case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) + case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink) + case groupLink(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink) case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + case memberContactAccepted(user: UserRef, contact: Contact) // receiving file responses case rcvFileAccepted(user: UserRef, chatItem: AChatItem) case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) @@ -848,8 +932,6 @@ enum ChatResponse2: Decodable, ChatAPIResult { // sending file responses case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload - case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used - case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) // call invitations case callInvitations(callInvitations: [RcvCallInvitation]) // notifications @@ -879,6 +961,9 @@ enum ChatResponse2: Decodable, ChatAPIResult { case .userDeletedMembers: "userDeletedMembers" case .leftMemberUser: "leftMemberUser" case .groupMembers: "groupMembers" + case .memberAccepted: "memberAccepted" + case .memberSupportChatRead: "memberSupportChatRead" + case .memberSupportChatDeleted: "memberSupportChatDeleted" case .membersRoleUser: "membersRoleUser" case .membersBlockedForAllUser: "membersBlockedForAllUser" case .groupUpdated: "groupUpdated" @@ -887,6 +972,7 @@ enum ChatResponse2: Decodable, ChatAPIResult { case .groupLinkDeleted: "groupLinkDeleted" case .newMemberContact: "newMemberContact" case .newMemberContactSentInv: "newMemberContactSentInv" + case .memberContactAccepted: "memberContactAccepted" case .rcvFileAccepted: "rcvFileAccepted" case .rcvFileAcceptedSndCancelled: "rcvFileAcceptedSndCancelled" case .standaloneFileInfo: "standaloneFileInfo" @@ -894,8 +980,6 @@ enum ChatResponse2: Decodable, ChatAPIResult { case .rcvFileCancelled: "rcvFileCancelled" case .sndFileCancelled: "sndFileCancelled" case .sndStandaloneFileCreated: "sndStandaloneFileCreated" - case .sndFileStartXFTP: "sndFileStartXFTP" - case .sndFileCancelledXFTP: "sndFileCancelledXFTP" case .callInvitations: "callInvitations" case .ntfTokenStatus: "ntfTokenStatus" case .ntfToken: "ntfToken" @@ -923,14 +1007,18 @@ enum ChatResponse2: Decodable, ChatAPIResult { case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .groupMembers(u, group): return withUser(u, String(describing: group)) + case let .memberAccepted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .memberSupportChatRead(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .memberSupportChatDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") - case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLinkCreated(u, groupInfo, groupLink): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)") + case let .groupLink(u, groupInfo, groupLink): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)") case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .memberContactAccepted(u, contact): return withUser(u, "contact: \(contact)") case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) case .rcvFileAcceptedSndCancelled: return noDetails case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) @@ -938,8 +1026,6 @@ enum ChatResponse2: Decodable, ChatAPIResult { case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) case .sndStandaloneFileCreated: return noDetails - case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .callInvitations(invs): return String(describing: invs) case let .ntfTokenStatus(status): return String(describing: status) case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" @@ -970,12 +1056,13 @@ enum ChatEvent: Decodable, ChatAPIResult { case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) case contactConnecting(user: UserRef, contact: Contact) case contactSndReady(user: UserRef, contact: Contact) - case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest, chat_: ChatData?) case contactUpdated(user: UserRef, toContact: Contact) case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember) case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) case networkStatus(networkStatus: NetworkStatus, connections: [String]) case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) + case chatInfoUpdated(user: UserRef, chatInfo: ChatInfo) case newChatItems(user: UserRef, chatItems: [AChatItem]) case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) @@ -988,6 +1075,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember) case businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) + case memberAcceptedByOther(user: UserRef, groupInfo: GroupInfo, acceptingMember: GroupMember, member: GroupMember) case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) @@ -1035,7 +1123,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) // pq case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) - + var responseType: String { switch self { case .chatSuspended: "chatSuspended" @@ -1053,6 +1141,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case .contactsMerged: "contactsMerged" case .networkStatus: "networkStatus" case .networkStatuses: "networkStatuses" + case .chatInfoUpdated: "chatInfoUpdated" case .newChatItems: "newChatItems" case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" case .chatItemUpdated: "chatItemUpdated" @@ -1064,6 +1153,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case .groupLinkConnecting: "groupLinkConnecting" case .businessLinkConnecting: "businessLinkConnecting" case .joinedGroupMemberConnecting: "joinedGroupMemberConnecting" + case .memberAcceptedByOther: "memberAcceptedByOther" case .memberRole: "memberRole" case .memberBlockedForAll: "memberBlockedForAll" case .deletedMemberUser: "deletedMemberUser" @@ -1107,7 +1197,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case .contactPQEnabled: "contactPQEnabled" } } - + var details: String { switch self { case .chatSuspended: return noDetails @@ -1119,12 +1209,13 @@ enum ChatEvent: Decodable, ChatAPIResult { case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) - case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + case let .receivedContactRequest(u, contactRequest, chat_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\nchat_: \(String(describing: chat_))") case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact)) case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)") case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .chatInfoUpdated(u, chatInfo): return withUser(u, String(describing: chatInfo)) case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) @@ -1144,6 +1235,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case let .groupLinkConnecting(u, groupInfo, hostMember): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))") case let .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") + case let .memberAcceptedByOther(u, groupInfo, acceptingMember, member): return withUser(u, "groupInfo: \(groupInfo)\nacceptingMember: \(acceptingMember)\nmember: \(member)") case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") @@ -1186,7 +1278,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") } - } + } } struct NewUser: Encodable { @@ -1224,14 +1316,14 @@ enum ConnectionPlan: Decodable, Hashable { } enum InvitationLinkPlan: Decodable, Hashable { - case ok + case ok(contactSLinkData_: ContactShortLinkData?) case ownLink case connecting(contact_: Contact?) case known(contact: Contact) } enum ContactAddressPlan: Decodable, Hashable { - case ok + case ok(contactSLinkData_: ContactShortLinkData?) case ownLink case connectingConfirmReconnect case connectingProhibit(contact: Contact) @@ -1240,7 +1332,7 @@ enum ContactAddressPlan: Decodable, Hashable { } enum GroupLinkPlan: Decodable, Hashable { - case ok + case ok(groupSLinkData_: GroupShortLinkData?) case ownLink(groupInfo: GroupInfo) case connectingConfirmReconnect case connectingProhibit(groupInfo_: GroupInfo?) @@ -1255,7 +1347,7 @@ struct ChatTagData: Encodable { struct UpdatedMessage: Encodable { var msgContent: MsgContent var mentions: [String: Int64] - + var cmdString: String { "json \(encodeJSON(self))" } @@ -1331,34 +1423,56 @@ struct UserMsgReceiptSettings: Codable { var clearOverrides: Bool } +protocol SimplexAddress { + var connLinkContact: CreatedConnLink { get } + var shortLinkDataSet: Bool { get } + var shortLinkLargeDataSet: Bool { get } +} -struct UserContactLink: Decodable, Hashable { - var connLinkContact: CreatedConnLink - var autoAccept: AutoAccept? +extension SimplexAddress { + var shouldBeUpgraded: Bool { + connLinkContact.connShortLink == nil || !shortLinkDataSet || !shortLinkLargeDataSet + } - var responseDetails: String { - "connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" + func shareAddress(short: Bool) { + showShareSheet(items: [simplexChatLink(connLinkContact.simplexChatUri(short: short))]) } } -struct AutoAccept: Codable, Hashable { - var businessAddress: Bool - var acceptIncognito: Bool - var autoReply: MsgContent? +struct UserContactLink: Decodable, Hashable, SimplexAddress { + var connLinkContact: CreatedConnLink + var shortLinkDataSet: Bool + var shortLinkLargeDataSet: Bool + var addressSettings: AddressSettings - static func cmdString(_ autoAccept: AutoAccept?) -> String { - guard let autoAccept = autoAccept else { return "off" } - var s = "on" - if autoAccept.acceptIncognito { - s += " incognito=on" - } else if autoAccept.businessAddress { - s += " business" - } - guard let msg = autoAccept.autoReply else { return s } - return s + " " + msg.cmdString + init(_ ccLink: CreatedConnLink) { + connLinkContact = ccLink + let slDataSet = ccLink.connShortLink != nil + shortLinkDataSet = slDataSet + shortLinkLargeDataSet = slDataSet + addressSettings = AddressSettings(businessAddress: false) } } +struct AddressSettings: Codable, Hashable { + var businessAddress: Bool + var autoAccept: AutoAccept? + var autoReply: MsgContent? +} + +struct AutoAccept: Codable, Hashable { + var acceptIncognito: Bool +} + +struct GroupLink: Decodable, Hashable, SimplexAddress { + var userContactLinkId: Int64 + var connLinkContact: CreatedConnLink + var shortLinkDataSet: Bool + var shortLinkLargeDataSet: Bool + var groupLinkId: String + var acceptMemberRole: GroupMemberRole +} + struct DeviceToken: Decodable { var pushProvider: PushProvider var token: String @@ -1876,13 +1990,13 @@ struct ProtocolTestFailure: Decodable, Error, Equatable { let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text) switch testError { case .SMP(_, .AUTH): - return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error") + return err + " " + NSLocalizedString("Server requires authorization to create queues, check password.", comment: "server test error") case .XFTP(.AUTH): - return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error") - case .BROKER(_, .NETWORK): - return err + " " + NSLocalizedString("Possibly, certificate fingerprint in server address is incorrect", comment: "server test error") + return err + " " + NSLocalizedString("Server requires authorization to upload, check password.", comment: "server test error") + case .BROKER(_, .NETWORK(.unknownCAError)): + return err + " " + NSLocalizedString("Fingerprint in server address does not match certificate.", comment: "server test error") default: - return err + return err + " " + String.localizedStringWithFormat(NSLocalizedString("Error: %@.", comment: "server test error"), String(describing: testError)) } } } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 9b9fda0397..e5fd6362a3 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -34,7 +34,7 @@ actor TerminalItems { await add(.cmd(start, cmd)) await addResult(res) } - + func addResult(_ res: APIResult) async { let item: TerminalItem = switch res { case let .result(r): .res(.now, r) @@ -52,8 +52,26 @@ private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) { items.append(item) } +// analogue for SecondaryContextFilter in Kotlin +enum SecondaryItemsModelFilter { + case groupChatScopeContext(groupScopeInfo: GroupChatScopeInfo) + case msgContentTagContext(contentTag: MsgContentTag) + + func descr() -> String { + switch self { + case let .groupChatScopeContext(groupScopeInfo): + return "groupChatScopeContext \(groupScopeInfo.toChatScope())" + case let .msgContentTagContext(contentTag): + return "msgContentTagContext \(contentTag.rawValue)" + } + } +} + +// analogue for ChatsContext in Kotlin class ItemsModel: ObservableObject { - static let shared = ItemsModel() + static let shared = ItemsModel(secondaryIMFilter: nil) + public var secondaryIMFilter: SecondaryItemsModelFilter? + public var preloadState = PreloadState() private let publisher = ObservableObjectPublisher() private var bag = Set() var reversedChatItems: [ChatItem] = [] { @@ -77,13 +95,20 @@ class ItemsModel: ObservableObject { chatState.splits.isEmpty || chatState.splits.first != reversedChatItems.first?.id } - init() { + init(secondaryIMFilter: SecondaryItemsModelFilter? = nil) { + self.secondaryIMFilter = secondaryIMFilter publisher .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) .sink { self.objectWillChange.send() } .store(in: &bag) } + static func loadSecondaryChat(_ chatId: ChatId, chatFilter: SecondaryItemsModelFilter, willNavigate: @escaping () -> Void = {}) { + let im = ItemsModel(secondaryIMFilter: chatFilter) + ChatModel.shared.secondaryIM = im + im.loadOpenChat(chatId, willNavigate: willNavigate) + } + func loadOpenChat(_ chatId: ChatId, willNavigate: @escaping () -> Void = {}) { navigationTimeoutTask?.cancel() loadChatTask?.cancel() @@ -99,7 +124,7 @@ class ItemsModel: ObservableObject { loadChatTask = Task { await MainActor.run { self.isLoading = true } // try? await Task.sleep(nanoseconds: 1000_000000) - await loadChat(chatId: chatId) + await loadChat(chatId: chatId, im: self) if !Task.isCancelled { await MainActor.run { self.isLoading = false @@ -114,7 +139,7 @@ class ItemsModel: ObservableObject { loadChatTask?.cancel() loadChatTask = Task { // try? await Task.sleep(nanoseconds: 1000_000000) - await loadChat(chatId: chatId, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil) + await loadChat(chatId: chatId, im: self, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil) if !Task.isCancelled { await MainActor.run { if openAroundItemId == nil { @@ -124,16 +149,44 @@ class ItemsModel: ObservableObject { } } } + + public var contentTag: MsgContentTag? { + switch secondaryIMFilter { + case nil: nil + case .groupChatScopeContext: nil + case let .msgContentTagContext(contentTag): contentTag + } + } + + public var groupScopeInfo: GroupChatScopeInfo? { + switch secondaryIMFilter { + case nil: nil + case let .groupChatScopeContext(scopeInfo): scopeInfo + case .msgContentTagContext: nil + } + } +} + +class PreloadState { + var prevFirstVisible: Int64 = Int64.min + var prevItemsCount: Int = 0 + var preloading: Bool = false + + func clear() { + prevFirstVisible = Int64.min + prevItemsCount = 0 + preloading = false + } } class ChatTagsModel: ObservableObject { static let shared = ChatTagsModel() - + @Published var userTags: [ChatTag] = [] @Published var activeFilter: ActiveFilter? = nil @Published var presetTags: [PresetTag:Int] = [:] @Published var unreadTags: [Int64:Int] = [:] - + func updateChatTags(_ chats: [Chat]) { let tm = ChatTagsModel.shared var newPresetTags: [PresetTag:Int] = [:] @@ -187,13 +240,13 @@ class ChatTagsModel: ObservableObject { } clearActiveChatFilterIfNeeded() } - + func markChatTagRead(_ chat: Chat) -> Void { if chat.unreadTag, let tags = chat.chatInfo.chatTags { decTagsReadCount(tags) } } - + func updateChatTagRead(_ chat: Chat, wasUnread: Bool) -> Void { guard let tags = chat.chatInfo.chatTags else { return } let nowUnread = chat.unreadTag @@ -261,6 +314,41 @@ class ChatItemDummyModel: ObservableObject { func sendUpdate() { objectWillChange.send() } } +class ConnectProgressManager: ObservableObject { + @Published private var connectInProgress: String? = nil + @Published private var connectProgressByTimeout: Bool = false + private var onCancel: (() -> Void)? + + static let shared = ConnectProgressManager() + + func startConnectProgress(_ text: String, onCancel: (() -> Void)? = nil) { + connectInProgress = text + self.onCancel = onCancel + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.connectProgressByTimeout = self.connectInProgress != nil + } + } + + func stopConnectProgress() { + connectInProgress = nil + onCancel = nil + connectProgressByTimeout = false + } + + func cancelConnectProgress() { + onCancel?() + stopConnectProgress() + } + + var showConnectProgress: String? { + connectProgressByTimeout ? connectInProgress : nil + } + + var isInProgress: Bool { + connectInProgress != nil + } +} + final class ChatModel: ObservableObject { @Published var onboardingStage: OnboardingStage? @Published var setDeliveryReceipts = false @@ -287,7 +375,6 @@ final class ChatModel: ObservableObject { // current chat @Published var chatId: String? @Published var openAroundItemId: ChatItem.ID? = nil - var chatItemStatuses: Dictionary = [:] @Published var chatToTop: String? @Published var groupMembers: [GMember] = [] @Published var groupMembersIndexes: Dictionary = [:] // groupMemberId to index in groupMembers list @@ -298,6 +385,7 @@ final class ChatModel: ObservableObject { @Published var userAddress: UserContactLink? @Published var chatItemTTL: ChatItemTTL = .none @Published var appOpenUrl: URL? + @Published var appOpenUrlLater: URL? @Published var deviceToken: DeviceToken? @Published var savedToken: DeviceToken? @Published var tokenRegistered = false @@ -336,6 +424,10 @@ final class ChatModel: ObservableObject { let im = ItemsModel.shared + // ItemsModel for secondary chat view (such as support scope chat), as opposed to ItemsModel.shared used for primary chat + @Published var secondaryIM: ItemsModel? = nil + @Published var secondaryPendingInviteeChatOpened = false + static var ok: Bool { ChatModel.shared.chatDbStatus == .ok } let ntfEnableLocal = true @@ -348,6 +440,10 @@ final class ChatModel: ObservableObject { remoteCtrlSession?.active ?? false } + var addressShortLinkDataSet: Bool { + userAddress?.shortLinkDataSet ?? true + } + func getUser(_ userId: Int64) -> User? { currentUser?.userId == userId ? currentUser @@ -393,7 +489,7 @@ final class ChatModel: ObservableObject { func getGroupChat(_ groupId: Int64) -> Chat? { chats.first { chat in - if case let .group(groupInfo) = chat.chatInfo { + if case let .group(groupInfo, _) = chat.chatInfo { return groupInfo.groupId == groupId } else { return false @@ -446,7 +542,11 @@ final class ChatModel: ObservableObject { func updateChatInfo(_ cInfo: ChatInfo) { if let i = getChatIndex(cInfo.id) { - chats[i].chatInfo = cInfo + if case let .group(groupInfo, groupChatScope) = cInfo, groupChatScope != nil { + chats[i].chatInfo = .group(groupInfo: groupInfo, groupChatScope: nil) + } else { + chats[i].chatInfo = cInfo + } chats[i].created = Date.now } } @@ -468,7 +568,7 @@ final class ChatModel: ObservableObject { } func updateGroup(_ groupInfo: GroupInfo) { - updateChat(.group(groupInfo: groupInfo)) + updateChat(.group(groupInfo: groupInfo, groupChatScope: nil)) } private func updateChat(_ cInfo: ChatInfo, addMissing: Bool = true) { @@ -500,8 +600,15 @@ final class ChatModel: ObservableObject { } } - func updateChats(_ newChats: [ChatData]) { - chats = newChats.map { Chat($0) } + func updateChats(_ newChats: [ChatData], keepingChatId: String? = nil) { + if let keepingChatId, + let chatToKeep = getChat(keepingChatId), + let i = newChats.firstIndex(where: { $0.id == keepingChatId }) { + let remainingNewChats = Array(newChats[..= currentPreviewItem.meta.itemTs { - [cItem] + // update preview + if cInfo.groupChatScope() == nil || cInfo.groupInfo?.membership.memberPending ?? false { + chats[i].chatItems = switch cInfo { + case .group: + if let currentPreviewItem = chats[i].chatItems.first { + if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs { + [cItem] + } else { + [currentPreviewItem] + } } else { - [currentPreviewItem] + [cItem] } - } else { + default: [cItem] } - default: - [cItem] - } - if case .rcvNew = cItem.meta.itemStatus { - unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0) + if case .rcvNew = cItem.meta.itemStatus { + unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0) + } } + // pop chat popChatCollector.throttlePopChat(cInfo.id, currentPosition: i) } else { - addChat(Chat(chatInfo: cInfo, chatItems: [cItem])) + if cInfo.groupChatScope() == nil { + addChat(Chat(chatInfo: cInfo, chatItems: [cItem])) + } else { + addChat(Chat(chatInfo: cInfo, chatItems: [])) + } } - // add to current chat - if chatId == cInfo.id { - _ = _upsertChatItem(cInfo, cItem) + // add to current scope + if let ciIM = getCIItemsModel(cInfo, cItem) { + _ = _upsertChatItem(ciIM, cInfo, cItem) + } + } + + func getCIItemsModel(_ cInfo: ChatInfo, _ ci: ChatItem) -> ItemsModel? { + let cInfoScope = cInfo.groupChatScope() + return if let cInfoScope = cInfoScope { + switch (cInfoScope, secondaryIM?.secondaryIMFilter) { + case let (.memberSupport, .some(.groupChatScopeContext(groupScopeInfo))): + // Chat with member or Chat with admins opened (secondaryIM has .groupChatScopeContext filter), cInfo has matching scope + (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil + + case let (.memberSupport, .some(.msgContentTagContext(contentTag))): + // Reports view opened (secondaryIM has .msgContentTagContext(.report) filter), we process event (cInfo has proper .memberSupport scope) + (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil + + case let (.reports, .some(.msgContentTagContext(contentTag))): + // Reports view opened (secondaryIM has .msgContentTagContext(.report) filter), we process user action (cInfo has surrogate .reports scope) + (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil + default: + nil + } + } else { + cInfo.id == chatId ? im : nil } } func upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { - // update previews - var res: Bool - if let chat = getChat(cInfo.id) { - if let pItem = chat.chatItems.last { - if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) { + // update chat list + var itemAdded: Bool = false + if cInfo.groupChatScope() == nil { + if let chat = getChat(cInfo.id) { + if let pItem = chat.chatItems.last { + if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) { + chat.chatItems = [cItem] + } + } else { chat.chatItems = [cItem] } } else { - chat.chatItems = [cItem] + addChat(Chat(chatInfo: cInfo, chatItems: [cItem])) + itemAdded = true + } + if cItem.isDeletedContent || cItem.meta.itemDeleted != nil { + VoiceItemState.stopVoiceInChatView(cInfo, cItem) } - res = false - } else { - addChat(Chat(chatInfo: cInfo, chatItems: [cItem])) - res = true } - if cItem.isDeletedContent || cItem.meta.itemDeleted != nil { - VoiceItemState.stopVoiceInChatView(cInfo, cItem) + // update current scope + if let ciIM = getCIItemsModel(cInfo, cItem) { + itemAdded = _upsertChatItem(ciIM, cInfo, cItem) } - // update current chat - return chatId == cInfo.id ? _upsertChatItem(cInfo, cItem) : res + return itemAdded } - private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { - if let i = getChatItemIndex(cItem) { - _updateChatItem(at: i, with: cItem) - ChatItemDummyModel.shared.sendUpdate() + private func _upsertChatItem(_ ciIM: ItemsModel, _ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { + if let i = getChatItemIndex(ciIM, cItem) { + let oldStatus = ciIM.reversedChatItems[i].meta.itemStatus + let newStatus = cItem.meta.itemStatus + var ci = cItem + if shouldKeepOldSndCIStatus(oldStatus: oldStatus, newStatus: newStatus) { + ci.meta.itemStatus = oldStatus + } + _updateChatItem(ciIM: ciIM, at: i, with: ci) + ChatItemDummyModel.shared.sendUpdate() // TODO [knocking] review what's this return false } else { - var ci = cItem - if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { - ci.meta.itemStatus = status - } - im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) - im.chatState.itemAdded((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0) - im.itemAdded = true + ciIM.reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0) + ciIM.chatState.itemAdded((cItem.id, cItem.isRcvNew), hasLiveDummy ? 1 : 0) + ciIM.itemAdded = true ChatItemDummyModel.shared.sendUpdate() return true } @@ -595,40 +744,42 @@ final class ChatModel: ObservableObject { } func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) { - if chatId == cInfo.id, let i = getChatItemIndex(cItem) { + if let ciIM = getCIItemsModel(cInfo, cItem), + let i = getChatItemIndex(ciIM, cItem) { withConditionalAnimation { - _updateChatItem(at: i, with: cItem) + _updateChatItem(ciIM: ciIM, at: i, with: cItem) } - } else if let status = status { - chatItemStatuses.updateValue(status, forKey: cItem.id) } } - private func _updateChatItem(at i: Int, with cItem: ChatItem) { - im.reversedChatItems[i] = cItem - im.reversedChatItems[i].viewTimestamp = .now + private func _updateChatItem(ciIM: ItemsModel, at i: Int, with cItem: ChatItem) { + ciIM.reversedChatItems[i] = cItem + ciIM.reversedChatItems[i].viewTimestamp = .now } - func getChatItemIndex(_ cItem: ChatItem) -> Int? { - im.reversedChatItems.firstIndex(where: { $0.id == cItem.id }) + func getChatItemIndex(_ ciIM: ItemsModel, _ cItem: ChatItem) -> Int? { + ciIM.reversedChatItems.firstIndex(where: { $0.id == cItem.id }) } func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) { - if cItem.isRcvNew { - unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0) - } - // update previews - if let chat = getChat(cInfo.id) { - if let pItem = chat.chatItems.last, pItem.id == cItem.id { - chat.chatItems = [ChatItem.deletedItemDummy()] + // update chat list + if cInfo.groupChatScope() == nil { + if cItem.isRcvNew { + unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0) + } + // update previews + if let chat = getChat(cInfo.id) { + if let pItem = chat.chatItems.last, pItem.id == cItem.id { + chat.chatItems = [ChatItem.deletedItemDummy()] + } } } - // remove from current chat - if chatId == cInfo.id { - if let i = getChatItemIndex(cItem) { + // remove from current scope + if let ciIM = getCIItemsModel(cInfo, cItem) { + if let i = getChatItemIndex(ciIM, cItem) { withAnimation { - let item = im.reversedChatItems.remove(at: i) - im.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed()) + let item = ciIM.reversedChatItems.remove(at: i) + ciIM.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed()) } } } @@ -644,7 +795,7 @@ final class ChatModel: ObservableObject { if chatId == groupInfo.id { for i in 0.. ChatItem? { let newContent: CIContent if case .groupSnd = item.chatDir, removedMember.groupMemberId == groupInfo.membership.groupMemberId { @@ -736,7 +887,7 @@ final class ChatModel: ObservableObject { im.reversedChatItems.first?.isLiveDummy == true } - func markAllChatItemsRead(_ cInfo: ChatInfo) { + func markAllChatItemsRead(_ chatIM: ItemsModel, _ cInfo: ChatInfo) { // update preview _updateChat(cInfo.id) { chat in self.decreaseUnreadCounter(user: self.currentUser!, chat: chat) @@ -747,7 +898,7 @@ final class ChatModel: ObservableObject { if chatId == cInfo.id { var i = 0 while i < im.reversedChatItems.count { - markChatItemRead_(i) + markChatItemRead_(chatIM, i) i += 1 } im.chatState.itemsRead(nil, im.reversedChatItems.reversed()) @@ -772,27 +923,26 @@ final class ChatModel: ObservableObject { } // clear current chat if chatId == cInfo.id { - chatItemStatuses = [:] im.reversedChatItems = [] im.chatState.clear() } } - func markChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], _ mentionsRead: Int) { + func markChatItemsRead(_ chatIM: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], _ mentionsRead: Int) { if self.chatId == cInfo.id { var unreadItemIds: Set = [] var i = 0 var ids = Set(itemIds) - while i < im.reversedChatItems.count && !ids.isEmpty { - let item = im.reversedChatItems[i] + while i < chatIM.reversedChatItems.count && !ids.isEmpty { + let item = chatIM.reversedChatItems[i] if ids.contains(item.id) && item.isRcvNew { - markChatItemRead_(i) + markChatItemRead_(chatIM, i) unreadItemIds.insert(item.id) ids.remove(item.id) } i += 1 } - im.chatState.itemsRead(unreadItemIds, im.reversedChatItems.reversed()) + chatIM.chatState.itemsRead(unreadItemIds, chatIM.reversedChatItems.reversed()) } self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count, unreadMentions: -mentionsRead) } @@ -827,7 +977,7 @@ final class ChatModel: ObservableObject { } let popChatCollector = PopChatCollector() - + class PopChatCollector { private let subject = PassthroughSubject() private var bag = Set() @@ -840,7 +990,7 @@ final class ChatModel: ObservableObject { .sink { self.popCollectedChats() } .store(in: &bag) } - + func throttlePopChat(_ chatId: ChatId, currentPosition: Int) { let m = ChatModel.shared if currentPosition > 0 && m.chatId == chatId { @@ -851,7 +1001,7 @@ final class ChatModel: ObservableObject { subject.send() } } - + func clear() { chatsToPop = [:] } @@ -888,13 +1038,13 @@ final class ChatModel: ObservableObject { } } - private func markChatItemRead_(_ i: Int) { - let meta = im.reversedChatItems[i].meta + private func markChatItemRead_(_ chatIM: ItemsModel, _ i: Int) { + let meta = chatIM.reversedChatItems[i].meta if case .rcvNew = meta.itemStatus { - im.reversedChatItems[i].meta.itemStatus = .rcvRead - im.reversedChatItems[i].viewTimestamp = .now + chatIM.reversedChatItems[i].meta.itemStatus = .rcvRead + chatIM.reversedChatItems[i].viewTimestamp = .now if meta.itemLive != true, let ttl = meta.itemTimed?.ttl { - im.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl) + chatIM.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl) } } } @@ -973,7 +1123,7 @@ final class ChatModel: ObservableObject { var count = 0 var ns: [String] = [] if let ciCategory = chatItem.mergeCategory, - var i = getChatItemIndex(chatItem) { + var i = getChatItemIndex(im, chatItem) { // TODO [knocking] review: use getCIItemsModel? while i < im.reversedChatItems.count { let ci = im.reversedChatItems[i] if ci.mergeCategory != ciCategory { break } @@ -989,7 +1139,7 @@ final class ChatModel: ObservableObject { // returns the index of the passed item and the next item (it has smaller index) func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) { - if let i = getChatItemIndex(ci) { + if let i = getChatItemIndex(im, ci) { // TODO [knocking] review: use getCIItemsModel? (i, i > 0 ? im.reversedChatItems[i - 1] : nil) } else { (nil, nil) @@ -1100,7 +1250,7 @@ final class ChatModel: ObservableObject { func removeWallpaperFilesFromChat(_ chat: Chat) { if case let .direct(contact) = chat.chatInfo { removeWallpaperFilesFromTheme(contact.uiThemes) - } else if case let .group(groupInfo) = chat.chatInfo { + } else if case let .group(groupInfo, _) = chat.chatInfo { removeWallpaperFilesFromTheme(groupInfo.uiThemes) } } @@ -1121,7 +1271,6 @@ struct ShowingInvitation { } struct NTFContactRequest { - var incognito: Bool var chatId: String } @@ -1159,11 +1308,23 @@ final class Chat: ObservableObject, Identifiable, ChatLike { default: chatStats.unreadChat } } - + var id: ChatId { get { chatInfo.id } } var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } } + var supportUnreadCount: Int { + switch chatInfo { + case let .group(groupInfo, _): + if groupInfo.canModerate { + return groupInfo.membersRequireAttention + } else { + return groupInfo.membership.supportChat?.unread ?? 0 + } + default: return 0 + } + } + public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) } diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index da55bd90d0..79f4ef2f09 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -12,7 +12,6 @@ import UIKit import SimpleXChat let ntfActionAcceptContact = "NTF_ACT_ACCEPT_CONTACT" -let ntfActionAcceptContactIncognito = "NTF_ACT_ACCEPT_CONTACT_INCOGNITO" let ntfActionAcceptCall = "NTF_ACT_ACCEPT_CALL" let ntfActionRejectCall = "NTF_ACT_REJECT_CALL" @@ -59,13 +58,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { logger.debug("NtfManager.processNotificationResponse changeActiveUser") changeActiveUser(userId, viewPwd: nil) } - if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito), + if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact, let chatId = content.userInfo["chatId"] as? String { - let incognito = action == ntfActionAcceptContactIncognito if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo { - Task { await acceptContactRequest(incognito: incognito, contactRequest: contactRequest) } + Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } } else { - chatModel.ntfContactRequest = NTFContactRequest(incognito: incognito, chatId: chatId) + chatModel.ntfContactRequest = NTFContactRequest(chatId: chatId) } } else if let (chatId, ntfAction) = ntfCallAction(content, action) { if let invitation = chatModel.callInvitations.removeValue(forKey: chatId) { @@ -161,10 +159,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { identifier: ntfActionAcceptContact, title: NSLocalizedString("Accept", comment: "accept contact request via notification"), options: .foreground - ), UNNotificationAction( - identifier: ntfActionAcceptContactIncognito, - title: NSLocalizedString("Accept incognito", comment: "accept contact request via notification"), - options: .foreground ) ], intentIdentifiers: [], diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index d92411decd..f95e6ac2dd 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -94,14 +94,14 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, return try apiResult(res) } -func chatApiSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) -> APIResult { +func chatApiSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, retryNum: Int32 = 0, log: Bool = true) -> APIResult { if log { logger.debug("chatSendCmd \(cmd.cmdType)") } let start = Date.now let resp: APIResult = bgTask - ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl) } - : sendSimpleXCmd(cmd, ctrl) + ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl, retryNum: retryNum) } + : sendSimpleXCmd(cmd, ctrl, retryNum: retryNum) if log { logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") if case let .invalid(_, json) = resp { @@ -120,10 +120,102 @@ func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDe return try apiResult(res) } +func chatApiSendCmdWithRetry(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, inProgress: BoxedValue? = nil, retryNum: Int32 = 0) async -> APIResult? { + let r: APIResult = await chatApiSendCmd(cmd, bgTask: bgTask, bgDelay: bgDelay, retryNum: retryNum) + if inProgress == nil || inProgress?.boxedValue == true, + case let .error(e) = r, let alert = retryableNetworkErrorAlert(e) { + return await withCheckedContinuation { cont in + showRetryAlert( + alert, + onCancel: { _ in + cont.resume(returning: nil) + }, + onRetry: { + let r1: APIResult? = await chatApiSendCmdWithRetry(cmd, bgTask: bgTask, bgDelay: bgDelay, inProgress: inProgress, retryNum: retryNum + 1) + cont.resume(returning: r1) + } + ) + } + } else { + return r + } +} + @inline(__always) -func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async -> APIResult { +func showRetryAlert(_ alert: (title: String, message: String), onCancel: @escaping (UIAlertAction) -> Void, onRetry: @escaping () async -> Void) { + DispatchQueue.main.async { + showAlert( + alert.title, + message: alert.message, + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .cancel, + handler: onCancel + ), + UIAlertAction( + title: NSLocalizedString("Retry", comment: "alert action"), + style: .default, + handler: { _ in Task(operation: onRetry) } + ) + ]} + ) + } +} + +func retryableNetworkErrorAlert(_ e: ChatError) -> (title: String, message: String)? { + switch e { + case let .errorAgent(.BROKER(addr, .TIMEOUT)): ( + title: NSLocalizedString("Connection timeout", comment: "alert title"), + message: serverErrorAlertMessage(addr) + ) + case let .errorAgent(.BROKER(addr, .NETWORK(.unknownCAError))): nil + case let .errorAgent(.BROKER(addr, .NETWORK)): ( + title: NSLocalizedString("Connection error", comment: "alert title"), + message: serverErrorAlertMessage(addr) + ) + case let .errorAgent(.SMP(serverAddress, .PROXY(.BROKER(.TIMEOUT)))): ( + title: NSLocalizedString("Private routing timeout", comment: "alert title"), + message: proxyErrorAlertMessage(serverAddress) + ) + case let .errorAgent(.SMP(serverAddress, .PROXY(.BROKER(.NETWORK(.unknownCAError))))): nil + case let .errorAgent(.SMP(serverAddress, .PROXY(.BROKER(.NETWORK)))): ( + title: NSLocalizedString("Private routing error", comment: "alert title"), + message: proxyErrorAlertMessage(serverAddress) + ) + case let .errorAgent(.PROXY(proxyServer, destServer, .protocolError(.PROXY(.BROKER(.TIMEOUT))))): ( + title: NSLocalizedString("Private routing timeout", comment: "alert title"), + message: proxyDestinationErrorAlertMessage(proxyServer: proxyServer, destServer: destServer) + ) + case let .errorAgent(.PROXY(proxyServer, destServer, .protocolError(.PROXY(.BROKER(.NETWORK(.unknownCAError)))))): nil + case let .errorAgent(.PROXY(proxyServer, destServer, .protocolError(.PROXY(.BROKER(.NETWORK))))): ( + title: NSLocalizedString("Private routing error", comment: "alert title"), + message: proxyDestinationErrorAlertMessage(proxyServer: proxyServer, destServer: destServer) + ) + case let .errorAgent(.PROXY(proxyServer, destServer, .protocolError(.PROXY(.NO_SESSION)))): ( + title: NSLocalizedString("No private routing session", comment: "alert title"), + message: proxyDestinationErrorAlertMessage(proxyServer: proxyServer, destServer: destServer) + ) + default: nil + } +} + +func serverErrorAlertMessage(_ addr: String) -> String { + String.localizedStringWithFormat(NSLocalizedString("Please check your network connection with %@ and try again.", comment: "alert message"), serverHostname(addr)) +} + +func proxyErrorAlertMessage(_ addr: String) -> String { + String.localizedStringWithFormat(NSLocalizedString("Error connecting to forwarding server %@. Please try later.", comment: "alert message"), serverHostname(addr)) +} + +func proxyDestinationErrorAlertMessage(proxyServer: String, destServer: String) -> String { + String.localizedStringWithFormat(NSLocalizedString("Forwarding server %@ failed to connect to destination server %@. Please try later.", comment: "alert message"), serverHostname(proxyServer), serverHostname(destServer)) +} + +@inline(__always) +func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, retryNum: Int32 = 0, log: Bool = true) async -> APIResult { await withCheckedContinuation { cont in - cont.resume(returning: chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)) + cont.resume(returning: chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, retryNum: retryNum, log: log)) } } @@ -200,6 +292,10 @@ func apiSetUserGroupReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgRec try await sendCommandOkResp(.apiSetUserGroupReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) } +func apiSetUserAutoAcceptMemberContacts(_ userId: Int64, enable: Bool) async throws { + try await sendCommandOkResp(.apiSetUserAutoAcceptMemberContacts(userId: userId, enable: enable)) +} + func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User { try await setUserPrivacy_(.apiHideUser(userId: userId, viewPwd: viewPwd)) } @@ -344,43 +440,54 @@ func apiGetChatTagsAsync() async throws -> [ChatTag] { let loadItemsPerPage = 50 -func apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) { - let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, pagination: pagination, search: search)) +func apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag? = nil, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) { + let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, scope: scope, contentTag: contentTag, pagination: pagination, search: search)) if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo ?? NavigationInfo()) } throw r.unexpected } -func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async { - await loadChat(chatId: chat.chatInfo.id, search: search, clearItems: clearItems) +func loadChat(chat: Chat, im: ItemsModel, search: String = "", clearItems: Bool = true) async { + await loadChat(chatId: chat.chatInfo.id, im: im, search: search, clearItems: clearItems) } -func loadChat(chatId: ChatId, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async { - let m = ChatModel.shared - let im = ItemsModel.shared +func loadChat(chatId: ChatId, im: ItemsModel, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async { await MainActor.run { - m.chatItemStatuses = [:] if clearItems { im.reversedChatItems = [] - ItemsModel.shared.chatState.clear() + im.chatState.clear() } } - await apiLoadMessages(chatId, openAroundItemId != nil ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage) : (search == "" ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)), im.chatState, search, openAroundItemId, { 0...0 }) + await apiLoadMessages( + chatId, + im, + ( // pagination + openAroundItemId != nil + ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage) + : ( + search == "" + ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage) + ) + ), + search, + openAroundItemId, + { 0...0 } + ) } -func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo { - let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId)) +func apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64) async throws -> ChatItemInfo { + let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, scope: scope, itemId: itemId)) if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo } throw r.unexpected } -func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) { - let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds)) +func apiPlanForwardChatItems(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) { + let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(fromChatType: type, fromChatId: id, fromScope: scope, itemIds: itemIds)) if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) } throw r.unexpected } -func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { - let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl) +func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { + let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, toScope: toScope, fromChatType: fromChatType, fromChatId: fromChatId, fromScope: fromScope, itemIds: itemIds, ttl: ttl) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) } @@ -412,8 +519,8 @@ func apiReorderChatTags(tagIds: [Int64]) async throws { try await sendCommandOkResp(.apiReorderChatTags(tagIds: tagIds)) } -func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? { - let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages) +func apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let cmd: ChatCommand = .apiSendMessages(type: type, id: id, scope: scope, live: live, ttl: ttl, composedMessages: composedMessages) return await processSendMessageCmd(toChatType: type, cmd: cmd) } @@ -490,8 +597,8 @@ private func createChatItemsErrorAlert(_ r: ChatError) { ) } -func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem { - let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay) +func apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem { + let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, scope: scope, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay) switch r { case let .chatItemUpdated(_, aChatItem): return aChatItem.chatItem case let .chatItemNotChanged(_, aChatItem): return aChatItem.chatItem @@ -499,8 +606,8 @@ func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: } } -func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem { - let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) +func apiChatItemReaction(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem { + let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, scope: scope, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) if case let .chatItemReaction(_, _, reaction) = r { return reaction.chatReaction.chatItem } throw r.unexpected } @@ -512,8 +619,8 @@ func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction) throw r.unexpected } -func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { - let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay) +func apiDeleteChatItems(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, scope: scope, itemIds: itemIds, mode: mode), bgDelay: msgDelay) if case let .chatItemsDeleted(_, items, _) = r { return items } throw r.unexpected } @@ -784,16 +891,16 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws - throw r.unexpected } -func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { - let r: ChatResponse0 = try await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) - if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } - throw r.unexpected +func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo)? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiContactQueueInfo(contactId: contactId)) + if case let .result(.queueInfo(_, rcvMsgInfo, queueInfo)) = r { return (rcvMsgInfo, queueInfo) } + if let r { throw r.unexpected } else { return nil } } -func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { - let r: ChatResponse0 = try await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) - if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } - throw r.unexpected +func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo)? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) + if case let .result(.queueInfo(_, rcvMsgInfo, queueInfo)) = r { return (rcvMsgInfo, queueInfo) } + if let r { throw r.unexpected } else { return nil } } func apiSwitchContact(contactId: Int64) throws -> ConnectionStats { @@ -863,10 +970,9 @@ func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactCo logger.error("apiAddContact: no current user") return (nil, nil) } - let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) - let r: APIResult = await chatApiSendCmd(.apiAddContact(userId: userId, short: short, incognito: incognito), bgTask: false) + let r: APIResult? = await chatApiSendCmdWithRetry(.apiAddContact(userId: userId, incognito: incognito), bgTask: false) if case let .result(.invitation(_, connLinkInv, connection)) = r { return ((connLinkInv, connection), nil) } - let alert = connectionErrorAlert(r) + let alert: Alert? = if let r { connectionErrorAlert(r) } else { nil } return (nil, alert) } @@ -876,21 +982,20 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P throw r.unexpected } -func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection { - let r: ChatResponse1 = try await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) - - if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection} - throw r.unexpected +func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiChangeConnectionUser(connId: connId, userId: userId)) + if case let .result(.connectionUserChanged(_, _, toConnection, _)) = r {return toConnection} + if let r { throw r.unexpected } else { return nil } } -func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) { +func apiConnectPlan(connLink: String, inProgress: BoxedValue) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiConnectPlan: no current user") return (nil, nil) } - let r: APIResult = await chatApiSendCmd(.apiConnectPlan(userId: userId, connLink: connLink)) + let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnectPlan(userId: userId, connLink: connLink), inProgress: inProgress) if case let .result(.connectionPlan(_, connLink, connPlan)) = r { return ((connLink, connPlan), nil) } - let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) + let alert: Alert? = if let r { apiConnectResponseAlert(r) } else { nil } return (nil, alert) } @@ -909,7 +1014,7 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT logger.error("apiConnect: no current user") return (nil, nil) } - let r: APIResult = await chatApiSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) + let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) let m = ChatModel.shared switch r { case let .result(.sentConfirmation(_, connection)): @@ -924,12 +1029,12 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT return (nil, alert) default: () } - let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) + let alert: Alert? = if let r { apiConnectResponseAlert(r) } else { nil } return (nil, alert) } -private func apiConnectResponseAlert(_ r: ChatError) -> Alert? { - switch r { +private func apiConnectResponseAlert(_ r: APIResult) -> Alert { + switch r.unexpected { case .error(.invalidConnReq): mkAlert( title: "Invalid connection link", @@ -965,12 +1070,12 @@ private func apiConnectResponseAlert(_ r: ChatError) -> Alert? { if internalErr == "SEUniqueID" { mkAlert( title: "Already connected?", - message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))." + message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(internalErr))." ) } else { - nil + connectionErrorAlert(r) } - default: nil + default: connectionErrorAlert(r) } } @@ -992,16 +1097,59 @@ private func connectionErrorAlert(_ r: APIResult) -> Alert { } } +func apiPrepareContact(connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData) async throws -> ChatData { + let userId = try currentUserId("apiPrepareContact") + let r: ChatResponse1 = try await chatSendCmd(.apiPrepareContact(userId: userId, connLink: connLink, contactShortLinkData: contactShortLinkData)) + if case let .newPreparedChat(_, chat) = r { return chat } + throw r.unexpected +} + +func apiPrepareGroup(connLink: CreatedConnLink, groupShortLinkData: GroupShortLinkData) async throws -> ChatData { + let userId = try currentUserId("apiPrepareGroup") + let r: ChatResponse1 = try await chatSendCmd(.apiPrepareGroup(userId: userId, connLink: connLink, groupShortLinkData: groupShortLinkData)) + if case let .newPreparedChat(_, chat) = r { return chat } + throw r.unexpected +} + +func apiChangePreparedContactUser(contactId: Int64, newUserId: Int64) async throws -> Contact { + let r: ChatResponse1 = try await chatSendCmd(.apiChangePreparedContactUser(contactId: contactId, newUserId: newUserId)) + if case let .contactUserChanged(_, _, _, toContact) = r {return toContact} + throw r.unexpected +} + +func apiChangePreparedGroupUser(groupId: Int64, newUserId: Int64) async throws -> GroupInfo { + let r: ChatResponse1 = try await chatSendCmd(.apiChangePreparedGroupUser(groupId: groupId, newUserId: newUserId)) + if case let .groupUserChanged(_, _, _, toGroup) = r {return toGroup} + throw r.unexpected +} + +func apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) async -> Contact? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnectPreparedContact(contactId: contactId, incognito: incognito, msg: msg)) + if case let .result(.startedConnectionToContact(_, contact)) = r { return contact } + if let r { AlertManager.shared.showAlert(apiConnectResponseAlert(r)) } + return nil +} + +func apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) async -> GroupInfo? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnectPreparedGroup(groupId: groupId, incognito: incognito, msg: msg)) + if case let .result(.startedConnectionToGroup(_, groupInfo)) = r { return groupInfo } + if let r { AlertManager.shared.showAlert(apiConnectResponseAlert(r)) } + return nil +} + func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Contact?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiConnectContactViaAddress: no current user") return (nil, nil) } - let r: APIResult = await chatApiSendCmd(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId)) + let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId)) if case let .result(.sentInvitationToContact(_, contact, _)) = r { return (contact, nil) } - logger.error("apiConnectContactViaAddress error: \(responseError(r.unexpected))") - let alert = connectionErrorAlert(r) - return (nil, alert) + if let r { + logger.error("apiConnectContactViaAddress error: \(responseError(r.unexpected))") + return (nil, connectionErrorAlert(r)) + } else { + return (nil, nil) + } } func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws { @@ -1167,18 +1315,18 @@ func apiSetChatUIThemes(chatId: ChatId, themes: ThemeModeOverrides?) async -> Bo } -func apiCreateUserAddress(short: Bool) async throws -> CreatedConnLink { +func apiCreateUserAddress() async throws -> CreatedConnLink? { let userId = try currentUserId("apiCreateUserAddress") - let r: ChatResponse1 = try await chatSendCmd(.apiCreateMyAddress(userId: userId, short: short)) - if case let .userContactLinkCreated(_, connLink) = r { return connLink } - throw r.unexpected + let r: APIResult? = await chatApiSendCmdWithRetry(.apiCreateMyAddress(userId: userId)) + if case let .result(.userContactLinkCreated(_, connLink)) = r { return connLink } + if let r { throw r.unexpected } else { return nil } } func apiDeleteUserAddress() async throws -> User? { let userId = try currentUserId("apiDeleteUserAddress") - let r: ChatResponse1 = try await chatSendCmd(.apiDeleteMyAddress(userId: userId)) - if case let .userContactLinkDeleted(user) = r { return user } - throw r.unexpected + let r: APIResult? = await chatApiSendCmdWithRetry(.apiDeleteMyAddress(userId: userId)) + if case let .result(.userContactLinkDeleted(user)) = r { return user } + if let r { throw r.unexpected } else { return nil } } func apiGetUserAddress() throws -> UserContactLink? { @@ -1199,18 +1347,25 @@ private func userAddressResponse(_ r: APIResult) throws -> UserCo } } -func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? { - let userId = try currentUserId("userAddressAutoAccept") - let r: APIResult = await chatApiSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) +func apiAddMyAddressShortLink() async throws -> UserContactLink? { + let userId = try currentUserId("apiAddMyAddressShortLink") + let r: APIResult? = await chatApiSendCmdWithRetry(.apiAddMyAddressShortLink(userId: userId)) + if case let .result(.userContactLink(_, contactLink)) = r { return contactLink } + if let r { throw r.unexpected } else { return nil } +} + +func apiSetUserAddressSettings(_ settings: AddressSettings) async throws -> UserContactLink? { + let userId = try currentUserId("apiSetUserAddressSettings") + let r: APIResult? = await chatApiSendCmdWithRetry(.apiSetAddressSettings(userId: userId, addressSettings: settings)) switch r { case let .result(.userContactLinkUpdated(_, contactLink)): return contactLink case .error(.errorStore(storeError: .userContactLinkNotFound)): return nil - default: throw r.unexpected + default: if let r { throw r.unexpected } else { return nil } } } func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact? { - let r: APIResult = await chatApiSendCmd(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId)) + let r: APIResult? = await chatApiSendCmdWithRetry(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId)) let am = AlertManager.shared if case let .result(.acceptingContactRequest(_, contact)) = r { return contact } @@ -1219,30 +1374,40 @@ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Cont title: "Connection error (AUTH)", message: "Sender may have deleted the connection request." ) - } else if let networkErrorAlert = networkErrorAlert(r) { - am.showAlert(networkErrorAlert) - } else { - logger.error("apiAcceptContactRequest error: \(String(describing: r))") - am.showAlertMsg( - title: "Error accepting contact request", - message: "Error: \(responseError(r.unexpected))" - ) + } else if let r { + if let networkErrorAlert = networkErrorAlert(r) { + am.showAlert(networkErrorAlert) + } else { + logger.error("apiAcceptContactRequest error: \(String(describing: r))") + am.showAlertMsg( + title: "Error accepting contact request", + message: "Error: \(responseError(r.unexpected))" + ) + } } return nil } -func apiRejectContactRequest(contactReqId: Int64) async throws { +func apiRejectContactRequest(contactReqId: Int64) async throws -> Contact? { let r: ChatResponse1 = try await chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) - if case .contactRequestRejected = r { return } + if case let .contactRequestRejected(_, _, contact_) = r { return contact_ } throw r.unexpected } func apiChatRead(type: ChatType, id: Int64) async throws { - try await sendCommandOkResp(.apiChatRead(type: type, id: id)) + try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: nil)) } -func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws { - try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, itemIds: itemIds)) +func apiSupportChatRead(type: ChatType, id: Int64, scope: GroupChatScope) async throws -> (GroupInfo, GroupMember) { + let r: ChatResponse2 = try await chatSendCmd(.apiChatRead(type: type, id: id, scope: scope)) + if case let .memberSupportChatRead(_, groupInfo, member) = r { return (groupInfo, member) } + throw r.unexpected +} + +func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ChatInfo { + let r: ChatResponse1 = try await chatSendCmd(.apiChatItemsRead(type: type, id: id, scope: scope, itemIds: itemIds)) + if case let .itemsReadForChat(_, updatedChatInfo) = r { return updatedChatInfo } + throw r.unexpected } func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { @@ -1294,7 +1459,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool var fileIdsToApprove: [Int64] = [] var srvsToApprove: Set = [] var otherFileErrs: [APIResult] = [] - + for fileId in fileIds { let r: APIResult = await chatApiSendCmd( .receiveFile( @@ -1318,7 +1483,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool otherFileErrs.append(r) } } - + if !auto { let otherErrsStr = fileErrorStrs(otherFileErrs) // If there are not approved files, alert is shown the same way both in case of singular and plural files reception @@ -1383,7 +1548,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool } } } - + func fileErrorStrs(_ errs: [APIResult]) -> String { var errStr = "" if errs.count >= 1 { @@ -1398,7 +1563,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool return errStr } } - + func cancelFile(user: User, fileId: Int64) async { if let chatItem = await apiCancelFile(fileId: fileId) { await chatItemSimpleUpdate(user, chatItem) @@ -1465,29 +1630,53 @@ func networkErrorAlert(_ res: APIResult) -> Alert? { } } -func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async { - if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) { +func acceptContactRequest(incognito: Bool, contactRequestId: Int64, inProgress: Binding? = nil) async { + await MainActor.run { inProgress?.wrappedValue = true } + if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequestId) { let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: []) await MainActor.run { - ChatModel.shared.replaceChat(contactRequest.id, chat) + if contact.contactRequestId != nil { // means contact request was initially created with contact, so we don't need to replace it + ChatModel.shared.updateContact(contact) + } else { + ChatModel.shared.replaceChat(contactRequestChatId(contactRequestId), chat) + } NetworkModel.shared.setContactNetworkStatus(contact, .connected) + inProgress?.wrappedValue = false } if contact.sndReady { + let chatId = chat.id DispatchQueue.main.async { dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(chat.id) + ItemsModel.shared.loadOpenChat(chatId) } } } + } else { + await MainActor.run { inProgress?.wrappedValue = false } } } -func rejectContactRequest(_ contactRequest: UserContactRequest) async { +func rejectContactRequest(_ contactRequestId: Int64, dismissToChatList: Bool = false) async { do { - try await apiRejectContactRequest(contactReqId: contactRequest.apiId) - DispatchQueue.main.async { ChatModel.shared.removeChat(contactRequest.id) } + let contact_ = try await apiRejectContactRequest(contactReqId: contactRequestId) + await MainActor.run { + if let contact = contact_ { // means contact request was initially created with contact, so we need to remove contact chat + ChatModel.shared.removeChat(contact.id) + } else { + ChatModel.shared.removeChat(contactRequestChatId(contactRequestId)) + } + if dismissToChatList { + ChatModel.shared.chatId = nil + } + } } catch let error { logger.error("rejectContactRequest: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error rejecting contact request", comment: "alert title"), + message: responseError(error) + ) + } } } @@ -1545,13 +1734,13 @@ func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] { throw r.unexpected } -func markChatRead(_ chat: Chat) async { +func markChatRead(_ im: ItemsModel, _ chat: Chat) async { do { if chat.chatStats.unreadCount > 0 { let cInfo = chat.chatInfo try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId) await MainActor.run { - withAnimation { ChatModel.shared.markAllChatItemsRead(cInfo) } + withAnimation { ChatModel.shared.markAllChatItemsRead(im, cInfo) } } } if chat.chatStats.unreadChat { @@ -1574,11 +1763,26 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async { } } -func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async { +func markSupportChatRead(_ groupInfo: GroupInfo, _ member: GroupMember) async { do { - try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds) - DispatchQueue.main.async { - ChatModel.shared.markChatItemsRead(cInfo, itemIds, mentionsRead) + if member.supportChatNotRead { + let (updatedGroupInfo, updatedMember) = try await apiSupportChatRead(type: .group, id: groupInfo.apiId, scope: .memberSupport(groupMemberId_: member.groupMemberId)) + await MainActor.run { + _ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember) + ChatModel.shared.updateGroup(updatedGroupInfo) + } + } + } catch { + logger.error("markSupportChatRead apiChatRead error: \(responseError(error))") + } +} + +func apiMarkChatItemsRead(_ im: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async { + do { + let updatedChatInfo = try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds) + await MainActor.run { + ChatModel.shared.updateChatInfo(updatedChatInfo) + ChatModel.shared.markChatItemsRead(im, cInfo, itemIds, mentionsRead) } } catch { logger.error("apiChatItemsRead error: \(responseError(error))") @@ -1616,19 +1820,31 @@ enum JoinGroupResult { case groupNotFound } -func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult { - let r: APIResult = await chatApiSendCmd(.apiJoinGroup(groupId: groupId)) +func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiJoinGroup(groupId: groupId)) switch r { case let .result(.userAcceptedGroupSent(_, groupInfo, _)): return .joined(groupInfo: groupInfo) case .error(.errorAgent(.SMP(_, .AUTH))): return .invitationRemoved case .error(.errorStore(.groupNotFound)): return .groupNotFound - default: throw r.unexpected + default: if let r { throw r.unexpected } else { return nil } } } -func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> [GroupMember] { +func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> (GroupInfo, GroupMember) { + let r: ChatResponse2 = try await chatSendCmd(.apiAcceptMember(groupId: groupId, groupMemberId: groupMemberId, memberRole: memberRole)) + if case let .memberAccepted(_, groupInfo, member) = r { return (groupInfo, member) } + throw r.unexpected +} + +func apiDeleteMemberSupportChat(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupInfo, GroupMember) { + let r: ChatResponse2 = try await chatSendCmd(.apiDeleteMemberSupportChat(groupId: groupId, groupMemberId: groupMemberId)) + if case let .memberSupportChatDeleted(_, groupInfo, member) = r { return (groupInfo, member) } + throw r.unexpected +} + +func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> (GroupInfo, [GroupMember]) { let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false) - if case let .userDeletedMembers(_, _, members, withMessages) = r { return members } + if case let .userDeletedMembers(_, updatedGroupInfo, members, _withMessages) = r { return (updatedGroupInfo, members) } throw r.unexpected } @@ -1669,8 +1885,8 @@ func apiListMembers(_ groupId: Int64) async -> [GroupMember] { func filterMembersToAdd(_ ms: [GMember]) -> [Contact] { let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil } return ChatModel.shared.chats - .compactMap{ $0.chatInfo.contact } - .filter{ c in c.sendMsgEnabled && !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) } + .compactMap{ c in c.chatInfo.sendMsgEnabled ? c.chatInfo.contact : nil } + .filter{ c in !c.sendMsgToConnect && !memberContactIds.contains(c.apiId) } .sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() } } @@ -1680,36 +1896,41 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws throw r.unexpected } -func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { - let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) - let r: ChatResponse2 = try await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short)) - if case let .groupLinkCreated(_, _, connLink, memberRole) = r { return (connLink, memberRole) } - throw r.unexpected +func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole)) + if case let .result(.groupLinkCreated(_, _, groupLink)) = r { return groupLink } + if let r { throw r.unexpected } else { return nil } } -func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { +func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink { let r: ChatResponse2 = try await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole)) - if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) } + if case let .groupLink(_, _, groupLink) = r { return groupLink } throw r.unexpected } func apiDeleteGroupLink(_ groupId: Int64) async throws { - let r: ChatResponse2 = try await chatSendCmd(.apiDeleteGroupLink(groupId: groupId)) - if case .groupLinkDeleted = r { return } - throw r.unexpected + let r: APIResult? = await chatApiSendCmdWithRetry(.apiDeleteGroupLink(groupId: groupId)) + if case .result(.groupLinkDeleted) = r { return } + if let r { throw r.unexpected } } -func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRole)? { +func apiGetGroupLink(_ groupId: Int64) throws -> GroupLink? { let r: APIResult = chatApiSendCmdSync(.apiGetGroupLink(groupId: groupId)) switch r { - case let .result(.groupLink(_, _, connLink, memberRole)): - return (connLink, memberRole) + case let .result(.groupLink(_, _, groupLink)): + return groupLink case .error(.errorStore(storeError: .groupLinkNotFound)): return nil default: throw r.unexpected } } +func apiAddGroupShortLink(_ groupId: Int64) async throws -> GroupLink? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiAddGroupShortLink(groupId: groupId)) + if case let .result(.groupLink(_, _, groupLink)) = r { return groupLink } + if let r { throw r.unexpected } else { return nil } +} + func apiCreateMemberContact(_ groupId: Int64, _ groupMemberId: Int64) async throws -> Contact { let r: ChatResponse2 = try await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) if case let .newMemberContact(_, contact, _, _) = r { return contact } @@ -1722,6 +1943,33 @@ func apiSendMemberContactInvitation(_ contactId: Int64, _ msg: MsgContent) async throw r.unexpected } +func apiAcceptMemberContact(contactId: Int64) async -> Contact? { + let r: APIResult? = await chatApiSendCmdWithRetry(.apiAcceptMemberContact(contactId: contactId)) + if case let .result(.memberContactAccepted(_, contact)) = r { return contact } + if let r { AlertManager.shared.showAlert(apiConnectResponseAlert(r)) } + return nil +} + +func acceptMemberContact(contactId: Int64, inProgress: Binding? = nil) async { + await MainActor.run { inProgress?.wrappedValue = true } + if let contact = await apiAcceptMemberContact(contactId: contactId) { + await MainActor.run { + ChatModel.shared.updateContact(contact) + NetworkModel.shared.setContactNetworkStatus(contact, .connected) + inProgress?.wrappedValue = false + } + if contact.sndReady { + DispatchQueue.main.async { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(contact.id) + } + } + } + } else { + await MainActor.run { inProgress?.wrappedValue = false } + } +} + func apiGetVersion() throws -> CoreVersionInfo { let r: ChatResponse2 = try chatSendCmdSync(.showVersion) if case let .versionInfo(info, _, _) = r { return info } @@ -1885,7 +2133,7 @@ private func changeActiveUser_(_ userId: Int64, viewPwd: String?) throws { try getUserChatData() } -func changeActiveUserAsync_(_ userId: Int64?, viewPwd: String?) async throws { +func changeActiveUserAsync_(_ userId: Int64?, viewPwd: String?, keepingChatId: String? = nil) async throws { let currentUser = if let userId = userId { try await apiSetActiveUserAsync(userId, viewPwd: viewPwd) } else { @@ -1897,7 +2145,7 @@ func changeActiveUserAsync_(_ userId: Int64?, viewPwd: String?) async throws { m.currentUser = currentUser m.users = users } - try await getUserChatDataAsync() + try await getUserChatDataAsync(keepingChatId: keepingChatId) await MainActor.run { if let currentUser = currentUser, var (_, invitation) = ChatModel.shared.callInvitations.first(where: { _, inv in inv.user.userId == userId }) { invitation.user = currentUser @@ -1919,7 +2167,7 @@ func getUserChatData() throws { tm.updateChatTags(m.chats) } -private func getUserChatDataAsync() async throws { +private func getUserChatDataAsync(keepingChatId: String?) async throws { let m = ChatModel.shared let tm = ChatTagsModel.shared if m.currentUser != nil { @@ -1930,7 +2178,7 @@ private func getUserChatDataAsync() async throws { await MainActor.run { m.userAddress = userAddress m.chatItemTTL = chatItemTTL - m.updateChats(chats) + m.updateChats(chats, keepingChatId: keepingChatId) tm.activeFilter = nil tm.userTags = tags tm.updateChatTags(m.chats) @@ -2041,17 +2289,27 @@ func processReceivedMsg(_ res: ChatEvent) async { await MainActor.run { n.setContactNetworkStatus(contact, .connected) } - case let .receivedContactRequest(user, contactRequest): + case let .receivedContactRequest(user, contactRequest, chat_): if active(user) { - let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest) await MainActor.run { - if m.hasChat(contactRequest.id) { - m.updateChatInfo(cInfo) + if let chat = chat_ { // means contact request was created with contact, so we need to add/update contact chat + if !m.hasChat(chat.id) { + m.addChat(Chat(chat)) + } else if m.chatId == chat.id { + m.updateChatInfo(chat.chatInfo) + } else { + m.replaceChat(chat.id, Chat(chat)) + } } else { - m.addChat(Chat( - chatInfo: cInfo, - chatItems: [] - )) + let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest) + if m.hasChat(contactRequest.id) { + m.updateChatInfo(cInfo) + } else { + m.addChat(Chat( + chatInfo: cInfo, + chatItems: [] + )) + } } } } @@ -2104,6 +2362,12 @@ func processReceivedMsg(_ res: ChatEvent) async { n.networkStatuses = ns } } + case let .chatInfoUpdated(user, chatInfo): + if active(user) { + await MainActor.run { + m.updateChatInfo(chatInfo) + } + } case let .newChatItems(user, chatItems): for chatItem in chatItems { let cInfo = chatItem.chatInfo @@ -2132,7 +2396,7 @@ func processReceivedMsg(_ res: ChatEvent) async { let cInfo = chatItem.chatInfo let cItem = chatItem.chatItem if !cItem.isDeletedContent && active(user) { - await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } + _ = await MainActor.run { m.upsertChatItem(cInfo, cItem) } } if let endTask = m.messageDelivery[cItem.id] { switch cItem.meta.itemStatus { @@ -2180,6 +2444,9 @@ func processReceivedMsg(_ res: ChatEvent) async { m.decreaseGroupReportsCounter(item.deletedChatItem.chatInfo.id) } } + if let updatedChatInfo = items.last?.deletedChatItem.chatInfo { + m.updateChatInfo(updatedChatInfo) + } } case let .groupChatItemsDeleted(user, groupInfo, chatItemIDs, _, member_): await groupChatItemsDeleted(user, groupInfo, chatItemIDs, member_) @@ -2228,6 +2495,13 @@ func processReceivedMsg(_ res: ChatEvent) async { _ = m.upsertGroupMember(groupInfo, member) } } + case let .memberAcceptedByOther(user, groupInfo, _, member): + if active(user) { + await MainActor.run { + _ = m.upsertGroupMember(groupInfo, member) + m.updateGroup(groupInfo) + } + } case let .deletedMemberUser(user, groupInfo, member, withMessages): // TODO update user member if active(user) { await MainActor.run { @@ -2240,6 +2514,7 @@ func processReceivedMsg(_ res: ChatEvent) async { case let .deletedMember(user, groupInfo, byMember, deletedMember, withMessages): if active(user) { await MainActor.run { + m.updateGroup(groupInfo) _ = m.upsertGroupMember(groupInfo, deletedMember) if withMessages { m.removeMemberItems(deletedMember, byMember: byMember, groupInfo) @@ -2249,6 +2524,7 @@ func processReceivedMsg(_ res: ChatEvent) async { case let .leftMember(user, groupInfo, member): if active(user) { await MainActor.run { + m.updateGroup(groupInfo) _ = m.upsertGroupMember(groupInfo, member) } } @@ -2263,6 +2539,17 @@ func processReceivedMsg(_ res: ChatEvent) async { await MainActor.run { m.updateGroup(groupInfo) } + if m.chatId == groupInfo.id { + if groupInfo.membership.memberPending { + await MainActor.run { + m.secondaryPendingInviteeChatOpened = true + } + } else if case .memberSupport(nil) = m.secondaryIM?.groupScopeInfo { + await MainActor.run { + m.secondaryPendingInviteeChatOpened = false + } + } + } } case let .joinedGroupMember(user, groupInfo, member): if active(user) { @@ -2310,7 +2597,7 @@ func processReceivedMsg(_ res: ChatEvent) async { case let .rcvFileAccepted(user, aChatItem): // usually rcvFileAccepted is a response, but it's also an event for XFTP files auto-accepted from NSE await chatItemSimpleUpdate(user, aChatItem) // TODO when aChatItem added -// case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): // usually rcvFileAcceptedSndCancelled is a response, but it's also an event for XFTP files auto-accepted from NSE +// case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): // usually rcvFileAcceptedSndCancelled is a response, but it's also an event for legacy files auto-accepted from NSE. // await chatItemSimpleUpdate(user, aChatItem) // Task { cleanupFile(aChatItem) } case let .rcvFileStart(user, aChatItem): @@ -2549,7 +2836,7 @@ func groupChatItemsDeleted(_ user: UserRef, _ groupInfo: GroupInfo, _ chatItemID return } let im = ItemsModel.shared - let cInfo = ChatInfo.group(groupInfo: groupInfo) + let cInfo = ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil) await MainActor.run { m.decreaseGroupReportsCounter(cInfo.id, by: chatItemIDs.count) } diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index f8d69c5fc8..e1a6bb61e8 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -19,7 +19,6 @@ struct SimpleXApp: App { @Environment(\.scenePhase) var scenePhase @State private var enteredBackgroundAuthenticated: TimeInterval? = nil - @State private var appOpenUrlLater: URL? init() { DispatchQueue.global(qos: .background).sync { @@ -46,7 +45,7 @@ struct SimpleXApp: App { if AppChatState.shared.value == .active { chatModel.appOpenUrl = url } else { - appOpenUrlLater = url + chatModel.appOpenUrlLater = url } } .onAppear() { @@ -98,15 +97,15 @@ struct SimpleXApp: App { if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { await updateCallInvitations() } - if let url = appOpenUrlLater { + if let url = chatModel.appOpenUrlLater { await MainActor.run { - appOpenUrlLater = nil + chatModel.appOpenUrlLater = nil chatModel.appOpenUrl = url } } } - } else if let url = appOpenUrlLater { - appOpenUrlLater = nil + } else if let url = chatModel.appOpenUrlLater { + chatModel.appOpenUrlLater = nil chatModel.appOpenUrl = url } } @@ -159,12 +158,12 @@ struct SimpleXApp: App { if let id = chatModel.chatId, let chat = chatModel.getChat(id), !NtfManager.shared.navigatingToChat { - Task { await loadChat(chat: chat, clearItems: false) } + Task { await loadChat(chat: chat, im: ItemsModel.shared, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { await MainActor.run { chatModel.ntfContactRequest = nil } if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo { - Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) } + Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } } } } catch let error { diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift index 62a41c504a..b60842a4a0 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift @@ -22,11 +22,28 @@ struct ChatInfoToolbar: View { Image(systemName: "theatermasks").frame(maxWidth: 24, maxHeight: 24, alignment: .center).foregroundColor(.indigo) Spacer().frame(width: 16) } - ChatInfoImage( - chat: chat, - size: imageSize, - color: Color(uiColor: .tertiaryLabel) - ) + ZStack(alignment: .bottomTrailing) { + ChatInfoImage( + chat: chat, + size: imageSize, + color: Color(uiColor: .tertiaryLabel) + ) + if chat.chatStats.reportsCount > 0 { + Image(systemName: "flag.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 14, height: 14) + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .red) + } else if chat.supportUnreadCount > 0 { + Image(systemName: "flag.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 14, height: 14) + .symbolRenderingMode(.palette) + .foregroundStyle(.white, theme.colors.primary) + } + } .padding(.trailing, 4) let t = Text(cInfo.displayName).font(.headline) (cInfo.contact?.verified == true ? contactVerifiedShield + t : t) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 8194c8fe6f..77c1db341a 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -111,7 +111,8 @@ struct ChatInfoView: View { @State private var sendReceiptsUserDefault = true @State private var progressIndicator = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false - + @State private var showSecrets: Set = [] + enum ChatInfoViewAlert: Identifiable { case clearChatAlert case networkStatusAlert @@ -135,7 +136,7 @@ struct ChatInfoView: View { } } } - + var body: some View { NavigationView { ZStack { @@ -146,12 +147,12 @@ struct ChatInfoView: View { .onTapGesture { aliasTextFieldFocused = false } - + localAliasTextEdit() .listRowBackground(Color.clear) .listRowSeparator(.hidden) .padding(.bottom, 18) - + GeometryReader { g in HStack(alignment: .center, spacing: 8) { let buttonWidth = g.size.width / 4 @@ -169,7 +170,7 @@ struct ChatInfoView: View { .listRowBackground(Color.clear) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8)) - + if let customUserProfile = customUserProfile { Section(header: Text("Incognito").foregroundColor(theme.colors.secondary)) { HStack { @@ -180,7 +181,7 @@ struct ChatInfoView: View { } } } - + Section { if let code = connectionCode { verifyCodeButton(code) } contactPreferencesButton() @@ -203,19 +204,19 @@ struct ChatInfoView: View { // } } .disabled(!contact.ready || !contact.active) - + Section { ChatTTLOption(chat: chat, progressIndicator: $progressIndicator) } footer: { Text("Delete chat messages from your device.") } - + if let conn = contact.activeConn { Section { infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard") } } - + if let contactLink = contact.contactLink { Section { SimpleXLinkQRCode(uri: contactLink) @@ -232,7 +233,7 @@ struct ChatInfoView: View { .foregroundColor(theme.colors.secondary) } } - + if contact.ready && contact.active { Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { networkStatusRow() @@ -261,12 +262,12 @@ struct ChatInfoView: View { } } } - + Section { clearChatButton() deleteContactButton() } - + if developerTools { Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { infoRow("Local name", chat.chatInfo.localDisplayName) @@ -274,8 +275,9 @@ struct ChatInfoView: View { Button ("Debug delivery") { Task { do { - let info = queueInfoText(try await apiContactQueueInfo(chat.chatInfo.apiId)) - await MainActor.run { alert = .queueInfo(info: info) } + if let info = try await apiContactQueueInfo(chat.chatInfo.apiId) { + await MainActor.run { alert = .queueInfo(info: queueInfoText(info)) } + } } catch let e { logger.error("apiContactQueueInfo error: \(responseError(e))") let a = getErrorAlert(e, "Error") @@ -290,7 +292,7 @@ struct ChatInfoView: View { .navigationBarHidden(true) .disabled(progressIndicator) .opacity(progressIndicator ? 0.6 : 1) - + if progressIndicator { ProgressView().scaleEffect(2) } @@ -302,7 +304,7 @@ struct ChatInfoView: View { sendReceiptsUserDefault = currentUser.sendRcptsContacts } sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault) - + Task { do { let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId) @@ -341,7 +343,7 @@ struct ChatInfoView: View { } } .actionSheet(item: $actionSheet) { $0.actionSheet } - .sheet(item: $sheet) { + .sheet(item: $sheet) { if #available(iOS 16.0, *) { $0.content .presentationDetents([.fraction($0.fraction)]) @@ -360,41 +362,52 @@ struct ChatInfoView: View { } } } - + private func contactInfoHeader() -> some View { VStack(spacing: 8) { let cInfo = chat.chatInfo ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.vertical, 12) + // show actual display name, alias can be edited in this view + let displayName = contact.profile.displayName.trimmingCharacters(in: .whitespacesAndNewlines) + let fullName = cInfo.fullName.trimmingCharacters(in: .whitespacesAndNewlines) if contact.verified { ( Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) + textSpace - + Text(contact.profile.displayName) + + Text(displayName) .font(.largeTitle) ) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } else { - Text(contact.profile.displayName) + Text(displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } - if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName { + if fullName != "" && fullName != displayName && fullName != cInfo.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { Text(cInfo.fullName) .font(.title2) + .multilineTextAlignment(.center) + .lineLimit(3) + .padding(.bottom, 2) + } + if let descr = cInfo.shortDescr?.trimmingCharacters(in: .whitespacesAndNewlines), descr != "" { + let r = markdownText(descr, textStyle: .subheadline, showSecrets: showSecrets, backgroundColor: theme.colors.background) + msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets, centered: true, smallFont: true) .multilineTextAlignment(.center) .lineLimit(4) + .fixedSize(horizontal: false, vertical: true) } } .frame(maxWidth: .infinity, alignment: .center) } - + private func localAliasTextEdit() -> some View { TextField("Set contact name…", text: $localAlias) .disableAutocorrection(true) @@ -411,7 +424,7 @@ struct ChatInfoView: View { .multilineTextAlignment(.center) .foregroundColor(theme.colors.secondary) } - + private func setContactAlias() { Task { do { @@ -474,7 +487,7 @@ struct ChatInfoView: View { ) } } - + private func contactPreferencesButton() -> some View { NavigationLink { ContactPreferencesView( @@ -490,21 +503,20 @@ struct ChatInfoView: View { Label("Contact preferences", systemImage: "switch.2") } } - + private func sendReceiptsOption() -> some View { - Picker(selection: $sendReceipts) { + WrappedPicker(selection: $sendReceipts) { ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in Text(opt.text) } } label: { Label("Send receipts", systemImage: "checkmark.message") } - .frame(height: 36) .onChange(of: sendReceipts) { _ in setSendReceipts() } } - + private func setSendReceipts() { var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults chatSettings.sendRcpts = sendReceipts.bool() @@ -524,7 +536,7 @@ struct ChatInfoView: View { .foregroundColor(.orange) } } - + private func synchronizeConnectionButtonForce() -> some View { Button { alert = .syncConnectionForceAlert @@ -533,7 +545,7 @@ struct ChatInfoView: View { .foregroundColor(.red) } } - + private func networkStatusRow() -> some View { HStack { Text("Network status") @@ -546,14 +558,14 @@ struct ChatInfoView: View { serverImage() } } - + private func serverImage() -> some View { let status = networkModel.contactNetworkStatus(contact) return Image(systemName: status.imageName) .foregroundColor(status == .connected ? .green : theme.colors.secondary) .font(.system(size: 12)) } - + private func deleteContactButton() -> some View { Button(role: .destructive) { deleteContactDialog( @@ -569,7 +581,7 @@ struct ChatInfoView: View { .foregroundColor(Color.red) } } - + private func clearChatButton() -> some View { Button() { alert = .clearChatAlert @@ -578,7 +590,7 @@ struct ChatInfoView: View { .foregroundColor(Color.orange) } } - + private func clearChatAlert() -> Alert { Alert( title: Text("Clear conversation?"), @@ -592,14 +604,14 @@ struct ChatInfoView: View { secondaryButton: .cancel() ) } - + private func networkStatusAlert() -> Alert { Alert( title: Text("Network status"), message: Text(networkModel.contactNetworkStatus(contact).statusExplanation) ) } - + private func switchContactAddress() { Task { do { @@ -618,7 +630,7 @@ struct ChatInfoView: View { } } } - + private func abortSwitchContactAddress() { Task { do { @@ -636,7 +648,7 @@ struct ChatInfoView: View { } } } - + private func savePreferences() { Task { do { @@ -662,19 +674,18 @@ struct ChatTTLOption: View { @State private var chatItemTTL: ChatTTL = ChatTTL.chat(.seconds(0)) var body: some View { - Picker("Delete messages after", selection: $chatItemTTL) { + WrappedPicker("Delete messages after", selection: $chatItemTTL) { ForEach(ChatItemTTL.values) { ttl in Text(ttl.deleteAfterText).tag(ChatTTL.chat(ttl)) } let defaultTTL = ChatTTL.userDefault(ChatModel.shared.chatItemTTL) Text(defaultTTL.text).tag(defaultTTL) - + if case .chat(let ttl) = chatItemTTL, case .seconds = ttl { Text(ttl.deleteAfterText).tag(chatItemTTL) } } .disabled(progressIndicator) - .frame(height: 36) .onChange(of: chatItemTTL) { ttl in if ttl == currentChatItemTTL { return } setChatTTL( @@ -687,7 +698,7 @@ struct ChatTTLOption: View { let m = ChatModel.shared do { try await setChatTTL(chatType: chat.chatInfo.chatType, id: chat.chatInfo.apiId, ttl) - await loadChat(chat: chat, clearItems: true) + await loadChat(chat: chat, im: ItemsModel.shared, clearItems: true) await MainActor.run { progressIndicator = false currentChatItemTTL = chatItemTTL @@ -700,7 +711,7 @@ struct ChatTTLOption: View { } catch let error { logger.error("setChatTTL error \(responseError(error))") - await loadChat(chat: chat, clearItems: true) + await loadChat(chat: chat, im: ItemsModel.shared, clearItems: true) await MainActor.run { chatItemTTL = currentChatItemTTL progressIndicator = false @@ -833,7 +844,7 @@ private struct CallButton: View { )) } } - } else if contact.nextSendGrpInv { + } else if contact.sendMsgToConnect { showAlert(SomeAlert( alert: mkAlert( title: "Can't call contact", @@ -938,7 +949,7 @@ struct ChatWallpaperEditorSheet: View { self.chat = chat self.themes = if case let ChatInfo.direct(contact) = chat.chatInfo, let uiThemes = contact.uiThemes { uiThemes - } else if case let ChatInfo.group(groupInfo) = chat.chatInfo, let uiThemes = groupInfo.uiThemes { + } else if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, let uiThemes = groupInfo.uiThemes { uiThemes } else { ThemeModeOverrides() @@ -974,7 +985,7 @@ struct ChatWallpaperEditorSheet: View { private func themesFromChat(_ chat: Chat) -> ThemeModeOverrides { if case let ChatInfo.direct(contact) = chat.chatInfo, let uiThemes = contact.uiThemes { uiThemes - } else if case let ChatInfo.group(groupInfo) = chat.chatInfo, let uiThemes = groupInfo.uiThemes { + } else if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, let uiThemes = groupInfo.uiThemes { uiThemes } else { ThemeModeOverrides() @@ -1052,12 +1063,12 @@ struct ChatWallpaperEditorSheet: View { chat.wrappedValue = Chat.init(chatInfo: ChatInfo.direct(contact: contact)) themes = themesFromChat(chat.wrappedValue) } - } else if case var ChatInfo.group(groupInfo) = chat.wrappedValue.chatInfo { + } else if case var ChatInfo.group(groupInfo, _) = chat.wrappedValue.chatInfo { groupInfo.uiThemes = changedThemesConstant await MainActor.run { - ChatModel.shared.updateChatInfo(ChatInfo.group(groupInfo: groupInfo)) - chat.wrappedValue = Chat.init(chatInfo: ChatInfo.group(groupInfo: groupInfo)) + ChatModel.shared.updateChatInfo(ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil)) + chat.wrappedValue = Chat.init(chatInfo: ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil)) themes = themesFromChat(chat.wrappedValue) } } @@ -1137,13 +1148,13 @@ func setChatTTL(_ ttl: ChatTTL, hasPreviousTTL: Bool, onCancel: @escaping () -> } else { NSLocalizedString("Enable automatic message deletion?", comment: "alert title") } - + let message = if ttl.neverExpires { NSLocalizedString("Messages in this chat will never be deleted.", comment: "alert message") } else { NSLocalizedString("This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted.", comment: "alert message") } - + showAlert(title, message: message) { [ UIAlertAction( diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift index 02be8af73b..b2b4441646 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift @@ -12,8 +12,8 @@ import SimpleXChat struct CIChatFeatureView: View { @EnvironmentObject var m: ChatModel @Environment(\.revealed) var revealed: Bool - @ObservedObject var im = ItemsModel.shared @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel @EnvironmentObject var theme: AppTheme var chatItem: ChatItem var feature: Feature @@ -53,7 +53,7 @@ struct CIChatFeatureView: View { private func mergedFeatures() -> [FeatureInfo]? { var fs: [FeatureInfo] = [] var icons: Set = [] - if var i = m.getChatItemIndex(chatItem) { + if var i = m.getChatItemIndex(im, chatItem) { while i < im.reversedChatItems.count, let f = featureInfo(im.reversedChatItems[i]) { if !icons.contains(f.icon) { @@ -108,6 +108,7 @@ struct CIChatFeatureView_Previews: PreviewProvider { let enabled = FeatureEnabled(forUser: false, forContact: false) CIChatFeatureView( chat: Chat.sampleData, + im: ItemsModel.shared, chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary) ).environment(\.revealed, true) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index b0b404d8b5..1b9376b5db 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -278,6 +278,7 @@ func showFileErrorAlert(_ err: FileError, temporary: Bool = false) { struct CIFileView_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared let sentFile: ChatItem = ChatItem( chatDir: .directSnd, meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true), @@ -293,16 +294,16 @@ struct CIFileView_Previews: PreviewProvider { file: nil ) Group { - ChatItemView(chat: Chat.sampleData, chatItem: sentFile, scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: fileChatItemWtFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) } .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 360)) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index d30369339d..d1f49f635a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct CIImageView: View { @EnvironmentObject var m: ChatModel let chatItem: ChatItem - var scrollToItemId: ((ChatItem.ID) -> Void)? = nil + var scrollToItem: ((ChatItem.ID) -> Void)? = nil var preview: UIImage? let maxWidth: CGFloat var imgWidth: CGFloat? @@ -26,7 +26,7 @@ struct CIImageView: View { if let uiImage = getLoadedImage(file) { Group { if smallView { smallViewImageView(uiImage) } else { imageView(uiImage) } } .fullScreenCover(isPresented: $showFullScreenImage) { - FullScreenMediaView(chatItem: chatItem, scrollToItemId: scrollToItemId, image: uiImage, showView: $showFullScreenImage) + FullScreenMediaView(chatItem: chatItem, scrollToItem: scrollToItem, image: uiImage, showView: $showFullScreenImage) } .if(!smallView) { view in view.modifier(PrivacyBlur(blurred: $blurred)) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index f9dbaede63..f07e90b953 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -30,7 +30,7 @@ struct CILinkView: View { VStack(alignment: .leading, spacing: 6) { Text(linkPreview.title) .lineLimit(3) - Text(linkPreview.uri.absoluteString) + Text(linkPreview.uri) .font(.caption) .lineLimit(1) .foregroundColor(theme.colors.secondary) @@ -44,29 +44,71 @@ struct CILinkView: View { } } -func openBrowserAlert(uri: URL) { +func openBrowserAlert(uri: String) { + let (url, err) = sanitizeUri(uri) + if let url { + let uriStr = url.uri.absoluteString + showAlert( + NSLocalizedString("Open link?", comment: "alert title"), + message: uriStr.count > 160 ? "\(uriStr.prefix(160))…" : uriStr, + actions: { + if let sanitizedUri = url.sanitizedUri { + [ + cancelAlertAction, + UIAlertAction( + title: NSLocalizedString("Open full link", comment: "alert action"), + style: .default, + handler: { _ in UIApplication.shared.open(url.uri) } + ), + UIAlertAction( + title: NSLocalizedString("Open clean link", comment: "alert action"), + style: .default, + handler: { _ in UIApplication.shared.open(sanitizedUri) } + ) + ] + } else { + [ + cancelAlertAction, + UIAlertAction( + title: NSLocalizedString("Open", comment: "alert action"), + style: .default, + handler: { _ in UIApplication.shared.open(url.uri) } + ) + ] + } + } + ) + } else { + showInvalidLinkAlert(uri, error: err) + } +} + +func showInvalidLinkAlert(_ uri: String, error: String? = nil) { + let message = if let error, !error.isEmpty { + error + "\n" + uri + } else { + uri + } showAlert( - NSLocalizedString("Open link?", comment: "alert title"), - message: uri.absoluteString, - actions: {[ - UIAlertAction( - title: NSLocalizedString("Cancel", comment: "alert action"), - style: .default, - handler: { _ in } - ), - UIAlertAction( - title: NSLocalizedString("Open", comment: "alert action"), - style: .default, - handler: { _ in UIApplication.shared.open(uri) } - ) - ]} + NSLocalizedString("Invalid link", comment: "alert title"), + message: message, + actions: {[okAlertAction]} ) } +func sanitizeUri(_ s: String) -> (url: (uri: URL, sanitizedUri: URL?)?, error: String?) { + let parsed = parseSanitizeUri(s, safe: false) + return if let uri = URL(string: s), let uriInfo = parsed?.uriInfo { + (url: (uri: uri, sanitizedUri: uriInfo.sanitized.flatMap { URL(string: $0) }), error: nil) + } else { + (url: nil, error: parsed?.parseError) + } +} + struct LargeLinkPreview_Previews: PreviewProvider { static var previews: some View { let preview = LinkPreview( - uri: URL(string: "http://DuckDuckGo.com")!, + uri: "http://DuckDuckGo.com", title: "Privacy, simplified.", description: "", image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z" diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 4e5713c263..3201332c1e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -45,7 +45,7 @@ struct CIRcvDecryptionError: View { viewBody() .onAppear { // for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear - if case let .group(groupInfo) = chat.chatInfo, + if case let .group(groupInfo, _) = chat.chatInfo, case let .groupRcv(groupMember) = chatItem.chatDir { do { let (member, stats) = try apiGroupMemberInfoSync(groupInfo.apiId, groupMember.groupMemberId) @@ -83,7 +83,7 @@ struct CIRcvDecryptionError: View { } else { basicDecryptionErrorItem() } - } else if case let .group(groupInfo) = chat.chatInfo, + } else if case let .group(groupInfo, _) = chat.chatInfo, case let .groupRcv(groupMember) = chatItem.chatDir, let mem = m.getGroupMember(groupMember.groupMemberId), let memberStats = mem.wrapped.activeConn?.connectionStats { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index 715e606a74..47aee2a586 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -435,6 +435,7 @@ class VoiceItemState { struct CIVoiceView_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared let sentVoiceMessage: ChatItem = ChatItem( chatDir: .directSnd, meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true), @@ -457,10 +458,10 @@ struct CIVoiceView_Previews: PreviewProvider { duration: 30, allowMenu: Binding.constant(true) ) - ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in }, allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItemId: { _ in }, allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }, allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, scrollToItemId: { _ in }, allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentVoiceMessage, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: voiceMessageWtFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true)) } .previewLayout(.fixed(width: 360, height: 360)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift index f4e2a4135a..0b6f249b9c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift @@ -77,6 +77,7 @@ struct FramedCIVoiceView: View { struct FramedCIVoiceView_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared let sentVoiceMessage: ChatItem = ChatItem( chatDir: .directSnd, meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true), @@ -92,11 +93,11 @@ struct FramedCIVoiceView_Previews: PreviewProvider { file: CIFile.getSample(fileStatus: .sndComplete) ) Group { - ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentVoiceMessage, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: voiceMessageWithQuote, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) } .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 360)) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index b27d266d8a..c9c9952688 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -13,8 +13,10 @@ struct FramedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel var chatItem: ChatItem - var scrollToItemId: (ChatItem.ID) -> Void + var scrollToItem: (ChatItem.ID) -> Void + @Binding var scrollToItemId: ChatItem.ID? var preview: UIImage? var maxWidth: CGFloat = .infinity @State var msgWidth: CGFloat = 0 @@ -56,12 +58,16 @@ struct FramedItemView: View { if let qi = chatItem.quotedItem { ciQuoteView(qi) .simultaneousGesture(TapGesture().onEnded { - if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) { + if let ci = im.reversedChatItems.first(where: { $0.id == qi.itemId }) { withAnimation { - scrollToItemId(ci.id) + scrollToItem(ci.id) } } else if let id = qi.itemId { - scrollToItemId(id) + if (chatItem.isReport && im.secondaryIMFilter != nil) { + scrollToItemId = id + } else { + scrollToItem(id) + } } else { showQuotedItemDoesNotExistAlert() } @@ -70,7 +76,7 @@ struct FramedItemView: View { framedItemHeader(icon: "arrowshape.turn.up.forward", caption: Text(itemForwarded.text(chat.chatInfo.chatType)).italic(), pad: true) } - ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: framedMsgContentView) + ChatItemContentView(chat: chat, im: im, chatItem: chatItem, msgContentView: framedMsgContentView) .padding(chatItem.content.msgContent != nil ? 0 : 4) .overlay(DetermineWidth()) } @@ -119,7 +125,7 @@ struct FramedItemView: View { } else { switch (chatItem.content.msgContent) { case let .image(text, _): - CIImageView(chatItem: chatItem, scrollToItemId: scrollToItemId, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery) + CIImageView(chatItem: chatItem, scrollToItem: scrollToItem, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery) .overlay(DetermineWidth()) if text == "" && !chatItem.meta.isLive { Color.clear @@ -290,7 +296,7 @@ struct FramedItemView: View { private func membership() -> GroupMember? { switch chat.chatInfo { - case let .group(groupInfo: groupInfo): return groupInfo.membership + case let .group(groupInfo: groupInfo, _): return groupInfo.membership default: return nil } } @@ -386,15 +392,16 @@ func chatItemFrameContextColor(_ ci: ChatItem, _ theme: AppTheme) -> Color { struct FramedItemView_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared Group{ - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) } .previewLayout(.fixed(width: 360, height: 200)) } @@ -402,17 +409,18 @@ struct FramedItemView_Previews: PreviewProvider { struct FramedItemView_Edited_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) } .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 200)) @@ -421,17 +429,18 @@ struct FramedItemView_Edited_Previews: PreviewProvider { struct FramedItemView_Deleted_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true)) } .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 200)) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift index 10e5efa298..f243a83142 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift @@ -14,7 +14,7 @@ import AVKit struct FullScreenMediaView: View { @EnvironmentObject var m: ChatModel @State var chatItem: ChatItem - var scrollToItemId: ((ChatItem.ID) -> Void)? + var scrollToItem: ((ChatItem.ID) -> Void)? @State var image: UIImage? @State var player: AVPlayer? = nil @State var url: URL? = nil @@ -71,7 +71,7 @@ struct FullScreenMediaView: View { let w = abs(t.width) if t.height > 60 && t.height > w * 2 { showView = false - scrollToItemId?(chatItem.id) + scrollToItem?(chatItem.id) } else if w > 60 && w > abs(t.height) * 2 && !scrolling { let previous = t.width > 0 scrolling = true diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index 87a9b2ce61..c6a5d0353c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -14,6 +14,7 @@ struct MarkedDeletedItemView: View { @EnvironmentObject var theme: AppTheme @Environment(\.revealed) var revealed: Bool @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel var chatItem: ChatItem var body: some View { @@ -29,14 +30,14 @@ struct MarkedDeletedItemView: View { var mergedMarkedDeletedText: LocalizedStringKey { if !revealed, let ciCategory = chatItem.mergeCategory, - var i = m.getChatItemIndex(chatItem) { + var i = m.getChatItemIndex(im, chatItem) { var moderated = 0 var blocked = 0 var blockedByAdmin = 0 var deleted = 0 var moderatedBy: Set = [] - while i < ItemsModel.shared.reversedChatItems.count, - let ci = .some(ItemsModel.shared.reversedChatItems[i]), + while i < im.reversedChatItems.count, + let ci = .some(im.reversedChatItems[i]), ci.mergeCategory == ciCategory, let itemDeleted = ci.meta.itemDeleted { switch itemDeleted { @@ -85,6 +86,7 @@ struct MarkedDeletedItemView_Previews: PreviewProvider { Group { MarkedDeletedItemView( chat: Chat.sampleData, + im: ItemsModel.shared, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)) ).environment(\.revealed, true) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index e04584dfff..2a1b526893 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -93,7 +93,18 @@ struct MsgContentView: View { @inline(__always) private func msgContentView() -> some View { - let r = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: containerBackground, prefix: prefix) + let r = messageText( + text, + formattedText, + textStyle: textStyle, + sender: sender, + mentions: mentions, + userMemberId: userMemberId, + showSecrets: showSecrets, + commands: chat.chatInfo.useCommands && chat.chatInfo.sndReady, + backgroundColor: containerBackground, + prefix: prefix + ) let s = r.string let t: Text if let mt = meta { @@ -104,7 +115,7 @@ struct MsgContentView: View { } else { t = Text(AttributedString(s)) } - return msgTextResultView(r, t, showSecrets: $showSecrets) + return msgTextResultView(r, t, showSecrets: $showSecrets, sendCommand: { cmd in sendCommandMsg(chat, cmd) }) } @inline(__always) @@ -120,13 +131,27 @@ struct MsgContentView: View { } } -func msgTextResultView(_ r: MsgTextResult, _ t: Text, showSecrets: Binding>? = nil) -> some View { +func msgTextResultView( + _ r: MsgTextResult, + _ t: Text, + showSecrets: Binding>? = nil, + sendCommand: ((String) -> Void)? = nil, + centered: Bool = false, + smallFont: Bool = false +) -> some View { t.if(r.hasSecrets, transform: hiddenSecretsView) - .if(r.handleTaps) { $0.overlay(handleTextTaps(r.string, showSecrets: showSecrets)) } + .if(r.handleTaps) { $0.overlay(handleTextTaps(r.string, showSecrets: showSecrets, sendCommand: sendCommand, centered: centered, smallFont: smallFont)) } } +// smallFont parameter is used to pad height, otherwise CTFrameGetLines fails to see them as lines - it's needed if font is not .body @inline(__always) -private func handleTextTaps(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { +private func handleTextTaps( + _ s: NSAttributedString, + showSecrets: Binding>? = nil, + sendCommand: ((String) -> Void)? = nil, + centered: Bool, + smallFont: Bool +) -> some View { return GeometryReader { g in Rectangle() .fill(Color.clear) @@ -135,38 +160,52 @@ private func handleTextTaps(_ s: NSAttributedString, showSecrets: Binding 100 { return } let framesetter = CTFramesetterCreateWithAttributedString(s as CFAttributedString) - let path = CGPath(rect: CGRect(origin: .zero, size: g.size), transform: nil) + let paddedSize = smallFont ? CGSize(width: g.size.width, height: g.size.height + 1.0) : g.size + let path = CGPath(rect: CGRect(origin: .zero, size: paddedSize), transform: nil) let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, s.length), path, nil) let point = CGPoint(x: event.location.x, y: g.size.height - event.location.y) // Flip y for UIKit var index: CFIndex? if let lines = CTFrameGetLines(frame) as? [CTLine] { var origins = [CGPoint](repeating: .zero, count: lines.count) CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &origins) + var maxWidth: CGFloat = 0 + if centered { + for line in lines { + let bounds = CTLineGetBoundsWithOptions(line, .useOpticalBounds) + if bounds.width > maxWidth { + maxWidth = bounds.width + } + } + } for i in 0 ..< lines.count { let bounds = CTLineGetBoundsWithOptions(lines[i], .useOpticalBounds) - if bounds.offsetBy(dx: origins[i].x, dy: origins[i].y).contains(point) { - index = CTLineGetStringIndexForPosition(lines[i], point) + let offsetX = centered ? (maxWidth - bounds.width) / 2 : 0 + if bounds.offsetBy(dx: origins[i].x + offsetX, dy: origins[i].y).contains(point) { + let relativePoint = centered ? CGPoint(x: point.x - origins[i].x - offsetX, y: point.y - origins[i].y) : point + index = CTLineGetStringIndexForPosition(lines[i], relativePoint) break } } } - if let index, let (url, browser) = attributedStringLink(s, for: index) { + if let index, let (uri, browser) = attributedStringLink(s, for: index) { if browser { - openBrowserAlert(uri: url) - } else { + openBrowserAlert(uri: uri) + } else if let url = URL(string: uri) { UIApplication.shared.open(url) + } else { + showInvalidLinkAlert(uri) } } }) } - func attributedStringLink(_ s: NSAttributedString, for index: CFIndex) -> (URL, Bool)? { - var linkURL: URL? + func attributedStringLink(_ s: NSAttributedString, for index: CFIndex) -> (String, Bool)? { + var linkURL: String? var browser: Bool = false s.enumerateAttributes(in: NSRange(location: 0, length: s.length)) { attrs, range, stop in if index >= range.location && index < range.location + range.length { - if let url = attrs[linkAttrKey] as? NSURL { - linkURL = url.absoluteURL + if let url = attrs[linkAttrKey] as? String { + linkURL = url browser = attrs[webLinkAttrKey] != nil } else if let showSecrets, let i = attrs[secretAttrKey] as? Int { if showSecrets.wrappedValue.contains(i) { @@ -174,6 +213,8 @@ private func handleTextTaps(_ s: NSAttributedString, showSecrets: Binding? = nil, + backgroundColor: Color +) -> MsgTextResult { + messageText( + s, + parseSimpleXMarkdown(s), + textStyle: textStyle, + sender: sender, + preview: preview, + mentions: mentions, + userMemberId: userMemberId, + showSecrets: showSecrets, + commands: false, + backgroundColor: UIColor(backgroundColor) + ) +} + + func messageText( _ text: String, _ formattedText: [FormattedText]?, @@ -216,6 +285,7 @@ func messageText( mentions: [String: CIMention]?, userMemberId: String?, showSecrets: Set?, + commands: Bool = false, backgroundColor: UIColor, prefix: NSAttributedString? = nil ) -> MsgTextResult { @@ -288,22 +358,41 @@ func messageText( case .uri: attrs = linkAttrs() if !preview { - let s = t.lowercased() - let link = s.hasPrefix("http://") || s.hasPrefix("https://") + let link = t.hasPrefix("http://") || t.hasPrefix("https://") ? t : "https://" + t - attrs[linkAttrKey] = NSURL(string: link) + attrs[linkAttrKey] = link attrs[webLinkAttrKey] = true handleTaps = true } - case let .simplexLink(linkType, simplexUri, smpHosts): + case let .hyperLink(text, uri): attrs = linkAttrs() + if let text { t = text } if !preview { - attrs[linkAttrKey] = NSURL(string: simplexUri) + attrs[linkAttrKey] = uri + attrs[webLinkAttrKey] = true handleTaps = true } - if case .description = privacySimplexLinkModeDefault.get() { - t = simplexLinkText(linkType, smpHosts) + case let .simplexLink(text, linkType, simplexUri, smpHosts): + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = simplexUri + handleTaps = true + } + if let s = text ?? (privacySimplexLinkModeDefault.get() == .description ? linkType.description : nil) { + res.append(NSAttributedString(string: s + " ", attributes: attrs)) + italic = italic ?? UIFont(descriptor: descr.withSymbolicTraits(.traitItalic) ?? descr, size: descr.pointSize) + attrs[.font] = italic + t = viaHost(smpHosts) + } + case let .command(cmdStr): + snippet = snippet ?? UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: .regular) + attrs[.font] = snippet + t = "/" + cmdStr + if !preview && commands { + attrs[.foregroundColor] = uiLinkColor + attrs[commandAttrKey] = t + handleTaps = true } case let .mention(memberName): if let m = mentions?[memberName] { @@ -326,15 +415,16 @@ func messageText( case .email: attrs = linkAttrs() if !preview { - attrs[linkAttrKey] = NSURL(string: "mailto:" + ft.text) + attrs[linkAttrKey] = "mailto:" + ft.text handleTaps = true } case .phone: attrs = linkAttrs() if !preview { - attrs[linkAttrKey] = NSURL(string: "tel:" + t.replacingOccurrences(of: " ", with: "")) + attrs[linkAttrKey] = "tel:" + t.replacingOccurrences(of: " ", with: "") handleTaps = true } + case .unknown: () case .none: () } res.append(NSAttributedString(string: t, attributes: attrs)) @@ -361,7 +451,11 @@ private func mentionText(_ name: String) -> String { } func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String { - linkType.description + " " + "(via \(smpHosts.first ?? "?"))" + linkType.description + " " + viaHost(smpHosts) +} + +func viaHost(_ smpHosts: [String]) -> String { + "(via \(smpHosts.first ?? "?"))" } struct MsgContentView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index cd75d1b0cd..87c6ba92f8 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -230,7 +230,7 @@ struct ChatItemInfoView: View { private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { let backgroundColor = chatItemFrameColor(ci, theme) return VStack(alignment: .leading, spacing: 4) { - textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil, backgroundColor: UIColor(backgroundColor)) + textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil, backgroundColor: backgroundColor) .padding(.horizontal, 12) .padding(.vertical, 6) .background(backgroundColor) @@ -258,7 +258,7 @@ struct ChatItemInfoView: View { .frame(maxWidth: maxWidth, alignment: .leading) } - @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil, backgroundColor: UIColor) -> some View { + @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil, backgroundColor: Color) -> some View { if text != "" { TextBubble(text: text, formattedText: formattedText, sender: sender, mentions: ci.mentions, userMemberId: userMemberId, backgroundColor: backgroundColor) } else { @@ -275,11 +275,11 @@ struct ChatItemInfoView: View { var sender: String? = nil var mentions: [String: CIMention]? var userMemberId: String? - var backgroundColor: UIColor + var backgroundColor: Color @State private var showSecrets: Set = [] var body: some View { - let r = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: backgroundColor) + let r = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: UIColor(backgroundColor)) return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) } } @@ -305,7 +305,7 @@ struct ChatItemInfoView: View { private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { let backgroundColor = quotedMsgFrameColor(qi, theme) return VStack(alignment: .leading, spacing: 4) { - textBubble(qi.text, qi.formattedText, qi.getSender(nil), backgroundColor: UIColor(backgroundColor)) + textBubble(qi.text, qi.formattedText, qi.getSender(nil), backgroundColor: backgroundColor) .padding(.horizontal, 12) .padding(.vertical, 6) .background(quotedMsgFrameColor(qi, theme)) diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index f5558bcd93..5f48c18881 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -40,25 +40,31 @@ extension EnvironmentValues { struct ChatItemView: View { @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel @EnvironmentObject var theme: AppTheme @Environment(\.showTimestamp) var showTimestamp: Bool @Environment(\.revealed) var revealed: Bool var chatItem: ChatItem - var scrollToItemId: (ChatItem.ID) -> Void + var scrollToItem: (ChatItem.ID) -> Void + @Binding var scrollToItemId: ChatItem.ID? var maxWidth: CGFloat = .infinity @Binding var allowMenu: Bool init( chat: Chat, + im: ItemsModel, chatItem: ChatItem, - scrollToItemId: @escaping (ChatItem.ID) -> Void, + scrollToItem: @escaping (ChatItem.ID) -> Void, + scrollToItemId: Binding = .constant(nil), showMember: Bool = false, maxWidth: CGFloat = .infinity, allowMenu: Binding = .constant(false) ) { self.chat = chat + self.im = im self.chatItem = chatItem - self.scrollToItemId = scrollToItemId + self.scrollToItem = scrollToItem + _scrollToItemId = scrollToItemId self.maxWidth = maxWidth _allowMenu = allowMenu } @@ -66,14 +72,14 @@ struct ChatItemView: View { var body: some View { let ci = chatItem if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) { - MarkedDeletedItemView(chat: chat, chatItem: chatItem) + MarkedDeletedItemView(chat: chat, im: im, chatItem: chatItem) } else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive { if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) { EmojiItemView(chat: chat, chatItem: ci) } else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent { CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu) } else if ci.content.msgContent == nil { - ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case + ChatItemContentView(chat: chat, im: im, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case } else { framedItemView() } @@ -101,8 +107,10 @@ struct ChatItemView: View { }() return FramedItemView( chat: chat, + im: im, chatItem: chatItem, - scrollToItemId: scrollToItemId, + scrollToItem: scrollToItem, + scrollToItemId: $scrollToItemId, preview: preview, maxWidth: maxWidth, imgWidth: adjustedMaxWidth, @@ -117,6 +125,7 @@ struct ChatItemContentView: View { @EnvironmentObject var theme: AppTheme @Environment(\.revealed) var revealed: Bool @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel var chatItem: ChatItem var msgContentView: () -> Content @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @@ -140,7 +149,9 @@ struct ChatItemContentView: View { case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole) case .rcvDirectEvent: eventItemView() case .rcvGroupEvent(.memberCreatedContact): CIMemberCreatedContactView(chatItem: chatItem) + case .rcvGroupEvent(.newMemberPendingReview): CIEventView(eventText: pendingReviewEventItemText()) case .rcvGroupEvent: eventItemView() + case .sndGroupEvent(.userPendingReview): CIEventView(eventText: pendingReviewEventItemText()) case .sndGroupEvent: eventItemView() case .rcvConnEvent: eventItemView() case .sndConnEvent: eventItemView() @@ -149,7 +160,7 @@ struct ChatItemContentView: View { case let .rcvChatPreference(feature, allowed, param): CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param) case let .sndChatPreference(feature, _, _): - CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary) + CIChatFeatureView(chat: chat, im: im, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary) case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary)) case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary)) case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red) @@ -161,6 +172,7 @@ struct ChatItemContentView: View { case let .rcvDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) case .sndGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) case .rcvGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) + case .chatBanner: EmptyView() case let .invalidJSON(json): CIInvalidJSONView(json: json) } } @@ -181,6 +193,13 @@ struct ChatItemContentView: View { CIEventView(eventText: eventItemViewText(theme.colors.secondary)) } + private func pendingReviewEventItemText() -> Text { + Text(chatItem.content.text) + .font(.caption) + .foregroundColor(theme.colors.secondary) + .fontWeight(.bold) + } + private func eventItemViewText(_ secondaryColor: Color) -> Text { if !revealed, let t = mergedGroupEventText { return chatEventText(t + textSpace + chatItem.timestampText, secondaryColor) @@ -196,7 +215,7 @@ struct ChatItemContentView: View { } private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View { - CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, iconColor: iconColor) + CIChatFeatureView(chat: chat, im: im, chatItem: chatItem, feature: feature, iconColor: iconColor) } private var mergedGroupEventText: Text? { @@ -223,16 +242,21 @@ struct ChatItemContentView: View { } private func directE2EEInfoText(_ info: E2EEInfo) -> Text { - info.pqEnabled - ? Text("Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.") - .font(.caption) - .foregroundColor(theme.colors.secondary) - .fontWeight(.light) - : e2eeInfoNoPQText() + if let pqEnabled = info.pqEnabled { + pqEnabled + ? e2eeInfoText("Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.") + : e2eeInfoNoPQText() + } else { + e2eeInfoText("Messages are protected by **end-to-end encryption**.") + } } private func e2eeInfoNoPQText() -> Text { - Text("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.") + e2eeInfoText("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.") + } + + private func e2eeInfoText(_ s: LocalizedStringKey) -> Text { + Text(s) .font(.caption) .foregroundColor(theme.colors.secondary) .fontWeight(.light) @@ -256,16 +280,17 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text { struct ChatItemView_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared Group{ - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getDeletedContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)).environment(\.revealed, true) + ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)).environment(\.revealed, true) } .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 70)) @@ -275,10 +300,12 @@ struct ChatItemView_Previews: PreviewProvider { struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { static var previews: some View { + let im = ItemsModel.shared let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false), param: nil) Group{ ChatItemView( chat: Chat.sampleData, + im: im, chatItem: ChatItem( chatDir: .directRcv, meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), @@ -286,10 +313,12 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - scrollToItemId: { _ in } + scrollToItem: { _ in }, + scrollToItemId: Binding.constant(nil) ) ChatItemView( chat: Chat.sampleData, + im: im, chatItem: ChatItem( chatDir: .directRcv, meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead), @@ -297,10 +326,11 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - scrollToItemId: { _ in } + scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil) ) ChatItemView( chat: Chat.sampleData, + im: im, chatItem: ChatItem( chatDir: .directRcv, meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), @@ -308,10 +338,12 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - scrollToItemId: { _ in } + scrollToItem: { _ in }, + scrollToItemId: Binding.constant(nil) ) ChatItemView( chat: Chat.sampleData, + im: im, chatItem: ChatItem( chatDir: .directRcv, meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), @@ -319,10 +351,12 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - scrollToItemId: { _ in } + scrollToItem: { _ in }, + scrollToItemId: Binding.constant(nil) ) ChatItemView( chat: Chat.sampleData, + im: im, chatItem: ChatItem( chatDir: .directRcv, meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted(deletedTs: .now)), @@ -330,7 +364,8 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { quotedItem: nil, file: nil ), - scrollToItemId: { _ in } + scrollToItem: { _ in }, + scrollToItemId: Binding.constant(nil) ) } .environment(\.revealed, true) diff --git a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift index 07034cf8ec..93ecf870eb 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift @@ -13,8 +13,8 @@ let TRIM_KEEP_COUNT = 200 func apiLoadMessages( _ chatId: ChatId, + _ im: ItemsModel, _ pagination: ChatPagination, - _ chatState: ActiveChatState, _ search: String = "", _ openAroundItemId: ChatItem.ID? = nil, _ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange = { 0 ... 0 } @@ -22,7 +22,7 @@ func apiLoadMessages( let chat: Chat let navInfo: NavigationInfo do { - (chat, navInfo) = try await apiGetChat(chatId: chatId, pagination: pagination, search: search) + (chat, navInfo) = try await apiGetChat(chatId: chatId, scope: im.groupScopeInfo?.toChatScope(), contentTag: im.contentTag, pagination: pagination, search: search) } catch let error { logger.error("apiLoadMessages error: \(responseError(error))") return @@ -38,30 +38,31 @@ func apiLoadMessages( return } - let unreadAfterItemId = chatState.unreadAfterItemId + let unreadAfterItemId = im.chatState.unreadAfterItemId - let oldItems = Array(ItemsModel.shared.reversedChatItems.reversed()) + let oldItems = Array(im.reversedChatItems.reversed()) var newItems: [ChatItem] = [] switch pagination { case .initial: let newSplits: [Int64] = if !chat.chatItems.isEmpty && navInfo.afterTotal > 0 { [chat.chatItems.last!.id] } else { [] } - if chatModel.getChat(chat.id) == nil { + if im.secondaryIMFilter == nil && chatModel.getChat(chat.id) == nil { chatModel.addChat(chat) } await MainActor.run { - chatModel.chatItemStatuses.removeAll() - ItemsModel.shared.reversedChatItems = chat.chatItems.reversed() - chatModel.updateChatInfo(chat.chatInfo) - chatState.splits = newSplits - if !chat.chatItems.isEmpty { - chatState.unreadAfterItemId = chat.chatItems.last!.id + im.reversedChatItems = chat.chatItems.reversed() + if im.secondaryIMFilter == nil { + chatModel.updateChatInfo(chat.chatInfo) } - chatState.totalAfter = navInfo.afterTotal - chatState.unreadTotal = chat.chatStats.unreadCount - chatState.unreadAfter = navInfo.afterUnread - chatState.unreadAfterNewestLoaded = navInfo.afterUnread + im.chatState.splits = newSplits + if !chat.chatItems.isEmpty { + im.chatState.unreadAfterItemId = chat.chatItems.last!.id + } + im.chatState.totalAfter = navInfo.afterTotal + im.chatState.unreadTotal = chat.chatStats.unreadCount + im.chatState.unreadAfter = navInfo.afterUnread + im.chatState.unreadAfterNewestLoaded = navInfo.afterUnread - PreloadState.shared.clear() + im.preloadState.clear() } case let .before(paginationChatItemId, _): newItems.append(contentsOf: oldItems) @@ -71,15 +72,15 @@ func apiLoadMessages( let wasSize = newItems.count let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() } let modifiedSplits = removeDuplicatesAndModifySplitsOnBeforePagination( - unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexes + unreadAfterItemId, &newItems, newIds, im.chatState.splits, visibleItemIndexes ) let insertAt = max((indexInCurrentItems - (wasSize - newItems.count) + modifiedSplits.trimmedIds.count), 0) newItems.insert(contentsOf: chat.chatItems, at: insertAt) let newReversed: [ChatItem] = newItems.reversed() await MainActor.run { - ItemsModel.shared.reversedChatItems = newReversed - chatState.splits = modifiedSplits.newSplits - chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems) + im.reversedChatItems = newReversed + im.chatState.splits = modifiedSplits.newSplits + im.chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems) } case let .after(paginationChatItemId, _): newItems.append(contentsOf: oldItems) @@ -89,7 +90,7 @@ func apiLoadMessages( let mappedItems = mapItemsToIds(chat.chatItems) let newIds = mappedItems.0 let (newSplits, unreadInLoaded) = removeDuplicatesAndModifySplitsOnAfterPagination( - mappedItems.1, paginationChatItemId, &newItems, newIds, chat, chatState.splits + mappedItems.1, paginationChatItemId, &newItems, newIds, chat, im.chatState.splits ) let indexToAdd = min(indexInCurrentItems + 1, newItems.count) let indexToAddIsLast = indexToAdd == newItems.count @@ -97,19 +98,19 @@ func apiLoadMessages( let new: [ChatItem] = newItems let newReversed: [ChatItem] = newItems.reversed() await MainActor.run { - ItemsModel.shared.reversedChatItems = newReversed - chatState.splits = newSplits - chatState.moveUnreadAfterItem(chatState.splits.first ?? new.last!.id, new) + im.reversedChatItems = newReversed + im.chatState.splits = newSplits + im.chatState.moveUnreadAfterItem(im.chatState.splits.first ?? new.last!.id, new) // loading clear bottom area, updating number of unread items after the newest loaded item if indexToAddIsLast { - chatState.unreadAfterNewestLoaded -= unreadInLoaded + im.chatState.unreadAfterNewestLoaded -= unreadInLoaded } } case .around: var newSplits: [Int64] if openAroundItemId == nil { newItems.append(contentsOf: oldItems) - newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed) + newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, im.chatState.splits, visibleItemIndexesNonReversed) } else { newSplits = [] } @@ -120,33 +121,37 @@ func apiLoadMessages( let newReversed: [ChatItem] = newItems.reversed() let orderedSplits = newSplits await MainActor.run { - ItemsModel.shared.reversedChatItems = newReversed - chatState.splits = orderedSplits - chatState.unreadAfterItemId = chat.chatItems.last!.id - chatState.totalAfter = navInfo.afterTotal - chatState.unreadTotal = chat.chatStats.unreadCount - chatState.unreadAfter = navInfo.afterUnread + im.reversedChatItems = newReversed + im.chatState.splits = orderedSplits + im.chatState.unreadAfterItemId = chat.chatItems.last!.id + im.chatState.totalAfter = navInfo.afterTotal + im.chatState.unreadTotal = chat.chatStats.unreadCount + im.chatState.unreadAfter = navInfo.afterUnread if let openAroundItemId { - chatState.unreadAfterNewestLoaded = navInfo.afterUnread - ChatModel.shared.openAroundItemId = openAroundItemId - ChatModel.shared.chatId = chatId + im.chatState.unreadAfterNewestLoaded = navInfo.afterUnread + if im.secondaryIMFilter == nil { + ChatModel.shared.openAroundItemId = openAroundItemId // TODO [knocking] move openAroundItemId from ChatModel to ItemsModel? + ChatModel.shared.chatId = chat.id + } } else { // no need to set it, count will be wrong // chatState.unreadAfterNewestLoaded = navInfo.afterUnread } - PreloadState.shared.clear() + im.preloadState.clear() } case .last: newItems.append(contentsOf: oldItems) - let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, chatState.splits) + let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, im.chatState.splits) newItems.append(contentsOf: chat.chatItems) let items = newItems await MainActor.run { - ItemsModel.shared.reversedChatItems = items.reversed() - chatState.splits = newSplits - chatModel.updateChatInfo(chat.chatInfo) - chatState.unreadAfterNewestLoaded = 0 + im.reversedChatItems = items.reversed() + im.chatState.splits = newSplits + if im.secondaryIMFilter == nil { + chatModel.updateChatInfo(chat.chatInfo) + } + im.chatState.unreadAfterNewestLoaded = 0 } } } diff --git a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift index 0a55ed48cc..5f2102b8bc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift @@ -10,6 +10,7 @@ import SwiftUI import SimpleXChat struct MergedItems: Hashable, Equatable { + let im: ItemsModel let items: [MergedItem] let splits: [SplitRange] // chat item id, index in list @@ -23,15 +24,15 @@ struct MergedItems: Hashable, Equatable { hasher.combine("\(items.hashValue)") } - static func create(_ items: [ChatItem], _ revealedItems: Set, _ chatState: ActiveChatState) -> MergedItems { - if items.isEmpty { - return MergedItems(items: [], splits: [], indexInParentItems: [:]) + static func create(_ im: ItemsModel, _ revealedItems: Set) -> MergedItems { + if im.reversedChatItems.isEmpty { + return MergedItems(im: im, items: [], splits: [], indexInParentItems: [:]) } - let unreadCount = chatState.unreadTotal + let unreadCount = im.chatState.unreadTotal - let unreadAfterItemId = chatState.unreadAfterItemId - let itemSplits = chatState.splits + let unreadAfterItemId = im.chatState.unreadAfterItemId + let itemSplits = im.chatState.splits var mergedItems: [MergedItem] = [] // Indexes of splits here will be related to reversedChatItems, not chatModel.chatItems var splitRanges: [SplitRange] = [] @@ -40,19 +41,19 @@ struct MergedItems: Hashable, Equatable { var unclosedSplitIndex: Int? = nil var unclosedSplitIndexInParent: Int? = nil var visibleItemIndexInParent = -1 - var unreadBefore = unreadCount - chatState.unreadAfterNewestLoaded + var unreadBefore = unreadCount - im.chatState.unreadAfterNewestLoaded var lastRevealedIdsInMergedItems: BoxedValue<[Int64]>? = nil var lastRangeInReversedForMergedItems: BoxedValue>? = nil var recent: MergedItem? = nil - while index < items.count { - let item = items[index] - let prev = index >= 1 ? items[index - 1] : nil - let next = index + 1 < items.count ? items[index + 1] : nil + while index < im.reversedChatItems.count { + let item = im.reversedChatItems[index] + let prev = index >= 1 ? im.reversedChatItems[index - 1] : nil + let next = index + 1 < im.reversedChatItems.count ? im.reversedChatItems[index + 1] : nil let category = item.mergeCategory let itemIsSplit = itemSplits.contains(item.id) if item.id == unreadAfterItemId { - unreadBefore = unreadCount - chatState.unreadAfter + unreadBefore = unreadCount - im.chatState.unreadAfter } if item.isRcvNew { unreadBefore -= 1 @@ -106,18 +107,19 @@ struct MergedItems: Hashable, Equatable { // found item that is considered as a split if let unclosedSplitIndex, let unclosedSplitIndexInParent { // it was at least second split in the list - splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1)) + splitRanges.append(SplitRange(itemId: im.reversedChatItems[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1)) } unclosedSplitIndex = index unclosedSplitIndexInParent = visibleItemIndexInParent - } else if index + 1 == items.count, let unclosedSplitIndex, let unclosedSplitIndexInParent { + } else if index + 1 == im.reversedChatItems.count, let unclosedSplitIndex, let unclosedSplitIndexInParent { // just one split for the whole list, there will be no more, it's the end - splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent)) + splitRanges.append(SplitRange(itemId: im.reversedChatItems[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent)) } indexInParentItems[item.id] = visibleItemIndexInParent index += 1 } return MergedItems( + im: im, items: mergedItems, splits: splitRanges, indexInParentItems: indexInParentItems @@ -127,7 +129,6 @@ struct MergedItems: Hashable, Equatable { // Use this check to ensure that mergedItems state based on currently actual state of global // splits and reversedChatItems func isActualState() -> Bool { - let im = ItemsModel.shared // do not load anything if global splits state is different than in merged items because it // will produce undefined results in terms of loading and placement of items. // Same applies to reversedChatItems @@ -434,7 +435,7 @@ class BoxedValue: Equatable, Hashable { } @MainActor -func visibleItemIndexesNonReversed(_ listState: EndlessScrollView.ListState, _ mergedItems: MergedItems) -> ClosedRange { +func visibleItemIndexesNonReversed(_ im: ItemsModel, _ listState: EndlessScrollView.ListState, _ mergedItems: MergedItems) -> ClosedRange { let zero = 0 ... 0 let items = mergedItems.items if items.isEmpty { @@ -445,12 +446,12 @@ func visibleItemIndexesNonReversed(_ listState: EndlessScrollView.Li guard let newest, let oldest else { return zero } - let size = ItemsModel.shared.reversedChatItems.count + let size = im.reversedChatItems.count let range = size - oldest ... size - newest if range.lowerBound < 0 || range.upperBound < 0 { return zero } - // visible items mapped to their underlying data structure which is ItemsModel.shared.reversedChatItems.reversed() + // visible items mapped to their underlying data structure which is im.reversedChatItems.reversed() return range } diff --git a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift index c1a1eec7d2..2fb1c3fb35 100644 --- a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift +++ b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift @@ -9,7 +9,7 @@ import SwiftUI import SimpleXChat -func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat) async { +func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat, _ im: ItemsModel) async { await MainActor.run { loadingMoreItems.wrappedValue = true loadingBottomItems.wrappedValue = true @@ -22,27 +22,15 @@ func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Bindin } return } - await apiLoadMessages(chat.chatInfo.id, ChatPagination.last(count: 50), ItemsModel.shared.chatState) + await apiLoadMessages(chat.chatInfo.id, im, ChatPagination.last(count: 50)) await MainActor.run { loadingMoreItems.wrappedValue = false loadingBottomItems.wrappedValue = false } } -class PreloadState { - static let shared = PreloadState() - var prevFirstVisible: Int64 = Int64.min - var prevItemsCount: Int = 0 - var preloading: Bool = false - - func clear() { - prevFirstVisible = Int64.min - prevItemsCount = 0 - preloading = false - } -} - func preloadIfNeeded( + _ im: ItemsModel, _ allowLoadMoreItems: Binding, _ ignoreLoadingRequests: Binding, _ listState: EndlessScrollView.ListState, @@ -50,7 +38,7 @@ func preloadIfNeeded( loadItems: @escaping (Bool, ChatPagination) async -> Bool, loadLastItems: @escaping () async -> Void ) { - let state = PreloadState.shared + let state = im.preloadState guard !listState.isScrolling && !listState.isAnimatedScrolling, !state.preloading, listState.totalItemsCount > 0 @@ -63,7 +51,7 @@ func preloadIfNeeded( Task { defer { state.preloading = false } var triedToLoad = true - await preloadItems(mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in + await preloadItems(im, mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in triedToLoad = await loadItems(false, pagination) return triedToLoad } @@ -73,11 +61,11 @@ func preloadIfNeeded( } // it's important to ask last items when the view is fully covered with items. Otherwise, visible items from one // split will be merged with last items and position of scroll will change unexpectedly. - if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded { + if listState.itemsCanCoverScreen && !im.lastItemsLoaded { await loadLastItems() } } - } else if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded { + } else if listState.itemsCanCoverScreen && !im.lastItemsLoaded { state.preloading = true Task { defer { state.preloading = false } @@ -87,6 +75,7 @@ func preloadIfNeeded( } func preloadItems( + _ im: ItemsModel, _ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ listState: EndlessScrollView.ListState, @@ -105,7 +94,7 @@ async { let splits = mergedItems.splits let lastVisibleIndex = listState.lastVisibleItemIndex var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits) - let items: [ChatItem] = ItemsModel.shared.reversedChatItems.reversed() + let items: [ChatItem] = im.reversedChatItems.reversed() if splits.isEmpty && !items.isEmpty && lastVisibleIndex > mergedItems.items.count - remaining { lastIndexToLoadFrom = items.count - 1 } @@ -122,7 +111,7 @@ async { let sizeWas = items.count let firstItemIdWas = items.first?.id let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT)) - if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id { + if triedToLoad && sizeWas == im.reversedChatItems.count && firstItemIdWas == im.reversedChatItems.last?.id { ignoreLoadingRequests.wrappedValue = loadFromItemId return false } @@ -133,7 +122,7 @@ async { let splits = mergedItems.splits let split = splits.last(where: { $0.indexRangeInParentItems.contains(firstVisibleIndex) }) // we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom) - let reversedItems: [ChatItem] = ItemsModel.shared.reversedChatItems + let reversedItems: [ChatItem] = im.reversedChatItems if let split, split.indexRangeInParentItems.lowerBound + remaining > firstVisibleIndex { let index = split.indexRangeInReversed.lowerBound if index >= 0 { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index c136ebc01b..fa53045391 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -15,8 +15,7 @@ private let memberImageSize: CGFloat = 34 struct ChatView: View { @EnvironmentObject var chatModel: ChatModel - @ObservedObject var im = ItemsModel.shared - @State var mergedItems: BoxedValue = BoxedValue(MergedItems.create(ItemsModel.shared.reversedChatItems, [], ItemsModel.shared.chatState)) + @StateObject private var connectProgressManager = ConnectProgressManager.shared @State var revealedItems: Set = Set() @State var theme: AppTheme = buildTheme() @Environment(\.dismiss) var dismiss @@ -24,6 +23,10 @@ struct ChatView: View { @Environment(\.presentationMode) var presentationMode @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel + @State var mergedItems: BoxedValue + @State var floatingButtonModel: FloatingButtonModel + @Binding var scrollToItemId: ChatItem.ID? @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -45,7 +48,7 @@ struct ChatView: View { @State private var selectedMember: GMember? = nil // opening GroupLinkView on link button (incognito) @State private var showGroupLinkSheet: Bool = false - @State private var groupLink: CreatedConnLink? + @State private var groupLink: GroupLink? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var forwardedChatItems: [ChatItem] = [] @State private var selectedChatItems: Set? = nil @@ -55,12 +58,16 @@ struct ChatView: View { @State private var allowLoadMoreItems: Bool = false @State private var ignoreLoadingRequests: Int64? = nil @State private var animatedScrollingInProgress: Bool = false - @State private var floatingButtonModel: FloatingButtonModel = FloatingButtonModel() + @State private var showUserSupportChatSheet = false + @State private var showCommandsMenu = false + @State private var supportChatMemberInfoLinkActive = false @State private var scrollView: EndlessScrollView = EndlessScrollView(frame: .zero) @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + let userSupportScopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil) + var body: some View { if #available(iOS 16.0, *) { viewBody @@ -73,42 +80,74 @@ struct ChatView: View { private var viewBody: some View { let cInfo = chat.chatInfo + let memberSupportChat: (groupInfo: GroupInfo, member: GroupMember?)? = + if case let .group(groupInfo, .memberSupport(member)) = cInfo { + (groupInfo, member) + } else { + nil + } + let userMemberKnockingChat = memberSupportChat?.groupInfo.membership.memberPending == true return ZStack { let wallpaperImage = theme.wallpaper.type.image let wallpaperType = theme.wallpaper.type let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background) let tintColor = theme.wallpaper.tint ?? wallpaperType.defaultTintColor(theme.base) Color.clear.ignoresSafeArea(.all) - .if(wallpaperImage != nil) { view in + .if(wallpaperImage != nil && im.secondaryIMFilter == nil) { view in view.modifier( ChatViewBackground(image: wallpaperImage!, imageType: wallpaperType, background: backgroundColor, tint: tintColor) ) } VStack(spacing: 0) { ZStack(alignment: .bottomTrailing) { - chatItemsList() - if let groupInfo = chat.chatInfo.groupInfo, !composeState.message.isEmpty { - GroupMentionsView(groupInfo: groupInfo, composeState: $composeState, selectedRange: $selectedRange, keyboardVisible: $keyboardVisible) + if userMemberKnockingChat { + ZStack(alignment: .top) { + chatItemsList() + userMemberKnockingTitleBar() + } + } else { + chatItemsList() } - FloatingButtons(theme: theme, scrollView: scrollView, chat: chat, loadingMoreItems: $loadingMoreItems, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel, reloadItems: { - mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + if let groupInfo = chat.chatInfo.groupInfo, !composeState.message.isEmpty { + GroupMentionsView(im: im, groupInfo: groupInfo, composeState: $composeState, selectedRange: $selectedRange, keyboardVisible: $keyboardVisible) + } + if !chat.chatInfo.menuCommands.isEmpty { + CommandsMenuView(chat: chat, composeState: $composeState, selectedRange: $selectedRange, showCommandsMenu: $showCommandsMenu) + } + FloatingButtons(im: im, theme: theme, scrollView: scrollView, chat: chat, loadingMoreItems: $loadingMoreItems, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel, reloadItems: { + mergedItems.boxedValue = MergedItems.create(im, revealedItems) scrollView.updateItems(mergedItems.boxedValue.items) } ) } - connectingText() + if let connectInProgressText = connectProgressManager.showConnectProgress { + connectInProgressView(connectInProgressText) + } + if let connectingText { + Text(connectingText) + .font(.caption) + .foregroundColor(theme.colors.secondary) + .padding(.top) + } if selectedChatItems == nil { let reason = chat.chatInfo.userCantSendReason + let composeEnabled = ( + chat.chatInfo.sendMsgEnabled || + (chat.chatInfo.groupInfo?.nextConnectPrepared ?? false) || // allow to join prepared group without message + (chat.chatInfo.contact?.nextAcceptContactRequest ?? false) // allow to accept or reject contact request + ) ComposeView( chat: chat, + im: im, composeState: $composeState, + showCommandsMenu: $showCommandsMenu, keyboardVisible: $keyboardVisible, keyboardHiddenDate: $keyboardHiddenDate, selectedRange: $selectedRange, disabledText: reason?.composeLabel ) - .disabled(!cInfo.sendMsgEnabled) - .if(!cInfo.sendMsgEnabled) { v in + .disabled(!composeEnabled) + .if(!composeEnabled) { v in v.disabled(true).onTapGesture { AlertManager.shared.showAlertMsg( title: "You can't send messages!", @@ -118,7 +157,7 @@ struct ChatView: View { } } else { SelectedItemsBottomToolbar( - chatItems: ItemsModel.shared.reversedChatItems, + im: im, selectedChatItems: $selectedChatItems, chatInfo: chat.chatInfo, deleteItems: { forAll in @@ -129,7 +168,7 @@ struct ChatView: View { showArchiveSelectedReports = true }, moderateItems: { - if case let .group(groupInfo) = chat.chatInfo { + if case let .group(groupInfo, _) = chat.chatInfo { showModerateSelectedMessagesAlert(groupInfo) } }, @@ -140,6 +179,28 @@ struct ChatView: View { if im.showLoadingProgress == chat.id { ProgressView().scaleEffect(2) } + if case let .group(groupInfo, _) = chat.chatInfo, + case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter, + case let .memberSupport(groupMember_) = groupScopeInfo, + let groupMember = groupMember_ { + NavigationLink(isActive: $supportChatMemberInfoLinkActive) { + GroupMemberInfoView( + groupInfo: groupInfo, + chat: chat, + groupMember: GMember(groupMember), + scrollToItemId: $scrollToItemId, + openedFromSupportChat: true + ) + .navigationBarHidden(false) + .modifier(BackButton(disabled: Binding.constant(false)) { + supportChatMemberInfoLinkActive = false + }) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } } .safeAreaInset(edge: .top) { VStack(spacing: .zero) { @@ -148,7 +209,11 @@ struct ChatView: View { } .background(ToolbarMaterial.material(toolbarMaterial)) } - .navigationTitle(cInfo.chatViewName) + .navigationTitle( + memberSupportChat == nil + ? cInfo.chatViewName + : memberSupportChat?.member?.chatViewName ?? NSLocalizedString("Chat with admins", comment: "chat toolbar") + ) .background(theme.colors.background) .navigationBarTitleDisplayMode(.inline) .environmentObject(theme) @@ -169,35 +234,34 @@ struct ChatView: View { .confirmationDialog(selectedChatItems?.count == 1 ? "Archive report?" : "Archive \((selectedChatItems?.count ?? 0)) reports?", isPresented: $showArchiveSelectedReports, titleVisibility: .visible) { Button("For me", role: .destructive) { if let selected = selectedChatItems { - archiveReports(chat.chatInfo, selected.sorted(), false, deletedSelectedMessages) + archiveReports(chat, selected.sorted(), false, deletedSelectedMessages) } } - if case let ChatInfo.group(groupInfo) = chat.chatInfo, groupInfo.membership.memberActive { + if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive { Button("For all moderators", role: .destructive) { if let selected = selectedChatItems { - archiveReports(chat.chatInfo, selected.sorted(), true, deletedSelectedMessages) + archiveReports(chat, selected.sorted(), true, deletedSelectedMessages) } } } } - .appSheet(item: $selectedMember) { member in - Group { - if case let .group(groupInfo) = chat.chatInfo { - GroupMemberInfoView( - groupInfo: groupInfo, - chat: chat, - groupMember: member, - navigation: true - ) - } + .appSheet(item: $selectedMember, onDismiss: { + chatModel.secondaryIM = nil + }) { member in + if case let .group(groupInfo, _) = chat.chatInfo { + GroupMemberInfoView( + groupInfo: groupInfo, + chat: chat, + groupMember: member, + scrollToItemId: $scrollToItemId, + navigation: true + ) } } // it should be presented on top level in order to prevent a bug in SwiftUI on iOS 16 related to .focused() modifier in AddGroupMembersView's search field .appSheet(isPresented: $showAddMembersSheet) { - Group { - if case let .group(groupInfo) = cInfo { - AddGroupMembersView(chat: chat, groupInfo: groupInfo) - } + if case let .group(groupInfo, _) = cInfo { + AddGroupMembersView(chat: chat, groupInfo: groupInfo) } } .sheet(isPresented: Binding( @@ -216,7 +280,23 @@ struct ChatView: View { ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) } } + .appSheet( + isPresented: $showUserSupportChatSheet, + onDismiss: { + if chat.chatInfo.groupInfo?.membership.memberPending ?? false { + chatModel.chatId = nil + } + } + ) { + if let groupInfo = cInfo.groupInfo { + SecondaryChatView( + chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: userSupportScopeInfo), chatItems: [], chatStats: ChatStats()), + scrollToItemId: $scrollToItemId + ) + } + } .onAppear { + ConnectProgressManager.shared.cancelConnectProgress() scrollView.listState.onUpdateListener = onChatItemsUpdated selectedChatItems = nil revealedItems = Set() @@ -231,8 +311,24 @@ struct ChatView: View { } } } + // if this is the main chat of the group with the pending member (knocking) + if case let .group(groupInfo, nil) = chat.chatInfo, + groupInfo.membership.memberPending { + ItemsModel.loadSecondaryChat(chat.id, chatFilter: .groupChatScopeContext(groupScopeInfo: userSupportScopeInfo)) { + showUserSupportChatSheet = true + chatModel.secondaryPendingInviteeChatOpened = true + } + } + } + .onChange(of: chatModel.secondaryPendingInviteeChatOpened) { secondaryChatOpened in + if secondaryChatOpened { + ItemsModel.loadSecondaryChat(chat.id, chatFilter: .groupChatScopeContext(groupScopeInfo: userSupportScopeInfo)) { + showUserSupportChatSheet = true + } + } } .onChange(of: chatModel.chatId) { cId in + ConnectProgressManager.shared.cancelConnectProgress() showChatInfoSheet = false selectedChatItems = nil revealedItems = Set() @@ -245,7 +341,7 @@ struct ChatView: View { initChatView() theme = buildTheme() closeSearch() - mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + mergedItems.boxedValue = MergedItems.create(im, revealedItems) scrollView.updateItems(mergedItems.boxedValue.items) if let openAround = chatModel.openAroundItemId, let index = mergedItems.boxedValue.indexInParentItems[openAround] { @@ -262,10 +358,18 @@ struct ChatView: View { dismiss() } } + .onChange(of: chatModel.secondaryPendingInviteeChatOpened) { opened in + if im.secondaryIMFilter != nil && !opened { + Task { + try? await Task.sleep(nanoseconds: 650_000000) + dismiss() + } + } + } .onChange(of: chatModel.openAroundItemId) { openAround in if let openAround { closeSearch() - mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + mergedItems.boxedValue = MergedItems.create(im, revealedItems) scrollView.updateItems(mergedItems.boxedValue.items) chatModel.openAroundItemId = nil @@ -283,14 +387,14 @@ struct ChatView: View { } } .onDisappear { + ConnectProgressManager.shared.cancelConnectProgress() VideoPlayerView.players.removeAll() stopAudioPlayer() if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented { DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { if chatModel.chatId == nil { - chatModel.chatItemStatuses = [:] - ItemsModel.shared.reversedChatItems = [] - ItemsModel.shared.chatState.clear() + im.reversedChatItems = [] + im.chatState.clear() chatModel.groupMembers = [] chatModel.groupMembersIndexes.removeAll() chatModel.membersLoaded = false @@ -303,124 +407,253 @@ struct ChatView: View { } .toolbar { ToolbarItem(placement: .principal) { - if selectedChatItems != nil { - SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems) - } else if case let .direct(contact) = cInfo { - Button { - Task { - showChatInfoSheet = true - } - } label: { - ChatInfoToolbar(chat: chat) - } - .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) { - ChatInfoView( - chat: chat, - contact: contact, - localAlias: chat.chatInfo.localAlias, - featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), - currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), - onSearch: { focusSearch() } - ) - } - } else if case let .group(groupInfo) = cInfo { - Button { - Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } } - } label: { - ChatInfoToolbar(chat: chat) - .tint(theme.colors.primary) - } - .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) { - GroupChatInfoView( - chat: chat, - groupInfo: Binding( - get: { groupInfo }, - set: { gInfo in - chat.chatInfo = .group(groupInfo: gInfo) - chat.created = Date.now - } - ), - onSearch: { focusSearch() }, - localAlias: groupInfo.localAlias - ) - } - } else if case .local = cInfo { - ChatInfoToolbar(chat: chat) + if im.secondaryIMFilter == nil { + primaryPrincipalToolbarContent() + } else if !userMemberKnockingChat { // no toolbar while knocking chat, it's unstable on sheet + secondaryPrincipalToolbarContent() } } ToolbarItem(placement: .navigationBarTrailing) { - if selectedChatItems != nil { - Button { - withAnimation { - selectedChatItems = nil - } - } label: { - Text("Cancel") - } - } else { - switch cInfo { - case let .direct(contact): - HStack { - let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser - if callsPrefEnabled { - if chatModel.activeCall == nil { - callButton(contact, .audio, imageName: "phone") - .disabled(!contact.ready || !contact.active) - } else if let call = chatModel.activeCall, call.contact.id == cInfo.id { - endCallButton(call) - } - } - Menu { - if callsPrefEnabled && chatModel.activeCall == nil { - Button { - CallController.shared.startCall(contact, .video) - } label: { - Label("Video call", systemImage: "video") - } - .disabled(!contact.ready || !contact.active) - } - searchButton() - ToggleNtfsButton(chat: chat) - .disabled(!contact.ready || !contact.active) - } label: { - Image(systemName: "ellipsis") - } - } - case let .group(groupInfo): - HStack { - if groupInfo.canAddMembers { - if (chat.chatInfo.incognito) { - groupLinkButton() - .appSheet(isPresented: $showGroupLinkSheet) { - GroupLinkView( - groupId: groupInfo.groupId, - groupLink: $groupLink, - groupLinkMemberRole: $groupLinkMemberRole, - showTitle: true, - creatingGroup: false - ) - } - } else { - addMembersButton() - } - } - Menu { - searchButton() - ToggleNtfsButton(chat: chat) - } label: { - Image(systemName: "ellipsis") - } - } - case .local: - searchButton() - default: - EmptyView() + if im.secondaryIMFilter == nil { + primaryTrailingToolbarContent() + } else if !userMemberKnockingChat { + secondaryTrailingToolbarContent() + } + } + } + .if(im.secondaryIMFilter == nil) { v in + v.onChange(of: scrollToItemId) { itemId in + if let itemId = itemId { + dismissAllSheets(animated: false) { + scrollToItem(itemId) + scrollToItemId = nil } } } } } + private func connectInProgressView(_ s: String) -> some View { + VStack(spacing: 0) { + Divider() + + HStack(spacing: 12) { + ProgressView() + Text(s) + + Spacer() + + Button { + ConnectProgressManager.shared.cancelConnectProgress() + } label: { + Image(systemName: "multiply") + } + .tint(theme.colors.primary) + } + .padding(12) + .frame(minHeight: 54) + .frame(maxWidth: .infinity, alignment: .leading) + .background(ToolbarMaterial.material(toolbarMaterial)) + } + } + + @inline(__always) + @ViewBuilder private func primaryPrincipalToolbarContent() -> some View { + let cInfo = chat.chatInfo + if selectedChatItems != nil { + SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems) + } else if case let .direct(contact) = cInfo { + Button { + Task { + showChatInfoSheet = true + } + } label: { + ChatInfoToolbar(chat: chat) + } + .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) { + ChatInfoView( + chat: chat, + contact: contact, + localAlias: chat.chatInfo.localAlias, + featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), + currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), + onSearch: { focusSearch() } + ) + } + } else if case let .group(groupInfo, _) = cInfo { + Button { + Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } } + } label: { + ChatInfoToolbar(chat: chat) + .tint(theme.colors.primary) + } + .appSheet(isPresented: $showChatInfoSheet, onDismiss: { + chatModel.secondaryIM = nil + theme = buildTheme() + }) { + GroupChatInfoView( + chat: chat, + groupInfo: Binding( + get: { groupInfo }, + set: { gInfo in + chat.chatInfo = .group(groupInfo: gInfo, groupChatScope: nil) + chat.created = Date.now + } + ), + scrollToItemId: $scrollToItemId, + onSearch: { focusSearch() }, + localAlias: groupInfo.localAlias + ) + } + } else if case .local = cInfo { + ChatInfoToolbar(chat: chat) + } + } + + @inline(__always) + @ViewBuilder private func primaryTrailingToolbarContent() -> some View { + let cInfo = chat.chatInfo + if selectedChatItems != nil { + Button { + withAnimation { + selectedChatItems = nil + } + } label: { + Text("Cancel") + } + } else { + switch cInfo { + case let .direct(contact): + HStack { + let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser + if callsPrefEnabled { + if chatModel.activeCall == nil { + callButton(contact, .audio, imageName: "phone") + .disabled(!contact.ready || !contact.active) + } else if let call = chatModel.activeCall, call.contact.id == cInfo.id { + endCallButton(call) + } + } + Menu { + if callsPrefEnabled && chatModel.activeCall == nil { + Button { + CallController.shared.startCall(contact, .video) + } label: { + Label("Video call", systemImage: "video") + } + .disabled(!contact.ready || !contact.active) + } + searchButton() + ToggleNtfsButton(chat: chat) + .disabled(!contact.ready || !contact.active) + } label: { + Image(systemName: "ellipsis") + } + } + case let .group(groupInfo, _): + HStack { + if groupInfo.canAddMembers { + if (chat.chatInfo.incognito) { + groupLinkButton() + .appSheet(isPresented: $showGroupLinkSheet) { + GroupLinkView( + groupId: groupInfo.groupId, + groupLink: $groupLink, + groupLinkMemberRole: $groupLinkMemberRole, + showTitle: true, + creatingGroup: false + ) + } + } else { + addMembersButton() + } + } + Menu { + searchButton() + ToggleNtfsButton(chat: chat) + } label: { + Image(systemName: "ellipsis") + } + } + case .local: + searchButton() + default: + EmptyView() + } + } + } + + @inline(__always) + @ViewBuilder private func secondaryPrincipalToolbarContent() -> some View { + if selectedChatItems != nil { + SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems) + } else { + switch im.secondaryIMFilter { + case let .groupChatScopeContext(groupScopeInfo): + switch groupScopeInfo { + case let .memberSupport(groupMember_): + if let groupMember = groupMember_ { + Button { + supportChatMemberInfoLinkActive = true + } label: { + MemberSupportChatToolbar(groupMember: groupMember) + } + } else { + textChatToolbar("Chat with admins") + } + case .reports: + textChatToolbar("Member reports") + } + case let .msgContentTagContext(contentTag): + switch contentTag { + case .report: + textChatToolbar("Member reports") + default: + EmptyView() + } + case .none: + EmptyView() + } + } + } + + @inline(__always) + @ViewBuilder private func secondaryTrailingToolbarContent() -> some View { + if selectedChatItems != nil { + Button { + withAnimation { + selectedChatItems = nil + } + } label: { + Text("Cancel") + } + } else { + searchButton() + } + } + + @inline(__always) + private func userMemberKnockingTitleBar() -> some View { + VStack(spacing: 0) { + Text("Chat with admins") + .font(.headline) + .foregroundColor(theme.colors.onBackground) + .padding(.top, 8) + .padding(.bottom, 14) + .frame(maxWidth: .infinity) + .background(ToolbarMaterial.material(toolbarMaterial)) + Divider() + } + } + + func textChatToolbar(_ text: LocalizedStringKey) -> some View { + Text(text) + .font(.headline) + .lineLimit(1) + .foregroundColor(theme.colors.onBackground) + .frame(width: 220) + } + private func initChatView() { let cInfo = chat.chatInfo // This check prevents the call to apiContactInfo after the app is suspended, and the database is closed. @@ -451,19 +684,19 @@ struct ChatView: View { floatingButtonModel.updateOnListChange(scrollView.listState) } - private func scrollToItemId(_ itemId: ChatItem.ID) { + private func scrollToItem(_ itemId: ChatItem.ID) { Task { do { var index = mergedItems.boxedValue.indexInParentItems[itemId] if index == nil { let pagination = ChatPagination.around(chatItemId: itemId, count: ChatPagination.PRELOAD_COUNT * 2) - let oldSize = ItemsModel.shared.reversedChatItems.count + let oldSize = im.reversedChatItems.count let triedToLoad = await loadChatItems(chat, pagination) if !triedToLoad { return } var repeatsLeft = 50 - while oldSize == ItemsModel.shared.reversedChatItems.count && repeatsLeft > 0 { + while oldSize == im.reversedChatItems.count && repeatsLeft > 0 { try await Task.sleep(nanoseconds: 20_000000) repeatsLeft -= 1 } @@ -473,7 +706,7 @@ struct ChatView: View { closeKeyboardAndRun { Task { await MainActor.run { animatedScrollingInProgress = true } - await scrollView.scrollToItemAnimated(min(ItemsModel.shared.reversedChatItems.count - 1, index)) + await scrollView.scrollToItemAnimated(min(im.reversedChatItems.count - 1, index)) await MainActor.run { animatedScrollingInProgress = false } } } @@ -539,31 +772,48 @@ struct ChatView: View { case let .single(item, _, _): item.item case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue.last!.item } - let voiceNoFrame = voiceWithoutFrame(ci) - let maxWidth = cInfo.chatType == .group - ? voiceNoFrame - ? (g.size.width - 28) - 42 - : (g.size.width - 28) * 0.84 - 42 - : voiceNoFrame - ? (g.size.width - 32) - : (g.size.width - 32) * 0.84 - return ChatItemWithMenu( - chat: $chat, - index: index, - isLastItem: index == mergedItems.boxedValue.items.count - 1, - chatItem: ci, - scrollToItemId: scrollToItemId, - merged: mergedItem, - maxWidth: maxWidth, - composeState: $composeState, - selectedMember: $selectedMember, - showChatInfoSheet: $showChatInfoSheet, - revealedItems: $revealedItems, - selectedChatItems: $selectedChatItems, - forwardedChatItems: $forwardedChatItems, - searchText: $searchText, - closeKeyboardAndRun: closeKeyboardAndRun - ) + return Group { + if case .chatBanner = ci.content { + VStack { + ChatBannerView(chat: $chat) + .padding(.bottom, 90) + .padding(.top, 8) + + let listItem = mergedItem.newest() + if let prevItem = listItem.prevItem { + DateSeparator(date: prevItem.meta.itemTs).padding(8) + } + } + } else { + let voiceNoFrame = voiceWithoutFrame(ci) + let maxWidth = cInfo.chatType == .group + ? voiceNoFrame + ? (g.size.width - 28) - 42 + : (g.size.width - 28) * 0.84 - 42 + : voiceNoFrame + ? (g.size.width - 32) + : (g.size.width - 32) * 0.84 + ChatItemWithMenu( + im: im, + chat: $chat, + index: index, + isLastItem: index == mergedItems.boxedValue.items.count - 1, + chatItem: ci, + scrollToItem: scrollToItem, + scrollToItemId: $scrollToItemId, + merged: mergedItem, + maxWidth: maxWidth, + composeState: $composeState, + selectedMember: $selectedMember, + showChatInfoSheet: $showChatInfoSheet, + revealedItems: $revealedItems, + selectedChatItems: $selectedChatItems, + forwardedChatItems: $forwardedChatItems, + searchText: $searchText, + closeKeyboardAndRun: closeKeyboardAndRun + ) + } + } // crashes on Cell size calculation without this line .environmentObject(ChatModel.shared) .environmentObject(theme) // crashes without this line when scrolling to the first unread in EndlessScrollVIew @@ -580,7 +830,7 @@ struct ChatView: View { } } .onChange(of: im.reversedChatItems) { items in - mergedItems.boxedValue = MergedItems.create(items, revealedItems, im.chatState) + mergedItems.boxedValue = MergedItems.create(im, revealedItems) scrollView.updateItems(mergedItems.boxedValue.items) if im.itemAdded { im.itemAdded = false @@ -592,7 +842,7 @@ struct ChatView: View { } } .onChange(of: revealedItems) { revealed in - mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealed, im.chatState) + mergedItems.boxedValue = MergedItems.create(im, revealed) scrollView.updateItems(mergedItems.boxedValue.items) } .onChange(of: chat.id) { _ in @@ -611,23 +861,164 @@ struct ChatView: View { } } - @ViewBuilder private func connectingText() -> some View { - if case let .direct(contact) = chat.chatInfo, - !contact.sndReady, - contact.active, - !contact.nextSendGrpInv { - Text("connecting…") - .font(.caption) - .foregroundColor(theme.colors.secondary) - .padding(.top) - } else { - EmptyView() + struct ChatBannerView: View { + @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_CHAT_ITEM_ROUNDNESS) private var roundness = defaultChatItemRoundness + @Binding @ObservedObject var chat: Chat + @State private var showSecrets: Set = [] + + var body: some View { + let v = VStack(spacing: 8) { + ChatInfoImage(chat: chat, size: alertProfileImageSize) + + Text(chat.chatInfo.displayName) + .font(.title3) + .multilineTextAlignment(.center) + .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: 240) + + let fullName = chat.chatInfo.fullName.trimmingCharacters(in: .whitespacesAndNewlines) + if fullName != "" && fullName != chat.chatInfo.displayName && fullName != chat.chatInfo.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { + Text(chat.chatInfo.fullName) + .font(.subheadline) + .multilineTextAlignment(.center) + .lineLimit(3) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: 260) + } + + if let shortDescr = chat.chatInfo.shortDescr { + let r = markdownText(shortDescr, textStyle: .subheadline, showSecrets: showSecrets, backgroundColor: theme.colors.background) + msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets, centered: true, smallFont: true) + .multilineTextAlignment(.center) + .lineLimit(4) + .fixedSize(horizontal: false, vertical: true) + .padding(.horizontal) + } + + if let chatContext { + Text(chatContext) + .font(.callout) + .foregroundColor(theme.colors.secondary) + .padding(.top, 8) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(theme.appColors.receivedMessage) + .clipShape(RoundedRectangle(cornerRadius: msgRectMaxRadius * roundness)) + if let (label, connLink) = chatAddress() { + v.contextMenu { + Button { + let shareItems: [Any] = [connLink] + showShareSheet(items: shareItems) + } label: { + Label(label, systemImage: "square.and.arrow.up") + } + } + .padding(.horizontal) + } else { + v.padding(.horizontal) + } + + } + + func chatAddress() -> (label: LocalizedStringKey, connLink: String)? { + switch chat.chatInfo { + case let .direct(contact): + if !contact.nextConnectPrepared && !contact.nextAcceptContactRequest { + let connLink: String? = if let pct = contact.preparedContact, case .con = pct.uiConnLinkType { + pct.connLinkToConnect.simplexChatUri() + } else { + contact.profile.contactLink + } + if let connLink { + return ("SimpleX address", connLink) + } + } + case let .group(groupInfo, _): + if !groupInfo.nextConnectPrepared { + if let pg = groupInfo.preparedGroup { + let connLink = pg.connLinkToConnect.simplexChatUri() + switch groupInfo.businessChat?.chatType { + case .none: return ("Group link", connLink) + case .business: return ("Business address", connLink) + default: () + } + } + } + default: () + } + return nil + } + + var chatContext: LocalizedStringKey? { + switch chat.chatInfo { + case let .direct(contact): + if contact.nextConnectPrepared, let linkType = contact.preparedContact?.uiConnLinkType { + switch linkType { + case .inv: + "Tap Connect to chat" + case .con: + contact.isBot ? "Tap Connect to use bot" : "Tap Connect to send request" + } + } else if contact.nextAcceptContactRequest { + "Accept contact request" + } else if case .bot = contact.profile.peerType { + "Bot" + } else { + "Your contact" + } + case let .group(groupInfo, _): + switch groupInfo.businessChat?.chatType { + case .none: + if groupInfo.nextConnectPrepared { + "Tap Join group" + } else { + switch (groupInfo.membership.memberStatus) { + case .memInvited: "Join group" + case .memCreator: "Your group" + default: "Group" + } + } + case .business: + if groupInfo.nextConnectPrepared { + "Tap Connect to chat" + } else { + "Business connection" + } + case .customer: + "Your business contact" + } + default: nil + } + } + } + + private var connectingText: LocalizedStringKey? { + switch (chat.chatInfo) { + case let .direct(contact): + if !contact.sndReady && contact.active && !contact.sendMsgToConnect && !contact.nextAcceptContactRequest { + (contact.preparedContact?.uiConnLinkType == .con && !contact.isBot) || contact.contactGroupMemberId != nil + ? "contact should accept…" + : "connecting…" + } else { + nil + } + case let .group(groupInfo, _): + switch (groupInfo.membership.memberStatus) { + case .memUnknown: groupInfo.preparedGroup?.connLinkStartedConnection == true ? "connecting…" : nil + case .memAccepted: "connecting…" + default: nil + } + default: nil } } private func updateWithInitiallyLoadedItems() { if mergedItems.boxedValue.items.isEmpty { - mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, ItemsModel.shared.chatState) + mergedItems.boxedValue = MergedItems.create(im, revealedItems) } let unreadIndex = mergedItems.boxedValue.items.lastIndex(where: { $0.hasUnread() }) let unreadItemId: Int64? = if let unreadIndex { mergedItems.boxedValue.items[unreadIndex].newest().item.id } else { nil } @@ -647,8 +1038,8 @@ struct ChatView: View { private func searchTextChanged(_ s: String) { Task { - await loadChat(chat: chat, search: s) - mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + await loadChat(chat: chat, im: im, search: s) + mergedItems.boxedValue = MergedItems.create(im, revealedItems) await MainActor.run { scrollView.updateItems(mergedItems.boxedValue.items) } @@ -663,79 +1054,8 @@ struct ChatView: View { } } - class FloatingButtonModel: ObservableObject { - @Published var unreadAbove: Int = 0 - @Published var unreadBelow: Int = 0 - @Published var isNearBottom: Bool = true - @Published var date: Date? = nil - @Published var isDateVisible: Bool = false - var hideDateWorkItem: DispatchWorkItem? = nil - - func updateOnListChange(_ listState: EndlessScrollView.ListState) { - let lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(listState) - let unreadBelow = if let lastVisibleItem { - max(0, ItemsModel.shared.chatState.unreadTotal - lastVisibleItem.unreadBefore) - } else { - 0 - } - let unreadAbove = ItemsModel.shared.chatState.unreadTotal - unreadBelow - let date: Date? = - if let lastVisible = listState.visibleItems.last { - Calendar.current.startOfDay(for: lastVisible.item.oldest().item.meta.itemTs) - } else { - nil - } - - // set the counters and date indicator - DispatchQueue.main.async { [weak self] in - guard let it = self else { return } - it.setDate(visibility: true) - it.unreadAbove = unreadAbove - it.unreadBelow = unreadBelow - it.date = date - } - - // set floating button indication mode - let nearBottom = listState.firstVisibleItemIndex < 1 - if nearBottom != self.isNearBottom { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in - self?.isNearBottom = nearBottom - } - } - - // hide Date indicator after 1 second of no scrolling - hideDateWorkItem?.cancel() - let workItem = DispatchWorkItem { [weak self] in - guard let it = self else { return } - it.setDate(visibility: false) - it.hideDateWorkItem = nil - } - DispatchQueue.main.async { [weak self] in - self?.hideDateWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem) - } - } - - func resetDate() { - date = nil - isDateVisible = false - } - - private func setDate(visibility isVisible: Bool) { - if isVisible { - if !isNearBottom, - !isDateVisible, - let date, !Calendar.current.isDateInToday(date) { - withAnimation { self.isDateVisible = true } - } - } else if isDateVisible { - withAnimation { self.isDateVisible = false } - } - } - - } - private struct FloatingButtons: View { + @ObservedObject var im: ItemsModel let theme: AppTheme let scrollView: EndlessScrollView let chat: Chat @@ -751,7 +1071,7 @@ struct ChatView: View { var body: some View { ZStack(alignment: .top) { - if let date = model.date { + if let date = model.date, date.timeIntervalSince1970 > 0 { DateSeparator(date: date) .padding(.vertical, 4).padding(.horizontal, 8) .background(.thinMaterial) @@ -780,7 +1100,7 @@ struct ChatView: View { .contextMenu { Button { Task { - await markChatRead(chat) + await markChatRead(im, chat) } } label: { Label("Mark read", systemImage: "checkmark") @@ -805,7 +1125,7 @@ struct ChatView: View { } } .onTapGesture { - if loadingBottomItems || !ItemsModel.shared.lastItemsLoaded { + if loadingBottomItems || !im.lastItemsLoaded { requestedTopScroll = false requestedBottomScroll = true } else { @@ -825,7 +1145,7 @@ struct ChatView: View { } } .onChange(of: loadingBottomItems) { loading in - if !loading && requestedBottomScroll && ItemsModel.shared.lastItemsLoaded { + if !loading && requestedBottomScroll && im.lastItemsLoaded { requestedBottomScroll = false scrollToBottom() } @@ -835,9 +1155,9 @@ struct ChatView: View { private func scrollToTopUnread() { Task { - if !ItemsModel.shared.chatState.splits.isEmpty { + if !im.chatState.splits.isEmpty { await MainActor.run { loadingMoreItems = true } - await loadChat(chatId: chat.id, openAroundItemId: nil, clearItems: false) + await loadChat(chatId: chat.id, im: im, openAroundItemId: nil, clearItems: false) await MainActor.run { reloadItems() } if let index = listState.items.lastIndex(where: { $0.hasUnread() }) { await MainActor.run { animatedScrollingInProgress = true } @@ -947,7 +1267,7 @@ struct ChatView: View { private func addMembersButton() -> some View { Button { - if case let .group(gInfo) = chat.chatInfo { + if case let .group(gInfo, _) = chat.chatInfo { Task { await chatModel.loadGroupMembers(gInfo) { showAddMembersSheet = true } } } } label: { @@ -957,11 +1277,12 @@ struct ChatView: View { private func groupLinkButton() -> some View { Button { - if case let .group(gInfo) = chat.chatInfo { + if case let .group(gInfo, _) = chat.chatInfo { Task { do { - if let link = try apiGetGroupLink(gInfo.groupId) { - (groupLink, groupLinkMemberRole) = link + if let gLink = try apiGetGroupLink(gInfo.groupId) { + groupLink = gLink + groupLinkMemberRole = gLink.acceptMemberRole } } catch let error { logger.error("ChatView apiGetGroupLink: \(responseError(error))") @@ -1008,6 +1329,7 @@ struct ChatView: View { let (validItems, confirmation) = try await apiPlanForwardChatItems( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, + scope: chat.chatInfo.groupChatScope(), itemIds: Array(selectedChatItems) ) if let confirmation { @@ -1097,7 +1419,6 @@ struct ChatView: View { } func openForwardingSheet(_ items: [Int64]) async { - let im = ItemsModel.shared var items = Set(items) var fci = [ChatItem]() for reversedChatItem in im.reversedChatItems { @@ -1136,11 +1457,11 @@ struct ChatView: View { private func loadChatItemsUnchecked(_ chat: Chat, _ pagination: ChatPagination) async -> Bool { await apiLoadMessages( chat.chatInfo.id, + im, pagination, - im.chatState, searchText, nil, - { visibleItemIndexesNonReversed(scrollView.listState, mergedItems.boxedValue) } + { visibleItemIndexesNonReversed(im, scrollView.listState, mergedItems.boxedValue) } ) return true } @@ -1152,11 +1473,12 @@ struct ChatView: View { func onChatItemsUpdated() { if !mergedItems.boxedValue.isActualState() { - //logger.debug("Items are not actual, waiting for the next update: \(String(describing: mergedItems.boxedValue.splits)) \(ItemsModel.shared.chatState.splits), \(mergedItems.boxedValue.indexInParentItems.count) vs \(ItemsModel.shared.reversedChatItems.count)") + //logger.debug("Items are not actual, waiting for the next update: \(String(describing: mergedItems.boxedValue.splits)) \(im.chatState.splits), \(mergedItems.boxedValue.indexInParentItems.count) vs \(im.reversedChatItems.count)") return } floatingButtonModel.updateOnListChange(scrollView.listState) preloadIfNeeded( + im, $allowLoadMoreItems, $ignoreLoadingRequests, scrollView.listState, @@ -1170,13 +1492,14 @@ struct ChatView: View { }, loadLastItems: { if !loadingMoreItems { - await loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) + await loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat, im) } } ) } private struct ChatItemWithMenu: View { + @ObservedObject var im: ItemsModel @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileRadius = defaultProfileImageCorner @@ -1185,7 +1508,8 @@ struct ChatView: View { let index: Int let isLastItem: Bool let chatItem: ChatItem - let scrollToItemId: (ChatItem.ID) -> Void + let scrollToItem: (ChatItem.ID) -> Void + @Binding var scrollToItemId: ChatItem.ID? let merged: MergedItem let maxWidth: CGFloat @Binding var composeState: ComposeState @@ -1261,8 +1585,6 @@ struct ChatView: View { } var body: some View { - let im = ItemsModel.shared - let last = isLastItem ? im.reversedChatItems.last : nil let listItem = merged.newest() let item = listItem.item @@ -1271,7 +1593,7 @@ struct ChatView: View { } else { nil } - let showAvatar = shouldShowAvatar(item, listItem.nextItem) + let showAvatar = shouldShowAvatar(item, merged.oldest().nextItem) let single = switch merged { case .single: true default: false @@ -1306,12 +1628,12 @@ struct ChatView: View { let (itemIds, unreadMentions) = unreadItemIds(range) if !itemIds.isEmpty { waitToMarkRead { - await apiMarkChatItemsRead(chat.chatInfo, itemIds, mentionsRead: unreadMentions) + await apiMarkChatItemsRead(im, chat.chatInfo, itemIds, mentionsRead: unreadMentions) } } } else if chatItem.isRcvNew { waitToMarkRead { - await apiMarkChatItemsRead(chat.chatInfo, [chatItem.id], mentionsRead: chatItem.meta.userMention ? 1 : 0) + await apiMarkChatItemsRead(im, chat.chatInfo, [chatItem.id], mentionsRead: chatItem.meta.userMention ? 1 : 0) } } } @@ -1333,7 +1655,6 @@ struct ChatView: View { } private func unreadItemIds(_ range: ClosedRange) -> ([ChatItem.ID], Int) { - let im = ItemsModel.shared var unreadItems: [ChatItem.ID] = [] var unreadMentions: Int = 0 @@ -1424,7 +1745,7 @@ struct ChatView: View { ) -> some View { let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2 if case let .groupRcv(member) = ci.chatDir, - case .group = chat.chatInfo { + case let .group(groupInfo, _) = chat.chatInfo { if showAvatar { VStack(alignment: .leading, spacing: 4) { if ci.content.showMemberName { @@ -1435,22 +1756,27 @@ struct ChatView: View { } else { (nil, 1) } - if memCount == 1 && member.memberRole > .member { + if memCount == 1 && (member.memberRole > .member || ci.meta.showGroupAsSender) { + let (name, role) = if ci.meta.showGroupAsSender { + (groupInfo.chatViewName, NSLocalizedString("group", comment: "shown on group welcome message")) + } else { + (member.chatViewName, member.memberRole.text) + } Group { if #available(iOS 16.0, *) { MemberLayout(spacing: 16, msgWidth: msgWidth) { - Text(member.chatViewName) + Text(name) .lineLimit(1) - Text(member.memberRole.text) + Text(role) .fontWeight(.semibold) .lineLimit(1) .padding(.trailing, 8) } } else { HStack(spacing: 16) { - Text(member.chatViewName) + Text(name) .lineLimit(1) - Text(member.memberRole.text) + Text(role) .fontWeight(.semibold) .lineLimit(1) .layoutPriority(1) @@ -1477,17 +1803,24 @@ struct ChatView: View { .padding(.trailing, 12) } HStack(alignment: .top, spacing: 10) { - MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) - .simultaneousGesture(TapGesture().onEnded { - if let mem = m.getGroupMember(member.groupMemberId) { - selectedMember = mem - } else { - let mem = GMember.init(member) - m.groupMembers.append(mem) - m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 - selectedMember = mem - } - }) + if ci.meta.showGroupAsSender { + ProfileImage(imageStr: groupInfo.image, iconName: groupInfo.chatIconName, size: memberImageSize, backgroundColor: theme.colors.background) + .simultaneousGesture(TapGesture().onEnded { + showChatInfoSheet = true + }) + } else { + MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) + .simultaneousGesture(TapGesture().onEnded { + if let mem = m.getGroupMember(member.groupMemberId) { + selectedMember = mem + } else { + let mem = GMember.init(member) + m.groupMembers.append(mem) + m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 + selectedMember = mem + } + }) + } chatItemWithMenu(ci, range, maxWidth, itemSeparation) .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } } @@ -1546,8 +1879,10 @@ struct ChatView: View { } ChatItemView( chat: chat, + im: im, chatItem: ci, - scrollToItemId: scrollToItemId, + scrollToItem: scrollToItem, + scrollToItemId: $scrollToItemId, maxWidth: maxWidth, allowMenu: $allowMenu ) @@ -1583,14 +1918,14 @@ struct ChatView: View { .confirmationDialog(archivingReports?.count == 1 ? "Archive report?" : "Archive \(archivingReports?.count ?? 0) reports?", isPresented: $showArchivingReports, titleVisibility: .visible) { Button("For me", role: .destructive) { if let reports = self.archivingReports { - archiveReports(chat.chatInfo, reports.sorted(), false) + archiveReports(chat, reports.sorted(), false) self.archivingReports = [] } } - if case let ChatInfo.group(groupInfo) = chat.chatInfo, groupInfo.membership.memberActive { + if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive { Button("For all moderators", role: .destructive) { if let reports = self.archivingReports { - archiveReports(chat.chatInfo, reports.sorted(), true) + archiveReports(chat, reports.sorted(), true) self.archivingReports = [] } } @@ -1636,7 +1971,7 @@ struct ChatView: View { }) } switch chat.chatInfo { - case let .group(groupInfo): + case let .group(groupInfo, _): v.contextMenu { ReactionContextMenu( groupInfo: groupInfo, @@ -1659,7 +1994,7 @@ struct ChatView: View { @ViewBuilder private func menu(_ ci: ChatItem, _ range: ClosedRange?, live: Bool) -> some View { - if case let .group(gInfo) = chat.chatInfo, ci.isReport, ci.meta.itemDeleted == nil { + if case let .group(gInfo, _) = chat.chatInfo, ci.isReport, ci.meta.itemDeleted == nil { if ci.chatDir != .groupSnd, gInfo.membership.memberRole >= .moderator { archiveReportButton(ci) } @@ -1718,7 +2053,7 @@ struct ChatView: View { if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) { moderateButton(ci, groupInfo) } else if ci.meta.itemDeleted == nil && chat.groupFeatureEnabled(.reports), - case let .group(gInfo) = chat.chatInfo, + case let .group(gInfo, _) = chat.chatInfo, gInfo.membership.memberRole == .member && !live && composeState.voiceMessageRecordingState == .noRecording { @@ -1829,6 +2164,7 @@ struct ChatView: View { let chatItem = try await apiChatItemReaction( type: cInfo.chatType, id: cInfo.apiId, + scope: cInfo.groupChatScope(), itemId: ci.id, add: add, reaction: reaction @@ -1888,7 +2224,7 @@ struct ChatView: View { } label: { Label( NSLocalizedString("Save", comment: "chat item action"), - systemImage: file.cryptoArgs == nil ? "square.and.arrow.down" : "lock.open" + systemImage: "square.and.arrow.down" ) } } @@ -1942,11 +2278,11 @@ struct ChatView: View { Task { do { let cInfo = chat.chatInfo - let ciInfo = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, itemId: ci.id) + let ciInfo = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemId: ci.id) await MainActor.run { chatItemInfo = ciInfo } - if case let .group(gInfo) = chat.chatInfo { + if case let .group(gInfo, _) = chat.chatInfo { await m.loadGroupMembers(gInfo) } } catch let error { @@ -2000,13 +2336,13 @@ struct ChatView: View { private func deleteButton(_ ci: ChatItem, label: LocalizedStringKey = "Delete") -> Button { Button(role: .destructive) { if !revealed, - let currIndex = m.getChatItemIndex(ci), + let currIndex = m.getChatItemIndex(im, ci), let ciCategory = ci.mergeCategory { let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory) if let range = itemsRange(currIndex, prevHidden) { var itemIds: [Int64] = [] for i in range { - itemIds.append(ItemsModel.shared.reversedChatItems[i].id) + itemIds.append(im.reversedChatItems[i].id) } showDeleteMessages = true deletingItems = itemIds @@ -2144,12 +2480,12 @@ struct ChatView: View { selectedChatItems = selectedChatItems ?? [] var itemIds: [Int64] = [] if !revealed, - let currIndex = m.getChatItemIndex(ci), + let currIndex = m.getChatItemIndex(im, ci), let ciCategory = ci.mergeCategory { let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory) if let range = itemsRange(currIndex, prevHidden) { for i in range { - itemIds.append(ItemsModel.shared.reversedChatItems[i].id) + itemIds.append(im.reversedChatItems[i].id) } } else { itemIds.append(ci.id) @@ -2183,6 +2519,7 @@ struct ChatView: View { try await apiDeleteChatItems( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, + scope: chat.chatInfo.groupChatScope(), itemIds: [di.id], mode: mode ) @@ -2199,6 +2536,7 @@ struct ChatView: View { if deletedItem.isActiveReport { m.decreaseGroupReportsCounter(chat.chatInfo.id) } + m.updateChatInfo(itemDeletion.deletedChatItem.chatInfo) } } } @@ -2237,14 +2575,14 @@ struct ChatView: View { if searchIsNotBlank { goToItemInnerButton(alignStart, "magnifyingglass", touchInProgress: touchInProgress) { closeKeyboardAndRun { - ItemsModel.shared.loadOpenChatNoWait(chat.id, chatItem.id) + im.loadOpenChatNoWait(chat.id, chatItem.id) } } } else if let chatTypeApiIdMsgId { goToItemInnerButton(alignStart, "arrow.right", touchInProgress: touchInProgress) { closeKeyboardAndRun { let (chatType, apiId, msgId) = chatTypeApiIdMsgId - ItemsModel.shared.loadOpenChatNoWait("\(chatType.rawValue)\(apiId)", msgId) + im.loadOpenChatNoWait("\(chatType.rawValue)\(apiId)", msgId) } } } @@ -2271,6 +2609,84 @@ struct ChatView: View { } } +class FloatingButtonModel: ObservableObject { + @ObservedObject var im: ItemsModel + + public init(im: ItemsModel) { + self.im = im + } + + @Published var unreadAbove: Int = 0 + @Published var unreadBelow: Int = 0 + @Published var isNearBottom: Bool = true + @Published var date: Date? = nil + @Published var isDateVisible: Bool = false + var hideDateWorkItem: DispatchWorkItem? = nil + + func updateOnListChange(_ listState: EndlessScrollView.ListState) { + let lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(listState) + let unreadBelow = if let lastVisibleItem { + max(0, im.chatState.unreadTotal - lastVisibleItem.unreadBefore) + } else { + 0 + } + let unreadAbove = im.chatState.unreadTotal - unreadBelow + let date: Date? = + if let lastVisible = listState.visibleItems.last { + Calendar.current.startOfDay(for: lastVisible.item.oldest().item.meta.itemTs) + } else { + nil + } + + // set the counters and date indicator + DispatchQueue.main.async { [weak self] in + guard let it = self else { return } + it.setDate(visibility: true) + it.unreadAbove = unreadAbove + it.unreadBelow = unreadBelow + it.date = date + } + + // set floating button indication mode + let nearBottom = listState.firstVisibleItemIndex < 1 + if nearBottom != self.isNearBottom { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in + self?.isNearBottom = nearBottom + } + } + + // hide Date indicator after 1 second of no scrolling + hideDateWorkItem?.cancel() + let workItem = DispatchWorkItem { [weak self] in + guard let it = self else { return } + it.setDate(visibility: false) + it.hideDateWorkItem = nil + } + DispatchQueue.main.async { [weak self] in + self?.hideDateWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem) + } + } + + func resetDate() { + date = nil + isDateVisible = false + } + + private func setDate(visibility isVisible: Bool) { + if isVisible { + if !isNearBottom, + !isDateVisible, + let date, !Calendar.current.isDateInToday(date) { + withAnimation { self.isDateVisible = true } + } + } else if isDateVisible { + withAnimation { self.isDateVisible = false } + } + } + +} + private func broadcastDeleteButtonText(_ chat: Chat) -> LocalizedStringKey { chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone" } @@ -2292,6 +2708,7 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe try await apiDeleteChatItems( type: chatInfo.chatType, id: chatInfo.apiId, + scope: chatInfo.groupChatScope(), itemIds: itemIds, mode: mode ) @@ -2300,15 +2717,18 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe await MainActor.run { for di in deletedItems { if let toItem = di.toChatItem { - _ = ChatModel.shared.upsertChatItem(chat.chatInfo, toItem.chatItem) + _ = ChatModel.shared.upsertChatItem(chatInfo, toItem.chatItem) } else { ChatModel.shared.removeChatItem(chatInfo, di.deletedChatItem.chatItem) } let deletedItem = di.deletedChatItem.chatItem if deletedItem.isActiveReport { - ChatModel.shared.decreaseGroupReportsCounter(chat.chatInfo.id) + ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id) } } + if let updatedChatInfo = deletedItems.last?.deletedChatItem.chatInfo { + ChatModel.shared.updateChatInfo(updatedChatInfo) + } } await onSuccess() } catch { @@ -2318,8 +2738,9 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe } } -func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) { +func archiveReports(_ chat: Chat, _ itemIds: [Int64], _ forAll: Bool, _ onSuccess: @escaping () async -> Void = {}) { if itemIds.count > 0 { + let chatInfo = chat.chatInfo Task { do { let deleted = try await apiDeleteReceivedReports( @@ -2340,6 +2761,9 @@ func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _ ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id) } } + if let updatedChatInfo = deleted.last?.deletedChatItem.chatInfo { + ChatModel.shared.updateChatInfo(updatedChatInfo) + } } await onSuccess() } catch { @@ -2353,7 +2777,7 @@ private func buildTheme() -> AppTheme { if let cId = ChatModel.shared.chatId, let chat = ChatModel.shared.getChat(cId) { let perChatTheme = if case let .direct(contact) = chat.chatInfo { contact.uiThemes?.preferredMode(!AppTheme.shared.colors.isLight) - } else if case let .group(groupInfo) = chat.chatInfo { + } else if case let .group(groupInfo, _) = chat.chatInfo { groupInfo.uiThemes?.preferredMode(!AppTheme.shared.colors.isLight) } else { nil as ThemeModeOverride? @@ -2506,7 +2930,7 @@ func updateChatSettings(_ chat: Chat, chatSettings: ChatSettings) { case var .direct(contact): contact.chatSettings = chatSettings ChatModel.shared.updateContact(contact) - case var .group(groupInfo): + case var .group(groupInfo, _): groupInfo.chatSettings = chatSettings ChatModel.shared.updateGroup(groupInfo) default: () @@ -2523,7 +2947,8 @@ struct ChatView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.chatId = "@1" - ItemsModel.shared.reversedChatItems = [ + let im = ItemsModel.shared + im.reversedChatItems = [ ChatItem.getSample(1, .directSnd, .now, "hello"), ChatItem.getSample(2, .directRcv, .now, "hi"), ChatItem.getSample(3, .directRcv, .now, "hi there"), @@ -2535,7 +2960,13 @@ struct ChatView_Previews: PreviewProvider { ChatItem.getSample(9, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") ] @State var showChatInfo = false - return ChatView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])) - .environmentObject(chatModel) + return ChatView( + chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), + im: im, + mergedItems: BoxedValue(MergedItems.create(im, [])), + floatingButtonModel: FloatingButtonModel(im: im), + scrollToItemId: Binding.constant(nil) + ) + .environmentObject(chatModel) } } diff --git a/apps/ios/Shared/Views/Chat/CommandsMenuView.swift b/apps/ios/Shared/Views/Chat/CommandsMenuView.swift new file mode 100644 index 0000000000..525bf5725c --- /dev/null +++ b/apps/ios/Shared/Views/Chat/CommandsMenuView.swift @@ -0,0 +1,187 @@ +// +// CommandsMenuView.swift +// SimpleX (iOS) +// +// Created by EP on 03/08/2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +let COMMAND_ROW_SIZE: CGFloat = 48 +let MAX_VISIBLE_COMMAND_ROWS: CGFloat = 5.8 + +struct CommandsMenuView: View { + @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme + @ObservedObject var chat: Chat + @Binding var composeState: ComposeState + @Binding var selectedRange: NSRange + @Binding var showCommandsMenu: Bool + + @State private var currentCommands: [ChatBotCommand] = [] + @State private var menuTreeBackPath: [(label: String, commands: [ChatBotCommand])] = [] + @State private var keywordWidth: CGFloat = 0 + + var body: some View { + ZStack(alignment: .bottom) { + if !currentCommands.isEmpty { + Color.white.opacity(0.01) + .edgesIgnoringSafeArea(.all) + .onTapGesture { + showCommandsMenu = false + currentCommands = [] + menuTreeBackPath = [] + } + VStack(spacing: 0) { + Spacer() + let cmdsCount = currentCommands.count + (menuTreeBackPath.isEmpty ? 0 : 1) + let scroll = ScrollView { + VStack(spacing: 0) { + if let prev = menuTreeBackPath.last { + Divider() + menuLabelRow(prev) + } + ForEach(currentCommands, id: \.self, content: commandRow) + } + } + .frame(maxWidth: .infinity, maxHeight: COMMAND_ROW_SIZE * min(MAX_VISIBLE_COMMAND_ROWS, CGFloat(cmdsCount))) + .background(theme.colors.background) + + if #available(iOS 16.0, *) { + scroll.scrollDismissesKeyboard(.never) + } else { + scroll + } + } + .onPreferenceChange(DetermineWidth.Key.self) { keywordWidth = $0 } + } + } + .onChange(of: composeState.message) { message in + let msg = message.trimmingCharacters(in: .whitespaces) + if msg == "/" { + currentCommands = chat.chatInfo.menuCommands + } else if msg.first == "/" { + currentCommands = filterShownCommands(chat.chatInfo.menuCommands, msg.dropFirst()) + } else { + showCommandsMenu = false + currentCommands = [] + } + menuTreeBackPath = [] + } + .onChange(of: showCommandsMenu) { show in + currentCommands = show ? chat.chatInfo.menuCommands : [] + menuTreeBackPath = [] + } + } + + private func menuLabelRow(_ prev: (label: String, commands: [ChatBotCommand])) -> some View { + HStack { + Image(systemName: "chevron.left") + .foregroundColor(theme.colors.secondary) + Text(prev.label) + .fontWeight(.medium) + .frame(maxWidth: .infinity) + } + .padding(.horizontal) + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: COMMAND_ROW_SIZE, alignment: .center) + .contentShape(Rectangle()) + .onTapGesture { + if !menuTreeBackPath.isEmpty { + currentCommands = menuTreeBackPath.removeLast().commands + } + } + } + + @ViewBuilder + private func commandRow(_ command: ChatBotCommand) -> some View { + Divider() + switch command { + case let .command(keyword, label, params): + HStack { + Text(label) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .leading) + Text("/" + keyword) + .font(.subheadline) + .lineLimit(1) + .foregroundColor(theme.colors.secondary) + .frame(minWidth: keywordWidth, alignment: .trailing) + .overlay(DetermineWidth()) + } + .padding(.horizontal) + .frame(height: COMMAND_ROW_SIZE, alignment: .center) + .contentShape(Rectangle()) + .onTapGesture { + if let params { + composeState.message = "/\(keyword) \(params)" + selectedRange = NSRange(location: composeState.message.count, length: 0) + } else { + composeState.message = "" + sendCommandMsg(chat, "/\(keyword)") + } + showCommandsMenu = false + currentCommands = [] + menuTreeBackPath = [] + } + case let .menu(label, cmds): + HStack { + Text(label) + .fontWeight(.medium) + .lineLimit(1) + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(theme.colors.secondary) + } + .padding(.horizontal) + .frame(height: COMMAND_ROW_SIZE, alignment: .center) + .contentShape(Rectangle()) + .onTapGesture { + menuTreeBackPath.append((label: label, commands: currentCommands)) + currentCommands = cmds + } + } + } + + private func filterShownCommands(_ commands: [ChatBotCommand], _ msg: String.SubSequence) -> [ChatBotCommand] { + var cmds: [ChatBotCommand] = [] + for command in commands { + switch command { + case let .command(keyword, _, _): + if keyword.starts(with: msg) { + cmds.append(command) + } + case let .menu(_, innerCmds): + cmds.append(contentsOf: filterShownCommands(innerCmds, msg)) + } + } + return cmds + } +} + +func sendCommandMsg(_ chat: Chat, _ cmd: String) { + if chat.chatInfo.sndReady { + Task { + if let chatItems = await apiSendMessages( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + scope: chat.chatInfo.groupChatScope(), + composedMessages: [ComposedMessage(msgContent: .text(cmd))] + ) { + await MainActor.run { + for ci in chatItems { + ChatModel.shared.addChatItem(chat.chatInfo, ci) + } + } + } + } + } else { + showAlert( + NSLocalizedString("You can't send messages!", comment: "alert title"), + message: NSLocalizedString("To send commands you must be connected.", comment: "alert message"), + actions: { [okAlertAction] } + ) + } +} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index e629a984df..878ebd9cbf 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -49,7 +49,7 @@ struct ComposeLinkView: View { VStack(alignment: .center, spacing: 4) { Text(linkPreview.title) .lineLimit(1) - Text(linkPreview.uri.absoluteString) + Text(linkPreview.uri) .font(.caption) .lineLimit(1) .foregroundColor(theme.colors.secondary) @@ -63,7 +63,7 @@ struct ComposeLinkView: View { struct SmallLinkPreview_Previews: PreviewProvider { static var previews: some View { let preview = LinkPreview( - uri: URL(string: "http://DuckDuckGo.com")!, + uri: "http://DuckDuckGo.com", title: "Privacy, simplified.", description: "", image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z" diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 8993de886f..683dea0f56 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -1,10 +1,3 @@ -// -// ComposeView.swift -// SimpleX -// -// Created by Evgeny on 13/03/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// import SwiftUI import SimpleXChat @@ -51,7 +44,8 @@ struct ComposeState { var contextItem: ComposeContextItem var voiceMessageRecordingState: VoiceMessageRecordingState var inProgress = false - var useLinkPreviews: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) + var progressByTimeout = false + var useLinkPreviews = true var mentions: MentionedMembers = [:] init( @@ -114,7 +108,7 @@ struct ComposeState { mentions: mentions ?? self.mentions ) } - + func mentionMemberName(_ name: String) -> String { var n = 0 var tryName = name @@ -124,11 +118,11 @@ struct ComposeState { } return tryName } - + var memberMentions: [String: Int64] { self.mentions.compactMapValues { $0.memberRef?.groupMemberId } } - + var editing: Bool { switch contextItem { case .editingItem: return true @@ -149,14 +143,14 @@ struct ComposeState { default: return false } } - + var reporting: Bool { switch contextItem { case .reportedItem: return true default: return false } } - + var submittingValidReport: Bool { switch contextItem { case let .reportedItem(_, reason): @@ -167,13 +161,13 @@ struct ComposeState { default: return false } } - + var sendEnabled: Bool { switch preview { case let .mediaPreviews(media): return !media.isEmpty case .voicePreview: return voiceMessageRecordingState == .finished case .filePreview: return true - default: return !message.isEmpty || forwarding || liveMessage != nil || submittingValidReport + default: return !whitespaceOnly || forwarding || liveMessage != nil || submittingValidReport } } @@ -254,7 +248,11 @@ struct ComposeState { } var empty: Bool { - message == "" && noPreview + whitespaceOnly && noPreview + } + + var whitespaceOnly: Bool { + message.allSatisfy { $0.isWhitespace } } } @@ -323,16 +321,18 @@ struct ComposeView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat + @ObservedObject var im: ItemsModel @Binding var composeState: ComposeState + @Binding var showCommandsMenu: Bool @Binding var keyboardVisible: Bool @Binding var keyboardHiddenDate: Date @Binding var selectedRange: NSRange var disabledText: LocalizedStringKey? = nil - @State var linkUrl: URL? = nil + @State var linkUrl: String? = nil @State var hasSimplexLink: Bool = false - @State var prevLinkUrl: URL? = nil - @State var pendingLinkUrl: URL? = nil + @State var prevLinkUrl: String? = nil + @State var pendingLinkUrl: String? = nil @State var cancelledLinks: Set = [] @Environment(\.colorScheme) private var colorScheme @@ -352,23 +352,46 @@ struct ComposeView: View { @UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false + @AppStorage(GROUP_DEFAULT_PRIVACY_SANITIZE_LINKS, store: groupDefaults) private var privacySanitizeLinks = false + @State private var updatingCompose = false var body: some View { VStack(spacing: 0) { Divider() - if chat.chatInfo.contact?.nextSendGrpInv ?? false { - ContextInvitingContactMemberView() + + if chat.chatInfo.nextConnectPrepared, + let user = chatModel.currentUser { + ContextProfilePickerView( + chat: chat, + selectedUser: user + ) Divider() } - + + if let groupInfo = chat.chatInfo.groupInfo, + case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter, + case let .memberSupport(member) = groupScopeInfo, + let member = member, + member.memberPending, + composeState.contextItem == .noContextItem, + composeState.noPreview { + ContextPendingMemberActionsView( + groupInfo: groupInfo, + member: member + ) + Divider() + } + if case let .reportedItem(_, reason) = composeState.contextItem { reportReasonView(reason) Divider() } // preference checks should match checks in forwarding list - let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) - let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files) + let simplexLinkProhibited = im.secondaryIMFilter == nil && hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) + let fileProhibited = im.secondaryIMFilter == nil && composeState.attachmentPreview && !chat.groupFeatureEnabled(.files) let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice) + let disableSendButton = simplexLinkProhibited || fileProhibited || voiceProhibited if simplexLinkProhibited { msgNotAllowedView("SimpleX links not allowed", icon: "link") Divider() @@ -385,72 +408,46 @@ struct ComposeView: View { case (true, .voicePreview): EmptyView() // ? we may allow playback when editing is allowed default: previewView() } - HStack (alignment: .bottom) { - let b = Button { - showChooseSource = true - } label: { - Image(systemName: "paperclip") - .resizable() - } - .disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) - .frame(width: 25, height: 25) - .padding(.bottom, 16) - .padding(.leading, 12) - .tint(theme.colors.primary) - if case let .group(g) = chat.chatInfo, - !g.fullGroupPreferences.files.on(for: g.membership) { - b.disabled(true).onTapGesture { - AlertManager.shared.showAlertMsg( - title: "Files and media prohibited!", - message: "Only group owners can enable files and media." - ) - } - } else { - b - } - ZStack(alignment: .leading) { - SendMessageView( - composeState: $composeState, - selectedRange: $selectedRange, - sendMessage: { ttl in - sendMessage(ttl: ttl) - resetLinkPreview() - }, - sendLiveMessage: chat.chatInfo.chatType != .local ? sendLiveMessage : nil, - updateLiveMessage: updateLiveMessage, - cancelLiveMessage: { - composeState.liveMessage = nil - chatModel.removeLiveDummy() - }, - nextSendGrpInv: chat.chatInfo.contact?.nextSendGrpInv ?? false, - voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice), - disableSendButton: simplexLinkProhibited || fileProhibited || voiceProhibited, - showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert, - startVoiceMessageRecording: { - Task { - await startVoiceMessageRecording() - } - }, - finishVoiceMessageRecording: finishVoiceMessageRecording, - allowVoiceMessagesToContact: allowVoiceMessagesToContact, - timedMessageAllowed: chat.chatInfo.featureEnabled(.timedMessages), - onMediaAdded: { media in if !media.isEmpty { chosenMedia = media }}, - keyboardVisible: $keyboardVisible, - keyboardHiddenDate: $keyboardHiddenDate, - sendButtonColor: chat.chatInfo.incognito - ? .indigo.opacity(colorScheme == .dark ? 1 : 0.7) - : theme.colors.primary - ) - .padding(.trailing, 12) - .disabled(!chat.chatInfo.sendMsgEnabled) - if let disabledText { - Text(disabledText) - .italic() - .foregroundColor(theme.colors.secondary) - .padding(.horizontal, 12) + let contact = chat.chatInfo.contact + + if chat.chatInfo.groupInfo?.nextConnectPrepared == true { + if chat.chatInfo.groupInfo?.businessChat == nil { + connectButtonView("Join group", icon: "person.2.fill", connect: connectPreparedGroup) + } else { + sendContactRequestView(disableSendButton, icon: "briefcase.fill", sendRequest: connectPreparedGroup) + } + } else if contact?.nextSendGrpInv == true { + contextSendMessageToConnect("Send direct message to connect") + Divider() + HStack (alignment: .center) { + attachmentAndCommandsButtons().disabled(true) + sendMessageView(disableSendButton, sendToConnect: sendMemberContactInvitation) + } + .padding(.horizontal, 12) + } else if let contact, + contact.nextConnectPrepared == true, + let linkType = contact.preparedContact?.uiConnLinkType { + switch linkType { + case .inv: + connectButtonView("Connect", icon: "person.fill.badge.plus", connect: sendConnectPreparedContact) + case .con: + if contact.isBot { + connectButtonView("Connect", icon: "bolt.fill", connect: sendConnectPreparedContact) + } else { + sendContactRequestView(disableSendButton, icon: "person.fill.badge.plus", sendRequest: sendConnectPreparedContactRequest) } } + } else if contact?.nextAcceptContactRequest == true, let crId = contact?.contactRequestId { + ContextContactRequestActionsView(contactRequestId: crId) + } else if let ct = contact, ct.nextAcceptContactRequest, let groupDirectInv = ct.groupDirectInv { + ContextMemberContactActionsView(contact: ct, groupDirectInv: groupDirectInv) + } else { + HStack (alignment: .center) { + attachmentAndCommandsButtons() + sendMessageView(disableSendButton) + } + .padding(.horizontal, 12) } } .background { @@ -459,19 +456,40 @@ struct ComposeView: View { .ignoresSafeArea(.all, edges: .bottom) } .onChange(of: composeState.message) { msg in - let parsedMsg = parseSimpleXMarkdown(msg) - composeState = composeState.copy(parsedMessage: parsedMsg ?? FormattedText.plain(msg)) - if composeState.linkPreviewAllowed { - if msg.count > 0 { + if updatingCompose { + updatingCompose = false + return + } + var parsedMsg = parseSimpleXMarkdown(msg) + if privacySanitizeLinks, let parsed = parsedMsg { + let r = sanitizeMessage(parsed) + if let sanitizedPos = r.sanitizedPos { + updatingCompose = true + composeState = composeState.copy(message: r.message, parsedMessage: r.parsedMsg) + if sanitizedPos < selectedRange.location { + selectedRange = NSRange(location: sanitizedPos, length: 0) + } + parsedMsg = r.parsedMsg + } else { + composeState = composeState.copy(parsedMessage: parsedMsg) + } + } else { + composeState = composeState.copy(parsedMessage: parsedMsg ?? FormattedText.plain(msg)) + } + if composeState.linkPreviewAllowed && UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) { + if !msg.isEmpty { showLinkPreview(parsedMsg) } else { resetLinkPreview() hasSimplexLink = false + composeState = composeState.copy(preview: .noPreview) } - } else if msg.count > 0 && !chat.groupFeatureEnabled(.simplexLinks) { - (_, hasSimplexLink) = getSimplexLink(parsedMsg) } else { - hasSimplexLink = false + resetLinkPreview() + hasSimplexLink = !msg.isEmpty && !chat.groupFeatureEnabled(.simplexLinks) && getMessageLinks(parsedMsg).hasSimplexLink + if composeState.linkPreviewAllowed { + composeState = composeState.copy(preview: .noPreview) + } } } .onChange(of: chat.chatInfo.sendMsgEnabled) { sendEnabled in @@ -481,6 +499,15 @@ struct ComposeView: View { clearState() } } + .onChange(of: composeState.inProgress) { inProgress in + if inProgress { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + composeState.progressByTimeout = composeState.inProgress + } + } else { + composeState.progressByTimeout = false + } + } .confirmationDialog("Attach", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true @@ -616,6 +643,243 @@ struct ComposeView: View { } } + private func connectButtonView(_ label: LocalizedStringKey, icon: String, connect: @escaping () -> Void) -> some View { + Button(action: connect) { + ZStack(alignment: .trailing) { + Label(label, systemImage: icon) + .frame(maxWidth: .infinity) + if composeState.progressByTimeout { + ProgressView() + .padding() + } + } + } + .frame(height: 60) + .disabled(composeState.inProgress) + } + + private func sendContactRequestView(_ disableSendButton: Bool, icon: String, sendRequest: @escaping () -> Void) -> some View { + HStack (alignment: .center) { + sendMessageView( + disableSendButton, + placeholder: NSLocalizedString("Add message", comment: "placeholder for sending contact request"), + sendToConnect: sendRequest + ) + if composeState.whitespaceOnly { + Button(action: sendRequest) { + HStack { + Text("Connect").fontWeight(.medium) + Image(systemName: icon) + } + } + .padding(.horizontal, 8) + .disabled(composeState.inProgress) + } + } + .padding(.horizontal, 12) + } + + private func sendMessageView(_ disableSendButton: Bool, placeholder: String? = nil, sendToConnect: (() -> Void)? = nil) -> some View { + ZStack(alignment: .leading) { + SendMessageView( + placeholder: placeholder, + composeState: $composeState, + selectedRange: $selectedRange, + sendMessage: { ttl in + sendMessage(ttl: ttl) + resetLinkPreview() + }, + sendLiveMessage: chat.chatInfo.chatType != .local ? sendLiveMessage : nil, + updateLiveMessage: updateLiveMessage, + cancelLiveMessage: { + composeState.liveMessage = nil + chatModel.removeLiveDummy() + }, + sendToConnect: sendToConnect, + hideSendButton: chat.chatInfo.nextConnect && chat.chatInfo.contact?.nextSendGrpInv != true && composeState.whitespaceOnly, + voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice), + disableSendButton: disableSendButton, + showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert, + startVoiceMessageRecording: { + Task { + await startVoiceMessageRecording() + } + }, + finishVoiceMessageRecording: finishVoiceMessageRecording, + allowVoiceMessagesToContact: allowVoiceMessagesToContact, + timedMessageAllowed: chat.chatInfo.featureEnabled(.timedMessages), + onMediaAdded: { media in if !media.isEmpty { chosenMedia = media }}, + keyboardVisible: $keyboardVisible, + keyboardHiddenDate: $keyboardHiddenDate, + sendButtonColor: chat.chatInfo.incognito + ? .indigo.opacity(colorScheme == .dark ? 1 : 0.7) + : theme.colors.primary + ) + .disabled(!chat.chatInfo.sendMsgEnabled) + + if let disabledText { + Text(disabledText) + .italic() + .foregroundColor(theme.colors.secondary) + .padding(.horizontal, 12) + } + } + } + + @ViewBuilder private func attachmentAndCommandsButtons() -> some View { + let msg = composeState.message.trimmingCharacters(in: .whitespaces) + let showAttachment = chat.chatInfo.contact?.profile.peerType != .bot || chat.chatInfo.featureEnabled(.files) + let showCommands = chat.chatInfo.useCommands && (!showAttachment || msg.isEmpty || msg.starts(with: "/")) + if showCommands { + commandsButton() + } + if showAttachment { + attachmentButton() + .padding(.trailing, 3) + .if(showCommands) { v in v.padding(.leading, 3) } + } + } + + private func commandsButton() -> some View { + Button { + showCommandsMenu.toggle() + } label: { + Text(verbatim: "//") + .font(.title3) + .italic() + .contentShape(Rectangle()) + } + .disabled(!chat.chatInfo.sendMsgEnabled || chat.chatInfo.menuCommands.isEmpty) + .frame(width: 25, height: 25) + .tint(theme.colors.primary) + .padding(.bottom, 2) + } + + @ViewBuilder private func attachmentButton() -> some View { + let b = Button { + showChooseSource = true + } label: { + Image(systemName: "paperclip") + .resizable() + } + .disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled) + .frame(width: 25, height: 25) + .tint(theme.colors.primary) + if im.secondaryIMFilter == nil, + !chat.chatInfo.featureEnabled(.files) { + b.disabled(true).onTapGesture { + AlertManager.shared.showAlertMsg( + title: "Files and media prohibited!", + message: chat.chatInfo.groupInfo == nil ? nil : "Only group owners can enable files and media." + ) + } + } else { + b + } + } + + private func sendMemberContactInvitation() { + Task { + do { + await MainActor.run { hideKeyboard() } + if let mc = connectCheckLinkPreview() { + await sending() + let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc) + await MainActor.run { + self.chatModel.updateContact(contact) + clearState() + NetworkModel.shared.setContactNetworkStatus(contact, .connected) + } + } else { + AlertManager.shared.showAlertMsg(title: "Empty message!") + } + } catch { + await MainActor.run { composeState.inProgress = false } + logger.error("ChatView.sendMemberContactInvitation error: \(error.localizedDescription)") + AlertManager.shared.showAlertMsg(title: "Error sending member contact invitation", message: "Error: \(responseError(error))") + } + } + } + + private func sendConnectPreparedContactRequest() { + hideKeyboard() + let empty = composeState.whitespaceOnly + AlertManager.shared.showAlert(Alert( + title: Text("Send contact request?"), + message: Text("You will be able to send messages **only after your request is accepted**."), + primaryButton: .default( + Text(empty ? "Send request without message" : "Send request"), + action: sendConnectPreparedContact + ), + secondaryButton: + empty + ? .cancel(Text("Add message"), action: hideKeyboard) + : .cancel() + )) + } + + private func sendConnectPreparedContact() { + Task { + await MainActor.run { hideKeyboard() } + await sending() + let mc = connectCheckLinkPreview() + let incognito = chat.chatInfo.profileChangeProhibited ? chat.chatInfo.incognito : incognitoDefault + if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognito, msg: mc) { + await MainActor.run { + self.chatModel.updateContact(contact) + NetworkModel.shared.setContactNetworkStatus(contact, .connected) + clearState() + } + } else { + await MainActor.run { composeState.inProgress = false } + } + } + } + + private func connectPreparedGroup() { + Task { + await MainActor.run { hideKeyboard() } + await sending() + let mc = connectCheckLinkPreview() + let incognito = chat.chatInfo.profileChangeProhibited ? chat.chatInfo.incognito : incognitoDefault + if let groupInfo = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognito, msg: mc) { + await MainActor.run { + self.chatModel.updateGroup(groupInfo) + clearState() + } + } else { + await MainActor.run { composeState.inProgress = false } + } + } + } + + @inline(__always) + private func connectCheckLinkPreview() -> MsgContent? { + let msgText = composeState.message.trimmingCharacters(in: .whitespacesAndNewlines) + return msgText.isEmpty ? nil : checkLinkPreview_(msgText) + } + + @inline(__always) + private func checkLinkPreview() -> MsgContent { + checkLinkPreview_(composeState.message.trimmingCharacters(in: .whitespacesAndNewlines)) + } + + private func checkLinkPreview_(_ msgText: String) -> MsgContent { + switch (composeState.preview) { + case let .linkPreview(linkPreview: linkPreview): + if let parsedMsg = parseSimpleXMarkdown(msgText), + let url = getMessageLinks(parsedMsg).url, + let linkPreview = linkPreview, + url == linkPreview.uri { + return .link(text: msgText, preview: linkPreview) + } else { + return .text(msgText) + } + default: + return .text(msgText) + } + } + private func addMediaContent(_ content: UploadContent) async { if let img = await resizeImageToStrSize(content.uiImage, maxDataSize: 14000) { var newMedia: [(String, UploadContent?)] = [] @@ -752,8 +1016,19 @@ struct ComposeView: View { .frame(maxWidth: .infinity, alignment: .leading) .background(.thinMaterial) } - - + + private func contextSendMessageToConnect(_ s: LocalizedStringKey) -> some View { + HStack { + Image(systemName: "message") + .foregroundColor(theme.colors.secondary) + Text(s) + } + .padding(12) + .frame(minHeight: 54) + .frame(maxWidth: .infinity, alignment: .leading) + .background(ToolbarMaterial.material(toolbarMaterial)) + } + private func reportReasonView(_ reason: ReportReason) -> some View { let reportText = switch reason { case .spam: NSLocalizedString("Report spam: only group moderators will see it.", comment: "report reason") @@ -831,9 +1106,7 @@ struct ComposeView: View { if liveMessage != nil { composeState = composeState.copy(liveMessage: nil) } await sending() } - if chat.chatInfo.contact?.nextSendGrpInv ?? false { - await sendMemberContactInvitation() - } else if case let .forwardingItems(chatItems, fromChatInfo) = composeState.contextItem { + if case let .forwardingItems(chatItems, fromChatInfo) = composeState.contextItem { // Composed text is send as a reply to the last forwarded item sent = await forwardItems(chatItems, fromChatInfo, ttl).last if !composeState.message.isEmpty { @@ -913,23 +1186,6 @@ struct ComposeView: View { nil } } - - func sending() async { - await MainActor.run { composeState.inProgress = true } - } - - func sendMemberContactInvitation() async { - do { - let mc = checkLinkPreview() - let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc) - await MainActor.run { - self.chatModel.updateContact(contact) - } - } catch { - logger.error("ChatView.sendMemberContactInvitation error: \(error.localizedDescription)") - AlertManager.shared.showAlertMsg(title: "Error sending member contact invitation", message: "Error: \(responseError(error))") - } - } func updateMessage(_ ei: ChatItem, live: Bool) async -> ChatItem? { if let oldMsgContent = ei.content.msgContent { @@ -939,6 +1195,7 @@ struct ComposeView: View { let chatItem = try await apiUpdateChatItem( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, + scope: chat.chatInfo.groupChatScope(), itemId: ei.id, updatedMessage: UpdatedMessage(msgContent: mc, mentions: composeState.memberMentions), live: live @@ -974,6 +1231,9 @@ struct ComposeView: View { return .file(msgText) case .report(_, let reason): return .report(text: msgText, reason: reason) + // TODO [short links] update chat link + case let .chat(_, chatLink): + return .chat(text: msgText, chatLink: chatLink) case .unknown(let type, _): return .unknown(type: type, text: msgText) } @@ -993,7 +1253,7 @@ struct ComposeView: View { return nil } } - + func send(_ reportReason: ReportReason, chatItemId: Int64) async -> ChatItem? { if let chatItems = await apiReportMessage( groupId: chat.chatInfo.apiId, @@ -1001,17 +1261,37 @@ struct ComposeView: View { reportReason: reportReason, reportText: msgText ) { - await MainActor.run { - for chatItem in chatItems { - chatModel.addChatItem(chat.chatInfo, chatItem) + if showReportsInSupportChatAlertDefault.get() { + await MainActor.run { + showReportsInSupportChatAlert() } } return chatItems.first } - + return nil } - + + func showReportsInSupportChatAlert() { + showAlert( + NSLocalizedString("Report sent to moderators", comment: "alert title"), + message: NSLocalizedString("You can view your reports in Chat with admins.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Don't show again", comment: "alert action"), + style: .default, + handler: { _ in + showReportsInSupportChatAlertDefault.set(false) + } + ), + UIAlertAction( + title: NSLocalizedString("Ok", comment: "alert action"), + style: .default + ) + ]} + ) + } + func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?, mentions: [String: Int64]) async -> ChatItem? { await send( [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc, mentions: mentions)], @@ -1026,6 +1306,7 @@ struct ComposeView: View { : await apiSendMessages( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, + scope: chat.chatInfo.groupChatScope(), live: live, ttl: ttl, composedMessages: msgs @@ -1050,8 +1331,10 @@ struct ComposeView: View { if let chatItems = await apiForwardChatItems( toChatType: chat.chatInfo.chatType, toChatId: chat.chatInfo.apiId, + toScope: chat.chatInfo.groupChatScope(), fromChatType: fromChatInfo.chatType, fromChatId: fromChatInfo.apiId, + fromScope: fromChatInfo.groupChatScope(), itemIds: forwardedItems.map { $0.id }, ttl: ttl ) { @@ -1074,22 +1357,10 @@ struct ComposeView: View { return [] } } + } - func checkLinkPreview() -> MsgContent { - switch (composeState.preview) { - case let .linkPreview(linkPreview: linkPreview): - if let parsedMsg = parseSimpleXMarkdown(msgText), - let url = getSimplexLink(parsedMsg).url, - let linkPreview = linkPreview, - url == linkPreview.uri { - return .link(text: msgText, preview: linkPreview) - } else { - return .text(msgText) - } - default: - return .text(msgText) - } - } + func sending() async { + await MainActor.run { composeState.inProgress = true } } private func startVoiceMessageRecording() async { @@ -1200,7 +1471,7 @@ struct ComposeView: View { private func showLinkPreview(_ parsedMsg: [FormattedText]?) { prevLinkUrl = linkUrl - (linkUrl, hasSimplexLink) = getSimplexLink(parsedMsg) + (linkUrl, hasSimplexLink) = getMessageLinks(parsedMsg) if let url = linkUrl { if url != composeState.linkPreview?.uri && url != pendingLinkUrl { pendingLinkUrl = url @@ -1217,39 +1488,38 @@ struct ComposeView: View { } } - private func getSimplexLink(_ parsedMsg: [FormattedText]?) -> (url: URL?, hasSimplexLink: Bool) { + private func getMessageLinks(_ parsedMsg: [FormattedText]?) -> (url: String?, hasSimplexLink: Bool) { guard let parsedMsg else { return (nil, false) } - let url: URL? = if let uri = parsedMsg.first(where: { ft in - ft.format == .uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) - }) { - URL(string: uri.text) - } else { - nil - } let simplexLink = parsedMsgHasSimplexLink(parsedMsg) - return (url, simplexLink) + for ft in parsedMsg { + if let link = ft.linkUri, !cancelledLinks.contains(link) && !isSimplexLink(link) { + return (link, simplexLink) + } + } + return (nil, simplexLink) } private func isSimplexLink(_ link: String) -> Bool { - link.starts(with: "https://simplex.chat") || link.starts(with: "http://simplex.chat") + link.starts(with: "https://simplex.chat") || link.starts(with: "http://simplex.chat") || link.starts(with: "simplex:/") } private func cancelLinkPreview() { - if let pendingLink = pendingLinkUrl?.absoluteString { + if let pendingLink = pendingLinkUrl { cancelledLinks.insert(pendingLink) } - if let uri = composeState.linkPreview?.uri.absoluteString { + if let uri = composeState.linkPreview?.uri { cancelledLinks.insert(uri) } pendingLinkUrl = nil composeState = composeState.copy(preview: .noPreview) } - private func loadLinkPreview(_ url: URL) { - if pendingLinkUrl == url { + private func loadLinkPreview(_ urlStr: String) { + if pendingLinkUrl == urlStr, let url = URL(string: urlStr) { composeState = composeState.copy(preview: .linkPreview(linkPreview: nil)) getLinkPreview(url: url) { linkPreview in - if let linkPreview, pendingLinkUrl == url { + if let linkPreview, pendingLinkUrl == urlStr { + privacyLinkPreviewsShowAlertGroupDefault.set(false) // to avoid showing alert to current users, show alert in v6.5 composeState = composeState.copy(preview: .linkPreview(linkPreview: linkPreview)) } else { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { @@ -1269,29 +1539,31 @@ struct ComposeView: View { } } -struct ComposeView_Previews: PreviewProvider { - static var previews: some View { - let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []) - @State var composeState = ComposeState(message: "hello") - @State var selectedRange = NSRange() - - return Group { - ComposeView( - chat: chat, - composeState: $composeState, - keyboardVisible: Binding.constant(true), - keyboardHiddenDate: Binding.constant(Date.now), - selectedRange: $selectedRange - ) - .environmentObject(ChatModel()) - ComposeView( - chat: chat, - composeState: $composeState, - keyboardVisible: Binding.constant(true), - keyboardHiddenDate: Binding.constant(Date.now), - selectedRange: $selectedRange - ) - .environmentObject(ChatModel()) +func sanitizeMessage(_ parsedMsg: [FormattedText]) -> (message: String, parsedMsg: [FormattedText], sanitizedPos: Int?) { + var pos: Int = 0 + var updatedMsg = "" + var sanitizedPos: Int? = nil + let updatedParsedMsg = parsedMsg.map { ft in + var updated = ft + switch ft.format { + case .uri: + if let sanitized = parseSanitizeUri(ft.text, safe: true)?.uriInfo?.sanitized { + updated = FormattedText(text: sanitized, format: .uri) + pos += updated.text.count + sanitizedPos = pos + } + case let .hyperLink(text, uri): + if let sanitized = parseSanitizeUri(uri, safe: true)?.uriInfo?.sanitized { + let updatedText = if let text { "[\(text)](\(sanitized))" } else { sanitized } + updated = FormattedText(text: updatedText, format: .hyperLink(showText: text, linkUri: sanitized)) + pos += updated.text.count + sanitizedPos = pos + } + default: + pos += ft.text.count } + updatedMsg += updated.text + return updated } + return (message: updatedMsg, parsedMsg: updatedParsedMsg, sanitizedPos: sanitizedPos) } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift new file mode 100644 index 0000000000..82c89cd43d --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift @@ -0,0 +1,97 @@ +// +// ContextContactRequestActionsView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 02.05.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct ContextContactRequestActionsView: View { + @EnvironmentObject var theme: AppTheme + var contactRequestId: Int64 + @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @State private var inProgress = false + @State private var progressByTimeout = false + + var body: some View { + HStack(spacing: 0) { + Button(role: .destructive, action: showRejectRequestAlert) { + Label("Reject", systemImage: "multiply") + } + .frame(maxWidth: .infinity, minHeight: 60) + + Button { + if ChatModel.shared.addressShortLinkDataSet { + acceptRequest() + } else { + showAcceptRequestAlert() + } + } label: { + Label("Accept", systemImage: "checkmark") + } + .frame(maxWidth: .infinity, minHeight: 60) + } + .disabled(inProgress) + .frame(maxWidth: .infinity) + .background(ToolbarMaterial.material(toolbarMaterial)) + .opacity(progressByTimeout ? 0.4 : 1) + .overlay { + if progressByTimeout { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .onChange(of: inProgress) { inPrgrs in + if inPrgrs { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + progressByTimeout = inProgress + } + } else { + progressByTimeout = false + } + } + } + + private func showRejectRequestAlert() { + showAlert( + NSLocalizedString("Reject contact request", comment: "alert title"), + message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"), + actions: {[ + UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in + Task { await rejectContactRequest(contactRequestId, dismissToChatList: true) } + }, + cancelAlertAction + ]} + ) + } + + private func showAcceptRequestAlert() { + showAlert( + NSLocalizedString("Accept contact request", comment: "alert title"), + actions: {[ + UIAlertAction(title: NSLocalizedString("Accept", comment: "alert action"), style: .default) { _ in + acceptRequest() + }, + UIAlertAction(title: NSLocalizedString("Accept incognito", comment: "alert action"), style: .default) { _ in + acceptRequest(incognito: true) + }, + cancelAlertAction + ]} + ) + } + + private func acceptRequest(incognito: Bool = false) { + Task { + await acceptContactRequest(incognito: incognito, contactRequestId: contactRequestId, inProgress: $inProgress) + } + } +} + +#Preview { + ContextContactRequestActionsView( + contactRequestId: 1 + ) +} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift deleted file mode 100644 index 82090f312a..0000000000 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ContextInvitingContactMemberView.swift -// SimpleX (iOS) -// -// Created by spaced4ndy on 18.09.2023. -// Copyright © 2023 SimpleX Chat. All rights reserved. -// - -import SwiftUI - -struct ContextInvitingContactMemberView: View { - @EnvironmentObject var theme: AppTheme - - var body: some View { - HStack { - Image(systemName: "message") - .foregroundColor(theme.colors.secondary) - Text("Send direct message to connect") - } - .padding(12) - .frame(minHeight: 54) - .frame(maxWidth: .infinity, alignment: .leading) - .background(.thinMaterial) - } -} - -struct ContextInvitingContactMemberView_Previews: PreviewProvider { - static var previews: some View { - ContextInvitingContactMemberView() - } -} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextMemberContactActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextMemberContactActionsView.swift new file mode 100644 index 0000000000..9a73b2b5d4 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextMemberContactActionsView.swift @@ -0,0 +1,110 @@ +// +// ContextMemberContactActionsView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 31.07.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct ContextMemberContactActionsView: View { + @EnvironmentObject var theme: AppTheme + var contact: Contact + var groupDirectInv: GroupDirectInvitation + @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @State private var inProgress = false + @State private var progressByTimeout = false + + var body: some View { + VStack { + if groupDirectInv.memberRemoved { + Label("Member is deleted - can't accept request", systemImage: "info.circle") + .foregroundColor(theme.colors.secondary) + .font(.subheadline) + .padding(.horizontal) + .frame(maxWidth: .infinity, minHeight: 60) + } else { + HStack(spacing: 0) { + Button(role: .destructive, action: { showRejectMemberContactRequestAlert(contact) }) { + Label("Reject", systemImage: "multiply") + } + .frame(maxWidth: .infinity, minHeight: 60) + + Button { + acceptMemberContactRequest(contact, inProgress: $inProgress) + } label: { + Label("Accept", systemImage: "checkmark") + } + .frame(maxWidth: .infinity, minHeight: 60) + } + } + } + .disabled(inProgress || groupDirectInv.memberRemoved) + .frame(maxWidth: .infinity) + .background(ToolbarMaterial.material(toolbarMaterial)) + .opacity(progressByTimeout ? 0.4 : 1) + .overlay { + if progressByTimeout { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .onChange(of: inProgress) { inPrgrs in + if inPrgrs { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + progressByTimeout = inProgress + } + } else { + progressByTimeout = false + } + } + } +} + +func showRejectMemberContactRequestAlert(_ contact: Contact) { + showAlert( + NSLocalizedString("Reject contact request", comment: "alert title"), + message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"), + actions: {[ + UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in + deleteContact(contact) + }, + cancelAlertAction + ]} + ) +} + +private func deleteContact(_ contact: Contact) { + Task { + do { + _ = try await apiDeleteContact(id: contact.contactId, chatDeleteMode: .full(notify: false)) + await MainActor.run { + ChatModel.shared.removeChat(contact.id) + ChatModel.shared.chatId = nil + } + } catch let error { + logger.error("apiDeleteContact: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error deleting chat!", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + +func acceptMemberContactRequest(_ contact: Contact, inProgress: Binding? = nil) { + Task { + await acceptMemberContact(contactId: contact.contactId, inProgress: inProgress) + } +} + +#Preview { + ContextMemberContactActionsView( + contact: Contact.sampleData, + groupDirectInv: GroupDirectInvitation.sampleData + ) +} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift new file mode 100644 index 0000000000..143bf42ea4 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift @@ -0,0 +1,106 @@ +// +// ContextPendingMemberActionsView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 02.05.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct ContextPendingMemberActionsView: View { + @EnvironmentObject var theme: AppTheme + @Environment(\.dismiss) var dismiss + var groupInfo: GroupInfo + var member: GroupMember + @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + + var body: some View { + HStack(spacing: 0) { + ZStack { + Text("Reject") + .foregroundColor(.red) + } + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + .onTapGesture { + showRejectMemberAlert(groupInfo, member, dismiss: dismiss) + } + + ZStack { + Text("Accept") + .foregroundColor(theme.colors.primary) + } + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + .onTapGesture { + showAcceptMemberAlert(groupInfo, member, dismiss: dismiss) + } + } + .frame(minHeight: 54) + .frame(maxWidth: .infinity) + .background(ToolbarMaterial.material(toolbarMaterial)) + } +} + +func showRejectMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) { + showAlert( + title: NSLocalizedString("Reject member?", comment: "alert title"), + buttonTitle: "Reject", + buttonAction: { removeMember(groupInfo, member, dismiss: dismiss) }, + cancelButton: true + ) +} + +func showAcceptMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) { + showAlert( + NSLocalizedString("Accept member", comment: "alert title"), + message: NSLocalizedString("Member will join the group, accept member?", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Accept as member", comment: "alert action"), + style: .default, + handler: { _ in + acceptMember(groupInfo, member, .member, dismiss: dismiss) + } + ), + UIAlertAction( + title: NSLocalizedString("Accept as observer", comment: "alert action"), + style: .default, + handler: { _ in + acceptMember(groupInfo, member, .observer, dismiss: dismiss) + } + ), + cancelAlertAction + ]} + ) +} + +func acceptMember(_ groupInfo: GroupInfo, _ member: GroupMember, _ role: GroupMemberRole, dismiss: DismissAction? = nil) { + Task { + do { + let (gInfo, acceptedMember) = try await apiAcceptMember(groupInfo.groupId, member.groupMemberId, role) + await MainActor.run { + _ = ChatModel.shared.upsertGroupMember(gInfo, acceptedMember) + ChatModel.shared.updateGroup(gInfo) + dismiss?() + } + } catch let error { + logger.error("apiAcceptMember error: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting member", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + +#Preview { + ContextPendingMemberActionsView( + groupInfo: GroupInfo.sampleData, + member: GroupMember.sampleData + ) +} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift new file mode 100644 index 0000000000..427a600627 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift @@ -0,0 +1,305 @@ +// +// ContextProfilePickerView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 13.06.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +let USER_ROW_SIZE: CGFloat = 60 +let MAX_VISIBLE_USER_ROWS: CGFloat = 4.8 + +struct ContextProfilePickerView: View { + @ObservedObject var chat: Chat + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @State var selectedUser: User + @State private var users: [User] = [] + @State private var listExpanded = false + @State private var expandedListReady = false + @State private var showIncognitoSheet = false + + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false + + var body: some View { + viewBody() + .onAppear { + users = chatModel.users + .map { $0.user } + .filter { u in u.activeUser || !u.hidden } + } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } + } + + private func viewBody() -> some View { + Group { + if !listExpanded || chat.chatInfo.profileChangeProhibited { + currentSelection() + } else { + profilePicker() + } + } + } + + private func currentSelection() -> some View { + VStack(spacing: 0) { + HStack { + Text("Your profile") + .font(.callout) + .foregroundColor(theme.colors.secondary) + Spacer() + } + .padding(.top, 8) + .padding(.bottom, -4) + .padding(.leading, 12) + .padding(.trailing) + + if chat.chatInfo.profileChangeProhibited { + if chat.chatInfo.incognito { + incognitoOption() + } else { + profilerPickerUserOption(selectedUser) + } + } else if incognitoDefault { + incognitoOption() + } else { + profilerPickerUserOption(selectedUser) + } + } + } + + private func profilePicker() -> some View { + ScrollViewReader { proxy in + Group { + if expandedListReady { + let scroll = ScrollView { + LazyVStack(spacing: 0) { + let otherUsers = users + .filter { u in u.userId != selectedUser.userId } + .sorted(using: KeyPathComparator(\.activeOrder)) + ForEach(otherUsers) { p in + profilerPickerUserOption(p) + .contentShape(Rectangle()) + Divider() + .padding(.leading) + .padding(.leading, 48) + } + + if incognitoDefault { + profilerPickerUserOption(selectedUser) + .contentShape(Rectangle()) + Divider() + .padding(.leading) + .padding(.leading, 48) + + incognitoOption() + .contentShape(Rectangle()) + .id("BOTTOM_ANCHOR") + } else { + incognitoOption() + .contentShape(Rectangle()) + Divider() + .padding(.leading) + .padding(.leading, 48) + + profilerPickerUserOption(selectedUser) + .contentShape(Rectangle()) + .id("BOTTOM_ANCHOR") + } + } + } + .frame(maxHeight: USER_ROW_SIZE * min(MAX_VISIBLE_USER_ROWS, CGFloat(users.count + 1))) // + 1 for incognito + .onAppear { + DispatchQueue.main.async { + withAnimation(nil) { + proxy.scrollTo("BOTTOM_ANCHOR", anchor: .bottom) + } + } + } + .onDisappear { + expandedListReady = false + } + + if #available(iOS 16.0, *) { + scroll.scrollDismissesKeyboard(.never) + } else { + scroll + } + } else { + // Keep showing current selection to avoid flickering of scroll to bottom + currentSelection() + .onAppear { + // Delay rendering of expanded profile list + DispatchQueue.main.async { + expandedListReady = true + } + } + } + } + } + } + + private func profilerPickerUserOption(_ user: User) -> some View { + Button { + if !chat.chatInfo.profileChangeProhibited { + if selectedUser == user { + if !incognitoDefault { + listExpanded.toggle() + } else { + incognitoDefault = false + listExpanded = false + } + } else if selectedUser != user { + changeProfile(user) + } + } else { + showCantChangeProfileAlert() + } + } label: { + HStack { + ProfileImage(imageStr: user.image, size: 38) + Text(user.chatViewName) + .fontWeight(selectedUser == user && !incognitoDefault ? .medium : .regular) + .foregroundColor(theme.colors.onBackground) + .lineLimit(1) + + Spacer() + + if selectedUser == user && !incognitoDefault { + if listExpanded { + Image(systemName: "chevron.down") + .font(.system(size: 12, weight: .bold)) + .foregroundColor(theme.colors.secondary) + .opacity(0.7) + } else if !chat.chatInfo.profileChangeProhibited { + Image(systemName: "chevron.up") + .font(.system(size: 12, weight: .bold)) + .foregroundColor(theme.colors.secondary) + .opacity(0.7) + } + } + } + .padding(.leading, 12) + .padding(.trailing) + .frame(height: USER_ROW_SIZE) + } + } + + private func changeProfile(_ newUser: User) { + Task { + do { + if let contact = chat.chatInfo.contact { + let updatedContact = try await apiChangePreparedContactUser(contactId: contact.contactId, newUserId: newUser.userId) + await MainActor.run { + selectedUser = newUser + incognitoDefault = false + listExpanded = false + chatModel.updateContact(updatedContact) + } + } else if let groupInfo = chat.chatInfo.groupInfo { + let updatedGroupInfo = try await apiChangePreparedGroupUser(groupId: groupInfo.groupId, newUserId: newUser.userId) + await MainActor.run { + selectedUser = newUser + incognitoDefault = false + listExpanded = false + chatModel.updateGroup(updatedGroupInfo) + } + } + do { + try await changeActiveUserAsync_(newUser.userId, viewPwd: nil, keepingChatId: chat.id) + } catch { + await MainActor.run { + showAlert( + NSLocalizedString("Error switching profile", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile.", comment: "alert message"), newUser.chatViewName) + ) + } + } + } catch let error { + await MainActor.run { + if let currentUser = chatModel.currentUser { + selectedUser = currentUser + } + showAlert( + NSLocalizedString("Error changing chat profile", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } + + private func incognitoOption() -> some View { + Button { + if !chat.chatInfo.profileChangeProhibited { + if incognitoDefault { + listExpanded.toggle() + } else { + incognitoDefault = true + listExpanded = false + } + } else { + showCantChangeProfileAlert() + } + } label : { + HStack { + incognitoProfileImage() + Text("Incognito") + .fontWeight(incognitoDefault ? .medium : .regular) + .foregroundColor(theme.colors.onBackground) + Image(systemName: "info.circle") + .font(.system(size: 16)) + .foregroundColor(theme.colors.primary) + .onTapGesture { + showIncognitoSheet = true + } + + Spacer() + + if incognitoDefault { + if listExpanded { + Image(systemName: "chevron.down") + .font(.system(size: 12, weight: .bold)) + .foregroundColor(theme.colors.secondary) + .opacity(0.7) + } else if !chat.chatInfo.profileChangeProhibited { + Image(systemName: "chevron.up") + .font(.system(size: 12, weight: .bold)) + .foregroundColor(theme.colors.secondary) + .opacity(0.7) + } + } + } + .padding(.leading, 12) + .padding(.trailing) + .frame(height: USER_ROW_SIZE) + } + } + + private func incognitoProfileImage() -> some View { + Image(systemName: "theatermasks.fill") + .resizable() + .scaledToFit() + .frame(width: 38) + .foregroundColor(.indigo) + } + + private func showCantChangeProfileAlert() { + showAlert( + NSLocalizedString("Can't change profile", comment: "alert title"), + message: NSLocalizedString("To use another profile after connection attempt, delete the chat and use the link again.", comment: "alert message") + ) + } +} + +#Preview { + ContextProfilePickerView( + chat: Chat.sampleData, + selectedUser: User.sampleData + ) +} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift index d809fd7b76..31d4ceecc6 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift @@ -63,13 +63,21 @@ struct NativeTextEditor: UIViewRepresentable { field.textAlignment = alignment(text) field.updateFont() field.updateHeight(updateBindingNow: false) + field.placeholder = text.isEmpty ? placeholder : "" } if field.placeholder != placeholder { - field.placeholder = placeholder + field.placeholder = text.isEmpty ? placeholder : "" } if field.selectedRange != selectedRange { field.selectedRange = selectedRange } + if focused && !field.isFocused { + DispatchQueue.main.async { + if !field.isFocused { + field.becomeFirstResponder() + } + } + } } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index e7b02c9aea..07cd61583b 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -12,6 +12,7 @@ import SimpleXChat private let liveMsgInterval: UInt64 = 3000_000000 struct SendMessageView: View { + var placeholder: String? @Binding var composeState: ComposeState @Binding var selectedRange: NSRange @EnvironmentObject var theme: AppTheme @@ -20,7 +21,8 @@ struct SendMessageView: View { var sendLiveMessage: (() async -> Void)? = nil var updateLiveMessage: (() async -> Void)? = nil var cancelLiveMessage: (() -> Void)? = nil - var nextSendGrpInv: Bool = false + var sendToConnect: (() -> Void)? = nil + var hideSendButton: Bool = false var showVoiceMessageButton: Bool = true var voiceMessageAllowed: Bool = true var disableSendButton = false @@ -42,7 +44,6 @@ struct SendMessageView: View { @State private var showCustomDisappearingMessageDialogue = false @State private var showCustomTimePicker = false @State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get() - @State private var progressByTimeout = false @UserDefault(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false var body: some View { @@ -64,7 +65,7 @@ struct SendMessageView: View { height: $teHeight, focused: $keyboardVisible, lastUnfocusedDate: $keyboardHiddenDate, - placeholder: Binding(get: { composeState.placeholder }, set: { _ in }), + placeholder: Binding(get: { placeholder ?? composeState.placeholder }, set: { _ in }), selectedRange: $selectedRange, onImagesAdded: onMediaAdded ) @@ -74,12 +75,12 @@ struct SendMessageView: View { } } .overlay(alignment: .topTrailing, content: { - if !progressByTimeout && teHeight > 100 && !composeState.inProgress { + if !composeState.progressByTimeout && teHeight > 100 && !composeState.inProgress { deleteTextButton() } }) - .overlay(alignment: .bottomTrailing, content: { - if progressByTimeout { + .overlay(alignment: .bottomTrailing) { + if composeState.progressByTimeout { ProgressView() .scaleEffect(1.4) .frame(width: 31, height: 31, alignment: .center) @@ -89,28 +90,21 @@ struct SendMessageView: View { // required for intercepting clicks .background(.white.opacity(0.000001)) } - }) + } .padding(.vertical, 1) .background(theme.colors.background) .clipShape(composeShape) .overlay(composeShape.strokeBorder(.secondary, lineWidth: 0.5).opacity(0.7)) .onChange(of: composeState.message, perform: { text in updateFont(text) }) - .onChange(of: composeState.inProgress) { inProgress in - if inProgress { - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - progressByTimeout = composeState.inProgress - } - } else { - progressByTimeout = false - } - } .padding(.vertical, 8) } @ViewBuilder private func composeActionButtons() -> some View { let vmrs = composeState.voiceMessageRecordingState - if nextSendGrpInv { - inviteMemberContactButton() + if hideSendButton { + EmptyView() + } else if let connect = sendToConnect { + sendToConnectButton(connect) } else if case .reportedItem = composeState.contextItem { sendMessageButton() } else if showVoiceMessageButton @@ -158,20 +152,16 @@ struct SendMessageView: View { .padding([.top, .trailing], 4) } - private func inviteMemberContactButton() -> some View { - Button { - sendMessage(nil) - } label: { + private func sendToConnectButton(_ connect: @escaping () -> Void) -> some View { + let disabled = !composeState.sendEnabled || composeState.inProgress || disableSendButton + return Button(action: connect) { Image(systemName: "arrow.up.circle.fill") .resizable() - .foregroundColor(sendButtonColor) + .foregroundColor(disabled ? theme.colors.secondary.opacity(0.67) : sendButtonColor) .frame(width: sendButtonSize, height: sendButtonSize) .opacity(sendButtonOpacity) } - .disabled( - !composeState.sendEnabled || - composeState.inProgress - ) + .disabled(disabled) .frame(width: 31, height: 31) .padding([.bottom, .trailing], 4) } diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 7cd543af10..3154f16f5b 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -78,6 +78,12 @@ struct AddGroupMembersViewCommon: View { let count = selectedContacts.count Section { if creatingGroup { + MemberAdmissionButton( + groupInfo: $groupInfo, + admission: groupInfo.groupProfile.memberAdmission_, + currentAdmission: groupInfo.groupProfile.memberAdmission_, + creatingGroup: true + ) GroupPreferencesButton( groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 15749b0761..d8929caa3e 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -17,11 +17,12 @@ struct GroupChatInfoView: View { @Environment(\.dismiss) var dismiss: DismissAction @ObservedObject var chat: Chat @Binding var groupInfo: GroupInfo + @Binding var scrollToItemId: ChatItem.ID? var onSearch: () -> Void @State var localAlias: String @FocusState private var aliasTextFieldFocused: Bool @State private var alert: GroupChatInfoViewAlert? = nil - @State private var groupLink: CreatedConnLink? + @State private var groupLink: GroupLink? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var groupLinkNavLinkActive: Bool = false @State private var addMembersNavLinkActive: Bool = false @@ -33,6 +34,7 @@ struct GroupChatInfoView: View { @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @State private var searchText: String = "" @FocusState private var searchFocussed + @State private var showSecrets: Set = [] enum GroupChatInfoViewAlert: Identifiable { case deleteGroupAlert @@ -74,12 +76,12 @@ struct GroupChatInfoView: View { List { groupInfoHeader() .listRowBackground(Color.clear) - + localAliasTextEdit() .listRowBackground(Color.clear) .listRowSeparator(.hidden) .padding(.bottom, 18) - + infoActionButtons() .padding(.horizontal) .frame(maxWidth: .infinity) @@ -87,7 +89,25 @@ struct GroupChatInfoView: View { .listRowBackground(Color.clear) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - + + Section { + if groupInfo.canAddMembers && groupInfo.businessChat == nil { + groupLinkButton() + } + if groupInfo.businessChat == nil && groupInfo.membership.memberRole >= .moderator { + memberSupportButton() + } + if groupInfo.canModerate { + GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) + } + if groupInfo.membership.memberActive + && (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) { + UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) + } + } header: { + Text("") + } + Section { if groupInfo.isOwner && groupInfo.businessChat == nil { editGroupButton() @@ -96,19 +116,6 @@ struct GroupChatInfoView: View { addOrEditWelcomeMessage() } GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences) - if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { - sendReceiptsOption() - } else { - sendReceiptsOptionDisabled() - } - - NavigationLink { - ChatWallpaperEditorSheet(chat: chat) - } label: { - Label("Chat theme", systemImage: "photo") - } - } header: { - Text("") } footer: { let label: LocalizedStringKey = ( groupInfo.businessChat == nil @@ -118,48 +125,64 @@ struct GroupChatInfoView: View { Text(label) .foregroundColor(theme.colors.secondary) } - + Section { + if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { + sendReceiptsOption() + } else { + sendReceiptsOptionDisabled() + } + NavigationLink { + ChatWallpaperEditorSheet(chat: chat) + } label: { + Label("Chat theme", systemImage: "photo") + } ChatTTLOption(chat: chat, progressIndicator: $progressIndicator) } footer: { Text("Delete chat messages from your device.") } - - Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) { - if groupInfo.canAddMembers { - if groupInfo.businessChat == nil { - groupLinkButton() + + if !groupInfo.nextConnectPrepared { + Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) { + if groupInfo.canAddMembers { + if (chat.chatInfo.incognito) { + Label("Invite members", systemImage: "plus") + .foregroundColor(Color(uiColor: .tertiaryLabel)) + .onTapGesture { alert = .cantInviteIncognitoAlert } + } else { + addMembersButton() + } } - if (chat.chatInfo.incognito) { - Label("Invite members", systemImage: "plus") - .foregroundColor(Color(uiColor: .tertiaryLabel)) - .onTapGesture { alert = .cantInviteIncognitoAlert } - } else { - addMembersButton() - } - } - searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary) - .padding(.leading, 8) - let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase - let filteredMembers = s == "" + searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary) + .padding(.leading, 8) + let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + let filteredMembers = s == "" ? members : members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } - MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) - ForEach(filteredMembers) { member in - MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, alert: $alert) + MemberRowView( + chat: chat, + groupInfo: groupInfo, + groupMember: GMember(groupInfo.membership), + scrollToItemId: $scrollToItemId, + user: true, + alert: $alert + ) + ForEach(filteredMembers) { member in + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, scrollToItemId: $scrollToItemId, alert: $alert) + } } } - + Section { clearChatButton() if groupInfo.canDelete { deleteGroupButton() } - if groupInfo.membership.memberCurrent { + if groupInfo.membership.memberCurrentOrPending { leaveGroupButton() } } - + if developerTools { Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { infoRow("Local name", chat.chatInfo.localDisplayName) @@ -171,7 +194,7 @@ struct GroupChatInfoView: View { .navigationBarHidden(true) .disabled(progressIndicator) .opacity(progressIndicator ? 0.6 : 1) - + if progressIndicator { ProgressView().scaleEffect(2) } @@ -199,8 +222,9 @@ struct GroupChatInfoView: View { } sendReceipts = SendReceipts.fromBool(groupInfo.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault) do { - if let link = try apiGetGroupLink(groupInfo.groupId) { - (groupLink, groupLinkMemberRole) = link + if let gLink = try apiGetGroupLink(groupInfo.groupId) { + groupLink = gLink + groupLinkMemberRole = gLink.acceptMemberRole } } catch let error { logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))") @@ -211,19 +235,30 @@ struct GroupChatInfoView: View { private func groupInfoHeader() -> some View { VStack { let cInfo = chat.chatInfo + // show actual display name, alias can be edited in this view + let displayName = (cInfo.groupInfo?.groupProfile.displayName ?? cInfo.displayName).trimmingCharacters(in: .whitespacesAndNewlines) + let fullName = cInfo.fullName.trimmingCharacters(in: .whitespacesAndNewlines) ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.top, 12) .padding() - Text(cInfo.groupInfo?.groupProfile.displayName ?? cInfo.displayName) + Text(displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(4) .padding(.bottom, 2) - if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName { + if fullName != "" && fullName != displayName && fullName != cInfo.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { Text(cInfo.fullName) .font(.title2) .multilineTextAlignment(.center) - .lineLimit(8) + .lineLimit(3) + .padding(.bottom, 2) + } + if let descr = cInfo.shortDescr?.trimmingCharacters(in: .whitespacesAndNewlines), descr != "" { + let r = markdownText(descr, textStyle: .subheadline, showSecrets: showSecrets, backgroundColor: theme.colors.background) + msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets, centered: true, smallFont: true) + .multilineTextAlignment(.center) + .lineLimit(4) + .fixedSize(horizontal: false, vertical: true) } } .frame(maxWidth: .infinity, alignment: .center) @@ -245,7 +280,7 @@ struct GroupChatInfoView: View { .multilineTextAlignment(.center) .foregroundColor(theme.colors.secondary) } - + private func setGroupAlias() { Task { do { @@ -259,7 +294,7 @@ struct GroupChatInfoView: View { } } } - + func infoActionButtons() -> some View { GeometryReader { g in let buttonWidth = g.size.width / 4 @@ -353,6 +388,7 @@ struct GroupChatInfoView: View { var chat: Chat var groupInfo: GroupInfo @ObservedObject var groupMember: GMember + @Binding var scrollToItemId: ChatItem.ID? @EnvironmentObject var theme: AppTheme var user: Bool = false @Binding var alert: GroupChatInfoViewAlert? @@ -392,7 +428,7 @@ struct GroupChatInfoView: View { if user { v - } else if groupInfo.membership.memberRole >= .admin { + } else if groupInfo.membership.memberRole >= .moderator { // TODO if there are more actions, refactor with lists of swipeActions let canBlockForAll = member.canBlockForAll(groupInfo: groupInfo) let canRemove = member.canBeRemoved(groupInfo: groupInfo) @@ -415,7 +451,7 @@ struct GroupChatInfoView: View { } private func memberInfoView() -> some View { - GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) + GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember, scrollToItemId: $scrollToItemId) .navigationBarHidden(false) } @@ -435,7 +471,7 @@ struct GroupChatInfoView: View { .foregroundColor(theme.colors.secondary) } else { let role = member.memberRole - if [.owner, .admin, .observer].contains(role) { + if [.owner, .admin, .moderator, .observer].contains(role) { Text(member.memberRole.text) .foregroundColor(theme.colors.secondary) } @@ -523,15 +559,99 @@ struct GroupChatInfoView: View { .navigationBarTitleDisplayMode(.large) } + struct UserSupportChatNavLink: View { + @ObservedObject var chat: Chat + @EnvironmentObject var theme: AppTheme + var groupInfo: GroupInfo + @EnvironmentObject var chatModel: ChatModel + @Binding var scrollToItemId: ChatItem.ID? + @State private var navLinkActive = false + + var body: some View { + let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil) + NavigationLink(isActive: $navLinkActive) { + SecondaryChatView( + chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()), + scrollToItemId: $scrollToItemId + ) + } label: { + HStack { + Label("Chat with admins", systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag") + Spacer() + if chat.supportUnreadCount > 0 { + UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary) + } + } + } + .onChange(of: navLinkActive) { active in + if active { + ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo)) + } + } + } + } + + private func memberSupportButton() -> some View { + NavigationLink { + MemberSupportView(groupInfo: groupInfo, scrollToItemId: $scrollToItemId) + .navigationBarTitle("Chats with members") + .modifier(ThemedBackground()) + .navigationBarTitleDisplayMode(.large) + } label: { + HStack { + Label( + "Chats with members", + systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag" + ) + Spacer() + if chat.supportUnreadCount > 0 { + UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary) + } + } + } + } + + struct GroupReportsChatNavLink: View { + @ObservedObject var chat: Chat + @EnvironmentObject var theme: AppTheme + var groupInfo: GroupInfo + @EnvironmentObject var chatModel: ChatModel + @Binding var scrollToItemId: ChatItem.ID? + @State private var navLinkActive = false + + var body: some View { + NavigationLink(isActive: $navLinkActive) { + SecondaryChatView( + chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: .reports), chatItems: [], chatStats: ChatStats()), + scrollToItemId: $scrollToItemId + ) + } label: { + HStack { + Label { + Text("Member reports") + } icon: { + Image(systemName: chat.chatStats.reportsCount > 0 ? "flag.fill" : "flag").foregroundColor(.red) + } + Spacer() + if chat.chatStats.reportsCount > 0 { + UnreadBadge(count: chat.chatStats.reportsCount, color: .red) + } + } + } + .onChange(of: navLinkActive) { active in + if active { + ItemsModel.loadSecondaryChat(chat.id, chatFilter: .msgContentTagContext(contentTag: .report)) + } + } + } + } + private func editGroupButton() -> some View { NavigationLink { GroupProfileView( groupInfo: $groupInfo, groupProfile: groupInfo.groupProfile ) - .navigationBarTitle("Group profile") - .modifier(ThemedBackground()) - .navigationBarTitleDisplayMode(.large) } label: { Label("Edit group profile", systemImage: "pencil") } @@ -642,14 +762,13 @@ struct GroupChatInfoView: View { } private func sendReceiptsOption() -> some View { - Picker(selection: $sendReceipts) { + WrappedPicker(selection: $sendReceipts) { ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in Text(opt.text) } } label: { Label("Send receipts", systemImage: "checkmark.message") } - .frame(height: 36) .onChange(of: sendReceipts) { _ in setSendReceipts() } @@ -683,26 +802,36 @@ struct GroupChatInfoView: View { title: Text("Remove member?"), message: Text(messageLabel), primaryButton: .destructive(Text("Remove")) { - Task { - do { - let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId]) - await MainActor.run { - updatedMembers.forEach { updatedMember in - _ = chatModel.upsertGroupMember(groupInfo, updatedMember) - } - } - } catch let error { - logger.error("apiRemoveMembers error: \(responseError(error))") - let a = getErrorAlert(error, "Error removing member") - alert = .error(title: a.title, error: a.message) - } - } + removeMember(groupInfo, mem) }, secondaryButton: .cancel() ) } } +func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: DismissAction? = nil) { + Task { + do { + let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId]) + await MainActor.run { + ChatModel.shared.updateGroup(updatedGroupInfo) + updatedMembers.forEach { updatedMember in + _ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember) + } + dismiss?() + } + } catch let error { + logger.error("apiRemoveMembers error: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error removing member", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { groupInfo.businessChat == nil ? ( groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") @@ -716,11 +845,11 @@ struct GroupPreferencesButton: View { @State var preferences: FullGroupPreferences @State var currentPreferences: FullGroupPreferences var creatingGroup: Bool = false - + private var label: LocalizedStringKey { groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences" } - + var body: some View { NavigationLink { GroupPreferencesView( @@ -738,7 +867,7 @@ struct GroupPreferencesButton: View { creatingGroup ? "Save" : "Save and notify group members", comment: "alert button" ) - + if groupInfo.fullGroupPreferences != preferences { showAlert( title: NSLocalizedString("Save preferences?", comment: "alert title"), @@ -756,7 +885,7 @@ struct GroupPreferencesButton: View { } } } - + private func savePreferences() { Task { do { @@ -773,7 +902,6 @@ struct GroupPreferencesButton: View { } } } - } @@ -796,6 +924,7 @@ struct GroupChatInfoView_Previews: PreviewProvider { GroupChatInfoView( chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: Binding.constant(GroupInfo.sampleData), + scrollToItemId: Binding.constant(nil), onSearch: {}, localAlias: "" ) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index a11c073a42..bc1ac4ab65 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct GroupLinkView: View { @EnvironmentObject var theme: AppTheme var groupId: Int64 - @Binding var groupLink: CreatedConnLink? + @Binding var groupLink: GroupLink? @Binding var groupLinkMemberRole: GroupMemberRole var showTitle: Bool = false var creatingGroup: Bool = false @@ -35,16 +35,23 @@ struct GroupLinkView: View { } var body: some View { - if creatingGroup { - groupLinkView() - .navigationBarBackButtonHidden() - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button ("Continue") { linkCreatedCb?() } + ZStack { + if creatingGroup { + groupLinkView() + .navigationBarBackButtonHidden() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button ("Continue") { linkCreatedCb?() } + } } - } - } else { - groupLinkView() + } else { + groupLinkView() + } + if creatingLink { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity) + } } } @@ -71,10 +78,21 @@ struct GroupLinkView: View { } } .frame(height: 36) - SimpleXCreatedLinkQRCode(link: groupLink, short: $showShortLink) - .id("simplex-qrcode-view-for-\(groupLink.simplexChatUri(short: showShortLink))") + SimpleXCreatedLinkQRCode(link: groupLink.connLinkContact, short: $showShortLink) + .id("simplex-qrcode-view-for-\(groupLink.connLinkContact.simplexChatUri(short: showShortLink))") + if groupLink.shouldBeUpgraded { + Button { + upgradeAndShareLinkAlert() + } label: { + Label("Upgrade link", systemImage: "arrow.up") + } + } Button { - showShareSheet(items: [groupLink.simplexChatUri(short: showShortLink)]) + if groupLink.shouldBeUpgraded { + upgradeAndShareLinkAlert(groupLink: groupLink) + } else { + groupLink.shareAddress(short: showShortLink) + } } label: { Label("Share link", systemImage: "square.and.arrow.up") } @@ -89,15 +107,10 @@ struct GroupLinkView: View { Label("Create link", systemImage: "link.badge.plus") } .disabled(creatingLink) - if creatingLink { - ProgressView() - .scaleEffect(2) - .frame(maxWidth: .infinity) - } } } header: { - if let groupLink, groupLink.connShortLink != nil { - ToggleShortLinkHeader(text: Text(""), link: groupLink, short: $showShortLink) + if let groupLink, groupLink.connLinkContact.connShortLink != nil { + ToggleShortLinkHeader(text: Text(""), link: groupLink.connLinkContact, short: $showShortLink) } } .alert(item: $alert) { alert in @@ -124,7 +137,7 @@ struct GroupLinkView: View { .onChange(of: groupLinkMemberRole) { _ in Task { do { - _ = try await apiGroupLinkMemberRole(groupId, memberRole: groupLinkMemberRole) + groupLink = try await apiGroupLinkMemberRole(groupId, memberRole: groupLinkMemberRole) } catch let error { let a = getErrorAlert(error, "Error updating group link") alert = .error(title: a.title, error: a.message) @@ -145,10 +158,10 @@ struct GroupLinkView: View { Task { do { creatingLink = true - let link = try await apiCreateGroupLink(groupId) + let gLink = try await apiCreateGroupLink(groupId) await MainActor.run { creatingLink = false - (groupLink, groupLinkMemberRole) = link + groupLink = gLink } } catch let error { logger.error("GroupLinkView apiCreateGroupLink: \(responseError(error))") @@ -160,12 +173,61 @@ struct GroupLinkView: View { } } } + + private func upgradeAndShareLinkAlert(groupLink: GroupLink? = nil) { + showAlert( + NSLocalizedString("Upgrade group link?", comment: "alert message"), + message: NSLocalizedString("The link will be short, and group profile will be shared via the link.", comment: "alert message"), + actions: { + var actions = [UIAlertAction(title: NSLocalizedString("Upgrade", comment: "alert button"), style: .default) { _ in + addShortLink(shareOnCompletion: groupLink != nil) + }] + if let groupLink { + actions.append(UIAlertAction(title: NSLocalizedString("Share old link", comment: "alert button"), style: .default) { _ in + groupLink.shareAddress(short: showShortLink) + }) + } + actions.append(cancelAlertAction) + return actions + } + ) + } + + private func addShortLink(shareOnCompletion: Bool = false) { + Task { + do { + creatingLink = true + let gLink = try await apiAddGroupShortLink(groupId) + await MainActor.run { + creatingLink = false + groupLink = gLink + if shareOnCompletion, let gLink { + gLink.shareAddress(short: showShortLink) + } + } + } catch let error { + logger.error("apiAddGroupShortLink: \(responseError(error))") + await MainActor.run { + creatingLink = false + let a = getErrorAlert(error, "Error adding short link") + alert = .error(title: a.title, error: a.message) + } + } + } + } } struct GroupLinkView_Previews: PreviewProvider { static var previews: some View { - @State var groupLink: CreatedConnLink? = CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil) - @State var noGroupLink: CreatedConnLink? = nil + @State var groupLink: GroupLink? = GroupLink( + userContactLinkId: 1, + connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil), + shortLinkDataSet: false, + shortLinkLargeDataSet: false, + groupLinkId: "abc", + acceptMemberRole: .member + ) + @State var noGroupLink: GroupLink? = nil return Group { GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member)) @@ -173,4 +235,3 @@ struct GroupLinkView_Previews: PreviewProvider { } } } - diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 79ad242366..2298af614e 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -16,7 +16,9 @@ struct GroupMemberInfoView: View { @State var groupInfo: GroupInfo @ObservedObject var chat: Chat @ObservedObject var groupMember: GMember + @Binding var scrollToItemId: ChatItem.ID? var navigation: Bool = false + var openedFromSupportChat: Bool = false @State private var connectionStats: ConnectionStats? = nil @State private var connectionCode: String? = nil @State private var connectionLoaded: Bool = false @@ -25,7 +27,6 @@ struct GroupMemberInfoView: View { @State private var knownContactConnectionStats: ConnectionStats? = nil @State private var newRole: GroupMemberRole = .member @State private var alert: GroupMemberInfoViewAlert? - @State private var sheet: PlanAndConnectActionSheet? @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @State private var justOpened = true @State private var progressIndicator = false @@ -40,7 +41,6 @@ struct GroupMemberInfoView: View { case switchAddressAlert case abortSwitchAddressAlert case syncConnectionForceAlert - case planAndConnectAlert(alert: PlanAndConnectAlert) case queueInfo(info: String) case someAlert(alert: SomeAlert) case error(title: LocalizedStringKey, error: LocalizedStringKey?) @@ -56,7 +56,6 @@ struct GroupMemberInfoView: View { case .switchAddressAlert: return "switchAddressAlert" case .abortSwitchAddressAlert: return "abortSwitchAddressAlert" case .syncConnectionForceAlert: return "syncConnectionForceAlert" - case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)" case let .queueInfo(info): return "queueInfo \(info)" case let .someAlert(alert): return "someAlert \(alert.id)" case let .error(title, _): return "error \(title)" @@ -103,6 +102,11 @@ struct GroupMemberInfoView: View { if member.memberActive { Section { + if !openedFromSupportChat + && groupInfo.membership.memberRole >= .moderator + && (member.memberRole < .moderator || member.supportChat != nil) { + MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId) + } if let code = connectionCode { verifyCodeButton(code) } if let connStats = connectionStats, connStats.ratchetSyncAllowed { @@ -178,7 +182,7 @@ struct GroupMemberInfoView: View { } } - if groupInfo.membership.memberRole >= .admin { + if groupInfo.membership.memberRole >= .moderator { adminDestructiveSection(member) } else { nonAdminBlockSection(member) @@ -195,8 +199,9 @@ struct GroupMemberInfoView: View { Button ("Debug delivery") { Task { do { - let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) - await MainActor.run { alert = .queueInfo(info: info) } + if let info = try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId) { + await MainActor.run { alert = .queueInfo(info: queueInfoText(info)) } + } } catch let e { logger.error("apiContactQueueInfo error: \(responseError(e))") let a = getErrorAlert(e, "Error") @@ -265,20 +270,18 @@ struct GroupMemberInfoView: View { case .switchAddressAlert: return switchAddressAlert(switchMemberAddress) case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress) case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) }) - case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true) case let .queueInfo(info): return queueInfoAlert(info) case let .someAlert(a): return a.alert case let .error(title, error): return mkAlert(title: title, message: error) } } - .actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) } if progressIndicator { ProgressView().scaleEffect(2) } } .onChange(of: chat.chatInfo) { c in - if case let .group(gI) = chat.chatInfo { + if case let .group(gI, _) = chat.chatInfo { groupInfo = gI } } @@ -345,10 +348,8 @@ struct GroupMemberInfoView: View { Button { planAndConnect( contactLink, - showAlert: { alert = .planAndConnectAlert(alert: $0) }, - showActionSheet: { sheet = $0 }, - dismiss: true, - incognito: nil + theme: theme, + dismiss: true ) } label: { Label("Connect", systemImage: "link") @@ -445,35 +446,70 @@ struct GroupMemberInfoView: View { MemberProfileImage(mem, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.top, 12) .padding() + // show alias if set, alias cannot be edited in this view + let displayName = mem.displayName.trimmingCharacters(in: .whitespacesAndNewlines) + let fullName = mem.fullName.trimmingCharacters(in: .whitespacesAndNewlines) if mem.verified { ( Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) + textSpace - + Text(mem.displayName) + + Text(displayName) .font(.largeTitle) ) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } else { - Text(mem.displayName) + Text(displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } - if mem.fullName != "" && mem.fullName != mem.displayName { + if fullName != "" && fullName != displayName && fullName != mem.memberProfile.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { Text(mem.fullName) .font(.title2) .multilineTextAlignment(.center) + .lineLimit(3) + .padding(.bottom, 2) + } + if let descr = mem.memberProfile.shortDescr?.trimmingCharacters(in: .whitespacesAndNewlines), descr != "" { + Text(descr) + .font(.subheadline) + .multilineTextAlignment(.center) .lineLimit(4) } } .frame(maxWidth: .infinity, alignment: .center) } + struct MemberInfoSupportChatNavLink: View { + @EnvironmentObject var theme: AppTheme + var groupInfo: GroupInfo + var member: GMember + @Binding var scrollToItemId: ChatItem.ID? + @State private var navLinkActive = false + + var body: some View { + let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: member.wrapped) + NavigationLink(isActive: $navLinkActive) { + SecondaryChatView( + chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()), + scrollToItemId: $scrollToItemId + ) + } label: { + Label("Chat with member", systemImage: "flag") + } + .onChange(of: navLinkActive) { active in + if active { + ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo)) + } + } + } + } + private func verifyCodeButton(_ code: String) -> some View { let member = groupMember.wrapped return NavigationLink { @@ -610,10 +646,11 @@ struct GroupMemberInfoView: View { primaryButton: .destructive(Text("Remove")) { Task { do { - let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId]) + let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId]) await MainActor.run { + chatModel.updateGroup(updatedGroupInfo) updatedMembers.forEach { updatedMember in - _ = chatModel.upsertGroupMember(groupInfo, updatedMember) + _ = chatModel.upsertGroupMember(updatedGroupInfo, updatedMember) } dismiss() } @@ -821,7 +858,8 @@ struct GroupMemberInfoView_Previews: PreviewProvider { GroupMemberInfoView( groupInfo: GroupInfo.sampleData, chat: Chat.sampleData, - groupMember: GMember.sampleData + groupMember: GMember.sampleData, + scrollToItemId: Binding.constant(nil) ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift index 9bb4a0cc35..cdbed7fe30 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift @@ -17,6 +17,7 @@ let MAX_VISIBLE_MEMBER_ROWS: CGFloat = 4.8 struct GroupMentionsView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme + var im: ItemsModel var groupInfo: GroupInfo @Binding var composeState: ComposeState @Binding var selectedRange: NSRange @@ -66,7 +67,7 @@ struct GroupMentionsView: View { } } .frame(maxHeight: MEMBER_ROW_SIZE * min(MAX_VISIBLE_MEMBER_ROWS, CGFloat(filtered.count))) - .background(Color(UIColor.systemBackground)) + .background(theme.colors.background) if #available(iOS 16.0, *) { scroll.scrollDismissesKeyboard(.never) @@ -93,12 +94,33 @@ struct GroupMentionsView: View { currentMessage = composeState.message } } - + + func contextMemberFilter(_ member: GroupMember) -> Bool { + switch im.secondaryIMFilter { + case nil: + return true + case let .groupChatScopeContext(groupScopeInfo): + switch (groupScopeInfo) { + case let .memberSupport(groupMember_): + if let scopeMember = groupMember_ { + return member.memberRole >= .moderator || member.groupMemberId == scopeMember.groupMemberId + } else { + return member.memberRole >= .moderator + } + case .reports: + return false + } + case .msgContentTagContext: + return false + } + } + private func filteredMembers() -> [GMember] { let s = mentionName.lowercased() - return s.isEmpty - ? sortedMembers - : sortedMembers.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } + return sortedMembers.filter { + contextMemberFilter($0.wrapped) + && (s.isEmpty || $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s)) + } } private func messageChanged(_ msg: String, _ parsedMsg: [FormattedText], _ range: NSRange) { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index ed39c401ce..55b1dc6d2e 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -30,6 +30,14 @@ struct GroupPreferencesView: View { let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members" VStack { List { + Section { + MemberAdmissionButton( + groupInfo: $groupInfo, + admission: groupInfo.groupProfile.memberAdmission_, + currentAdmission: groupInfo.groupProfile.memberAdmission_, + creatingGroup: creatingGroup + ) + } featureSection(.timedMessages, $preferences.timedMessages.enable) featureSection(.fullDelete, $preferences.fullDelete.enable) featureSection(.directMessages, $preferences.directMessages.enable, $preferences.directMessages.role) @@ -140,6 +148,66 @@ struct GroupPreferencesView: View { } } +struct MemberAdmissionButton: View { + @Binding var groupInfo: GroupInfo + @State var admission: GroupMemberAdmission + @State var currentAdmission: GroupMemberAdmission + var creatingGroup: Bool = false + + var body: some View { + NavigationLink { + MemberAdmissionView( + groupInfo: $groupInfo, + admission: $admission, + currentAdmission: currentAdmission, + creatingGroup: creatingGroup, + saveAdmission: saveAdmission + ) + .navigationBarTitle("Member admission") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + .onDisappear { + let saveText = NSLocalizedString( + creatingGroup ? "Save" : "Save and notify group members", + comment: "alert button" + ) + + if groupInfo.groupProfile.memberAdmission_ != admission { + showAlert( + title: NSLocalizedString("Save admission settings?", comment: "alert title"), + buttonTitle: saveText, + buttonAction: { saveAdmission() }, + cancelButton: true + ) + } + } + } label: { + if creatingGroup { + Text("Set member admission") + } else { + Label("Member admission", systemImage: "switch.2") + } + } + } + + private func saveAdmission() { + Task { + do { + var gp = groupInfo.groupProfile + gp.memberAdmission = admission + let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp) + await MainActor.run { + groupInfo = gInfo + ChatModel.shared.updateGroup(gInfo) + currentAdmission = admission + } + } catch { + logger.error("MemberAdmissionView apiUpdateGroup error: \(responseError(error))") + } + } + } +} + struct GroupPreferencesView_Previews: PreviewProvider { static var previews: some View { GroupPreferencesView( diff --git a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift index 1617edd11f..69587c0152 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift @@ -26,6 +26,8 @@ struct GroupProfileView: View { @Environment(\.dismiss) var dismiss: DismissAction @Binding var groupInfo: GroupInfo @State var groupProfile: GroupProfile + @State private var shortDescr: String = "" + @State private var currentProfileHash: Int? @State private var showChooseSource = false @State private var showImagePicker = false @State private var showTakePhoto = false @@ -34,60 +36,54 @@ struct GroupProfileView: View { @FocusState private var focusDisplayName var body: some View { - return VStack(alignment: .leading) { - Text("Group profile is stored on members' devices, not on the servers.") - .padding(.vertical) + List { + EditProfileImage(profileImage: $groupProfile.image, showChooseSource: $showChooseSource) + .if(!focusDisplayName) { $0.padding(.top) } - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { - profileImageView(groupProfile.image) - if groupProfile.image != nil { - Button { - groupProfile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - } - } - } - - editImageButton { showChooseSource = true } - } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - ZStack(alignment: .topLeading) { - if !validNewProfileName() { + Section { + HStack { + TextField("Group display name", text: $groupProfile.displayName) + .focused($focusDisplayName) + if !validNewProfileName { Button { alert = .invalidName(validName: mkValidName(groupProfile.displayName)) } label: { Image(systemName: "exclamationmark.circle").foregroundColor(.red) } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) } - profileNameTextEdit("Group display name", $groupProfile.displayName) - .focused($focusDisplayName) } - .padding(.bottom) let fullName = groupInfo.groupProfile.fullName if fullName != "" && fullName != groupProfile.displayName { - profileNameTextEdit("Group full name (optional)", $groupProfile.fullName) - .padding(.bottom) + TextField("Group full name (optional)", text: $groupProfile.fullName) } - HStack(spacing: 20) { - Button("Cancel") { dismiss() } - Button("Save group profile") { saveProfile() } - .disabled(!canUpdateProfile()) + HStack { + TextField("Short description", text: $shortDescr) + if !shortDescrFitsLimit() { + Button { + showAlert(NSLocalizedString("Description too large", comment: "alert title")) + } label: { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } + } } + } footer: { + Text("Group profile is stored on members' devices, not on the servers.") } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + Section { + Button("Reset") { + groupProfile = groupInfo.groupProfile + shortDescr = groupInfo.groupProfile.shortDescr ?? "" + currentProfileHash = groupProfile.hashValue + } + .disabled( + currentProfileHash == groupProfile.hashValue && + (groupInfo.groupProfile.shortDescr ?? "") == shortDescr.trimmingCharacters(in: .whitespaces) + ) + Button("Save group profile", action: saveProfile) + .disabled(!canUpdateProfile) + } } - .padding() - .frame(maxHeight: .infinity, alignment: .top) .confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true @@ -95,6 +91,11 @@ struct GroupProfileView: View { Button("Choose from library") { showImagePicker = true } + if UIPasteboard.general.hasImages { + Button("Paste image") { + chosenImage = UIPasteboard.general.image + } + } } .fullScreenCover(isPresented: $showTakePhoto) { ZStack { @@ -120,8 +121,21 @@ struct GroupProfileView: View { } } .onAppear { + currentProfileHash = groupProfile.hashValue + shortDescr = groupInfo.groupProfile.shortDescr ?? "" DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - focusDisplayName = true + withAnimation { focusDisplayName = true } + } + } + .onDisappear { + if canUpdateProfile { + showAlert( + title: NSLocalizedString("Save group profile?", comment: "alert title"), + message: NSLocalizedString("Group profile was changed. If you save it, the updated profile will be sent to group members.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save (and notify members)", comment: "alert button"), + buttonAction: saveProfile, + cancelButton: true + ) } } .alert(item: $alert) { a in @@ -135,30 +149,39 @@ struct GroupProfileView: View { return createInvalidNameAlert(name, $groupProfile.displayName) } } - .contentShape(Rectangle()) - .onTapGesture { hideKeyboard() } + .navigationBarTitle("Group profile") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(focusDisplayName ? .inline : .large) } - private func canUpdateProfile() -> Bool { - groupProfile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName() + private var canUpdateProfile: Bool { + ( + currentProfileHash != groupProfile.hashValue || + (groupProfile.shortDescr ?? "") != shortDescr.trimmingCharacters(in: .whitespaces) + ) && + groupProfile.displayName.trimmingCharacters(in: .whitespaces) != "" && + validNewProfileName && + shortDescrFitsLimit() } - private func validNewProfileName() -> Bool { + private var validNewProfileName: Bool { groupProfile.displayName == groupInfo.groupProfile.displayName || validDisplayName(groupProfile.displayName.trimmingCharacters(in: .whitespaces)) } - func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding) -> some View { - TextField(label, text: name) - .padding(.leading, 32) + private func shortDescrFitsLimit() -> Bool { + chatJsonLength(shortDescr) <= MAX_BIO_LENGTH_BYTES } func saveProfile() { Task { do { groupProfile.displayName = groupProfile.displayName.trimmingCharacters(in: .whitespaces) + groupProfile.fullName = groupProfile.fullName.trimmingCharacters(in: .whitespaces) + groupProfile.shortDescr = shortDescr.trimmingCharacters(in: .whitespaces) let gInfo = try await apiUpdateGroup(groupInfo.groupId, groupProfile) await MainActor.run { + currentProfileHash = groupProfile.hashValue groupInfo = gInfo chatModel.updateGroup(gInfo) dismiss() @@ -174,6 +197,9 @@ struct GroupProfileView: View { struct GroupProfileView_Previews: PreviewProvider { static var previews: some View { - GroupProfileView(groupInfo: Binding.constant(GroupInfo.sampleData), groupProfile: GroupProfile.sampleData) + GroupProfileView( + groupInfo: Binding.constant(GroupInfo.sampleData), + groupProfile: GroupProfile.sampleData + ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 97bff70efb..f58f2c213d 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -59,7 +59,7 @@ struct GroupWelcomeView: View { } private func textPreview() -> some View { - let r = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: showSecrets, backgroundColor: UIColor(theme.colors.background)) + let r = markdownText(welcomeText, showSecrets: showSecrets, backgroundColor: theme.colors.background) return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) .frame(minHeight: 130, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) @@ -157,6 +157,9 @@ struct GroupWelcomeView: View { struct GroupWelcomeView_Previews: PreviewProvider { static var previews: some View { - GroupProfileView(groupInfo: Binding.constant(GroupInfo.sampleData), groupProfile: GroupProfile.sampleData) + GroupProfileView( + groupInfo: Binding.constant(GroupInfo.sampleData), + groupProfile: GroupProfile.sampleData + ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/MemberAdmissionView.swift b/apps/ios/Shared/Views/Chat/Group/MemberAdmissionView.swift new file mode 100644 index 0000000000..d80615b5d2 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/Group/MemberAdmissionView.swift @@ -0,0 +1,93 @@ +// +// MemberAdmissionView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 28.04.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +private let memberCriterias: [(criteria: MemberCriteria?, text: LocalizedStringKey)] = [ + (nil, "off"), + (.all, "all") +] + +struct MemberAdmissionView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @Binding var groupInfo: GroupInfo + @Binding var admission: GroupMemberAdmission + var currentAdmission: GroupMemberAdmission + let creatingGroup: Bool + let saveAdmission: () -> Void + @State private var showSaveDialogue = false + + var body: some View { + let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members" + VStack { + List { + admissionSection( + NSLocalizedString("Review members", comment: "admission stage"), + NSLocalizedString("Review members before admitting (\"knocking\").", comment: "admission stage description"), + $admission.review + ) + + if groupInfo.isOwner { + Section { + Button("Reset") { admission = currentAdmission } + Button(saveText) { saveAdmission() } + } + .disabled(currentAdmission == admission) + } + } + } + .modifier(BackButton(disabled: Binding.constant(false)) { + if currentAdmission == admission { + dismiss() + } else { + showSaveDialogue = true + } + }) + .confirmationDialog("Save admission settings?", isPresented: $showSaveDialogue) { + Button(saveText) { + saveAdmission() + dismiss() + } + Button("Exit without saving") { + admission = currentAdmission + dismiss() + } + } + } + + private func admissionSection(_ admissionStageStr: String, _ admissionStageDescrStr: String, _ memberCriteria: Binding) -> some View { + Section { + if groupInfo.isOwner { + Picker(admissionStageStr, selection: memberCriteria) { + ForEach(memberCriterias, id: \.criteria) { mc in + Text(mc.text) + } + } + .frame(height: 36) + } else { + infoRow(Text(admissionStageStr), memberCriteria.wrappedValue?.text ?? NSLocalizedString("off", comment: "member criteria value")) + } + } footer: { + Text(admissionStageDescrStr) + .foregroundColor(theme.colors.secondary) + } + } +} + +#Preview { + MemberAdmissionView( + groupInfo: Binding.constant(GroupInfo.sampleData), + admission: Binding.constant(GroupMemberAdmission.sampleData), + currentAdmission: GroupMemberAdmission.sampleData, + creatingGroup: false, + saveAdmission: {} + ) +} diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportChatToolbar.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportChatToolbar.swift new file mode 100644 index 0000000000..23001e64bf --- /dev/null +++ b/apps/ios/Shared/Views/Chat/Group/MemberSupportChatToolbar.swift @@ -0,0 +1,44 @@ +// +// MemberSupportChatToolbar.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 01.05.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct MemberSupportChatToolbar: View { + @Environment(\.colorScheme) var colorScheme + @EnvironmentObject var theme: AppTheme + var groupMember: GroupMember + var imageSize: CGFloat = 32 + + var body: some View { + return HStack { + MemberProfileImage(groupMember, size: imageSize) + .padding(.trailing, 4) + let t = Text(groupMember.chatViewName).font(.headline) + (groupMember.verified ? memberVerifiedShield + t : t) + .lineLimit(1) + } + .foregroundColor(theme.colors.onBackground) + .frame(width: 220) + } + + private var memberVerifiedShield: Text { + (Text(Image(systemName: "checkmark.shield")) + textSpace) + .font(.caption) + .foregroundColor(theme.colors.secondary) + .baselineOffset(1) + .kerning(-2) + } +} + +#Preview { + MemberSupportChatToolbar( + groupMember: GroupMember.sampleData + ) + .environmentObject(CurrentColors.toAppTheme()) +} diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift new file mode 100644 index 0000000000..75a6840c4e --- /dev/null +++ b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift @@ -0,0 +1,297 @@ +// +// MemberSupportView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 28.04.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct MemberSupportView: View { + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @State private var searchText: String = "" + @FocusState private var searchFocussed + var groupInfo: GroupInfo + @Binding var scrollToItemId: ChatItem.ID? + + var body: some View { + viewBody() + .onAppear { + Task { + await chatModel.loadGroupMembers(groupInfo) + } + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + Task { + await chatModel.loadGroupMembers(groupInfo) + } + } label: { + Image(systemName: "arrow.clockwise") + } + } + } + } + + @ViewBuilder private func viewBody() -> some View { + let membersWithChats = sortedMembersWithChats() + let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase + let filteredMembersWithChats = s == "" + ? membersWithChats + : membersWithChats.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } + + if membersWithChats.isEmpty { + Text("No chats with members") + .foregroundColor(.secondary) + } else { + List { + searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary) + .padding(.leading, 8) + ForEach(filteredMembersWithChats) { memberWithChat in + MemberSupportChatNavLink( + groupInfo: groupInfo, + memberWithChat: memberWithChat, + scrollToItemId: $scrollToItemId + ) + } + } + } + } + + struct MemberSupportChatNavLink: View { + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @State private var memberSupportChatNavLinkActive = false + var groupInfo: GroupInfo + var memberWithChat: GMember + @Binding var scrollToItemId: ChatItem.ID? + + var body: some View { + ZStack { + let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: memberWithChat.wrapped) + Button { + ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo)) { + memberSupportChatNavLinkActive = true + } + } label: { + SupportChatRowView(groupMember: memberWithChat, groupInfo: groupInfo) + } + + NavigationLink(isActive: $memberSupportChatNavLinkActive) { + SecondaryChatView( + chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()), + scrollToItemId: $scrollToItemId + ) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + .if(!memberWithChat.wrapped.memberPending && memberWithChat.wrapped.supportChatNotRead) { v in + v.swipeActions(edge: .leading, allowsFullSwipe: true) { + Button { + Task { await markSupportChatRead(groupInfo, memberWithChat.wrapped) } + } label: { + Label("Read", systemImage: "checkmark") + } + .tint(theme.colors.primary) + } + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + if memberWithChat.wrapped.memberPending { + Button { + showAcceptMemberAlert(groupInfo, memberWithChat.wrapped) + } label: { + Label("Accept", systemImage: "checkmark") + } + .tint(theme.colors.primary) + } else { + Button { + showDeleteMemberSupportChatAlert(groupInfo, memberWithChat.wrapped) + } label: { + Label("Delete", systemImage: "trash") + } + .tint(.red) + } + } + } + } + + func sortedMembersWithChats() -> [GMember] { + chatModel.groupMembers + .filter { + $0.wrapped.supportChat != nil && + $0.wrapped.memberStatus != .memLeft && + $0.wrapped.memberStatus != .memRemoved + } + .sorted { (m0: GMember, m1: GMember) -> Bool in + if m0.wrapped.memberPending != m1.wrapped.memberPending { + return m0.wrapped.memberPending + } + + let mentions0 = (m0.wrapped.supportChat?.mentions ?? 0) > 0 + let mentions1 = (m1.wrapped.supportChat?.mentions ?? 0) > 0 + if mentions0 != mentions1 { + return mentions0 + } + + let attention0 = (m0.wrapped.supportChat?.memberAttention ?? 0) > 0 + let attention1 = (m1.wrapped.supportChat?.memberAttention ?? 0) > 0 + if attention0 != attention1 { + return attention0 + } + + let unread0 = (m0.wrapped.supportChat?.unread ?? 0) > 0 + let unread1 = (m1.wrapped.supportChat?.unread ?? 0) > 0 + if unread0 != unread1 { + return unread0 + } + + return (m0.wrapped.supportChat?.chatTs ?? .distantPast) > (m1.wrapped.supportChat?.chatTs ?? .distantPast) + } + } + + private struct SupportChatRowView: View { + @EnvironmentObject var chatModel: ChatModel + @ObservedObject var groupMember: GMember + @EnvironmentObject var theme: AppTheme + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + var groupInfo: GroupInfo + + var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize } + + var body: some View { + let member = groupMember.wrapped + HStack{ + MemberProfileImage(member, size: 38) + .padding(.trailing, 2) + VStack(alignment: .leading) { + let t = Text(member.chatViewName).foregroundColor(theme.colors.onBackground) + (member.verified ? memberVerifiedShield + t : t) + .lineLimit(1) + Text(memberStatus(member)) + .lineLimit(1) + .font(.caption) + .foregroundColor(theme.colors.secondary) + } + + Spacer() + + if member.memberPending { + Image(systemName: "flag.fill") + .resizable() + .scaledToFill() + .frame(width: dynamicChatInfoSize * 0.8, height: dynamicChatInfoSize * 0.8) + .foregroundColor(theme.colors.primary) + } + if let supportChat = member.supportChat { + SupportChatUnreadIndicator(supportChat: supportChat) + } + } + } + + private func memberStatus(_ member: GroupMember) -> LocalizedStringKey { + if member.activeConn?.connDisabled ?? false { + return "disabled" + } else if member.activeConn?.connInactive ?? false { + return "inactive" + } else if member.memberPending { + return member.memberStatus.text + } else { + return LocalizedStringKey(member.memberRole.text) + } + } + + struct SupportChatUnreadIndicator: View { + @EnvironmentObject var theme: AppTheme + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + var supportChat: GroupSupportChat + + var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize } + + private var indicatorTint: Color { + if supportChat.mentions > 0 || supportChat.memberAttention > 0 { + return theme.colors.primary + } else { + return theme.colors.secondary + } + } + + var body: some View { + HStack(alignment: .center, spacing: 2) { + if supportChat.unread > 0 || supportChat.mentions > 0 || supportChat.memberAttention > 0 { + if supportChat.mentions > 0 && supportChat.unread > 1 { + Text("\(MENTION_START)") + .font(userFont <= .xxxLarge ? .body : .callout) + .foregroundColor(indicatorTint) + .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize) + .cornerRadius(dynamicSize(userFont).unreadCorner) + .padding(.bottom, 1) + } + let singleUnreadIsMention = supportChat.mentions > 0 && supportChat.unread == 1 + (singleUnreadIsMention ? Text("\(MENTION_START)") : unreadCountText(supportChat.unread)) + .font(userFont <= .xxxLarge ? .caption : .caption2) + .foregroundColor(.white) + .padding(.horizontal, dynamicSize(userFont).unreadPadding) + .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize) + .background(indicatorTint) + .cornerRadius(dynamicSize(userFont).unreadCorner) + } + } + .frame(height: dynamicChatInfoSize) + .frame(minWidth: 22) + } + } + + private var memberVerifiedShield: Text { + (Text(Image(systemName: "checkmark.shield")) + textSpace) + .font(.caption) + .baselineOffset(2) + .kerning(-2) + .foregroundColor(theme.colors.secondary) + } + } +} + +func showDeleteMemberSupportChatAlert(_ groupInfo: GroupInfo, _ member: GroupMember) { + showAlert( + title: NSLocalizedString("Delete chat with member?", comment: "alert title"), + buttonTitle: "Delete", + buttonAction: { deleteMemberSupportChat(groupInfo, member) }, + cancelButton: true + ) +} + +func deleteMemberSupportChat(_ groupInfo: GroupInfo, _ member: GroupMember) { + Task { + do { + let (gInfo, updatedMember) = try await apiDeleteMemberSupportChat(groupInfo.groupId, member.groupMemberId) + await MainActor.run { + _ = ChatModel.shared.upsertGroupMember(gInfo, updatedMember) + ChatModel.shared.updateGroup(gInfo) + } + // TODO member row doesn't get removed from list (upsertGroupMember correctly sets supportChat to nil) - this repopulates list to fix it + await ChatModel.shared.loadGroupMembers(gInfo) + } catch let error { + logger.error("apiDeleteMemberSupportChat error: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error deleting chat", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + +#Preview { + MemberSupportView( + groupInfo: GroupInfo.sampleData, + scrollToItemId: Binding.constant(nil) + ) +} diff --git a/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift b/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift new file mode 100644 index 0000000000..e2092f7a24 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift @@ -0,0 +1,44 @@ +// +// SecondaryChatView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 29.04.2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct SecondaryChatView: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject var chatModel: ChatModel + @ObservedObject var chat: Chat + @Binding var scrollToItemId: ChatItem.ID? + + var body: some View { + if let im = chatModel.secondaryIM { + ChatView( + chat: chat, + im: im, + mergedItems: BoxedValue(MergedItems.create(im, [])), + floatingButtonModel: FloatingButtonModel(im: im), + scrollToItemId: $scrollToItemId + ) + .modifier(BackButton(disabled: Binding.constant(false)) { + chatModel.secondaryIM = nil + dismiss() + }) + } + } +} + +#Preview { + SecondaryChatView( + chat: Chat( + chatInfo: .group(groupInfo: GroupInfo.sampleData, groupChatScope: .memberSupport(groupMember_: GroupMember.sampleData)), + chatItems: [], + chatStats: ChatStats() + ), + scrollToItemId: Binding.constant(nil) + ) +} diff --git a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift index 85d6b279c5..4855c3ca8d 100644 --- a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift +++ b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift @@ -25,7 +25,7 @@ struct SelectedItemsTopToolbar: View { struct SelectedItemsBottomToolbar: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var theme: AppTheme - let chatItems: [ChatItem] + let im: ItemsModel @Binding var selectedChatItems: Set? var chatInfo: ChatInfo // Bool - delete for everyone is possible @@ -75,9 +75,9 @@ struct SelectedItemsBottomToolbar: View { .resizable() .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!moderateEnabled || deleteCountProhibited ? theme.colors.secondary : .red) + .foregroundColor(!moderateEnabled || deleteCountProhibited || im.secondaryIMFilter != nil ? theme.colors.secondary : .red) } - .disabled(!moderateEnabled || deleteCountProhibited) + .disabled(!moderateEnabled || deleteCountProhibited || im.secondaryIMFilter != nil) .opacity(canModerate ? 1 : 0) Spacer() @@ -88,24 +88,24 @@ struct SelectedItemsBottomToolbar: View { .resizable() .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!forwardEnabled || forwardCountProhibited ? theme.colors.secondary : theme.colors.primary) + .foregroundColor(!forwardEnabled || forwardCountProhibited || im.secondaryIMFilter != nil ? theme.colors.secondary : theme.colors.primary) } - .disabled(!forwardEnabled || forwardCountProhibited) + .disabled(!forwardEnabled || forwardCountProhibited || im.secondaryIMFilter != nil) } .frame(maxHeight: .infinity) .padding([.leading, .trailing], 12) } .onAppear { - recheckItems(chatInfo, chatItems, selectedChatItems) + recheckItems(chatInfo, im.reversedChatItems, selectedChatItems) } .onChange(of: chatInfo) { info in - recheckItems(info, chatItems, selectedChatItems) + recheckItems(info, im.reversedChatItems, selectedChatItems) } - .onChange(of: chatItems) { items in + .onChange(of: im.reversedChatItems) { items in recheckItems(chatInfo, items, selectedChatItems) } .onChange(of: selectedChatItems) { selected in - recheckItems(chatInfo, chatItems, selected) + recheckItems(chatInfo, im.reversedChatItems, selected) } .frame(height: 55.5) .background(.thinMaterial) @@ -116,7 +116,7 @@ struct SelectedItemsBottomToolbar: View { deleteCountProhibited = count == 0 || count > 200 forwardCountProhibited = count == 0 || count > 20 canModerate = possibleToModerate(chatInfo) - let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info) = chatInfo { + let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info, _) = chatInfo { info } else { nil @@ -145,8 +145,8 @@ struct SelectedItemsBottomToolbar: View { private func possibleToModerate(_ chatInfo: ChatInfo) -> Bool { return switch chatInfo { - case let .group(groupInfo): - groupInfo.membership.memberRole >= .admin + case let .group(groupInfo, _): + groupInfo.membership.memberRole >= .moderator default: false } } diff --git a/apps/ios/Shared/Views/Chat/VerifyCodeView.swift b/apps/ios/Shared/Views/Chat/VerifyCodeView.swift index 7b01fe0300..373311073a 100644 --- a/apps/ios/Shared/Views/Chat/VerifyCodeView.swift +++ b/apps/ios/Shared/Views/Chat/VerifyCodeView.swift @@ -24,85 +24,70 @@ struct VerifyCodeView: View { } private func verifyCodeView(_ code: String) -> some View { - ScrollView { - let splitCode = splitToParts(code, length: 24) - VStack(alignment: .leading) { - Group { + let splitCode = splitToParts(code, length: 24) + return List { + Section { + QRCode(uri: code, small: true) + + Text(splitCode) + .multilineTextAlignment(.leading) + .font(.body.monospaced()) + .lineLimit(20) + .frame(maxWidth: .infinity, alignment: .center) + } header: { + if connectionVerified { HStack { - if connectionVerified { - Image(systemName: "checkmark.shield") - .foregroundColor(theme.colors.secondary) - Text("\(displayName) is verified") - } else { - Text("\(displayName) is not verified") - } + Image(systemName: "checkmark.shield").foregroundColor(theme.colors.secondary) + Text("\(displayName) is verified").textCase(.none) } - .frame(height: 24) - - QRCode(uri: code) - .padding(.horizontal) - - Text(splitCode) - .multilineTextAlignment(.leading) - .font(.body.monospaced()) - .lineLimit(20) - .padding(.bottom, 8) + } else { + Text("\(displayName) is not verified").textCase(.none) } - .frame(maxWidth: .infinity, alignment: .center) - + } footer: { Text("To verify end-to-end encryption with your contact compare (or scan) the code on your devices.") - .padding(.bottom) + } - Group { - if connectionVerified { - Button { - verifyCode(nil) - } label: { - Label("Clear verification", systemImage: "shield") - } - .padding() - } else { - HStack { - NavigationLink { - ScanCodeView(connectionVerified: $connectionVerified, verify: verify) - .navigationBarTitleDisplayMode(.large) - .navigationTitle("Scan code") - .modifier(ThemedBackground()) - } label: { - Label("Scan code", systemImage: "qrcode") - } - .padding() - Button { - verifyCode(code) { verified in - if !verified { showCodeError = true } - } - } label: { - Label("Mark verified", systemImage: "checkmark.shield") - } - .padding() - .alert(isPresented: $showCodeError) { - Alert(title: Text("Incorrect security code!")) - } - } - } - } - .frame(maxWidth: .infinity, alignment: .center) - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { + Section { + if connectionVerified { Button { - showShareSheet(items: [splitCode]) + verifyCode(nil) } label: { - Image(systemName: "square.and.arrow.up") + Label("Clear verification", systemImage: "shield") + } + } else { + NavigationLink { + ScanCodeView(connectionVerified: $connectionVerified, verify: verify) + .navigationBarTitleDisplayMode(.large) + .navigationTitle("Scan code") + .modifier(ThemedBackground()) + } label: { + Label("Scan code", systemImage: "qrcode") + } + Button { + verifyCode(code) { verified in + if !verified { showCodeError = true } + } + } label: { + Label("Mark verified", systemImage: "checkmark.shield") + } + .alert(isPresented: $showCodeError) { + Alert(title: Text("Incorrect security code!")) } } } - .onChange(of: connectionVerified) { _ in - if connectionVerified { dismiss() } + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + showShareSheet(items: [splitCode]) + } label: { + Image(systemName: "square.and.arrow.up") + } } } + .onChange(of: connectionVerified) { _ in + if connectionVerified { dismiss() } + } } private func verifyCode(_ code: String?, _ cb: ((Bool) -> Void)? = nil) { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 81d78fbadd..4937bca20e 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -66,7 +66,7 @@ struct ChatListNavLink: View { switch chat.chatInfo { case let .direct(contact): contactNavLink(contact) - case let .group(groupInfo): + case let .group(groupInfo, _): groupNavLink(groupInfo) case let .local(noteFolder): noteFolderNavLink(noteFolder) @@ -92,7 +92,7 @@ struct ChatListNavLink: View { private func contactNavLink(_ contact: Contact) -> some View { Group { - if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { + if contact.isContactCard { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { @@ -122,29 +122,81 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) } ) .frameCompat(height: dynamicRowHeight) - .swipeActions(edge: .leading, allowsFullSwipe: true) { - markReadButton() - toggleFavoriteButton() - toggleNtfsButton(chat: chat) + .if(!contact.nextAcceptContactRequest) { v in + v.swipeActions(edge: .leading, allowsFullSwipe: true) { + markReadButton() + toggleFavoriteButton() + toggleNtfsButton(chat: chat) + } } .swipeActions(edge: .trailing, allowsFullSwipe: true) { - tagChatButton(chat) - if !chat.chatItems.isEmpty { - clearChatButton() + if contact.nextAcceptContactRequest { + if let contactRequestId = contact.contactRequestId { + Button { + Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) } + } label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) } + .tint(theme.colors.primary) + if !ChatModel.shared.addressShortLinkDataSet { + Button { + Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) } + } label: { + SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI) + } + .tint(.indigo) + } + Button { + AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequestId)) + } label: { + SwipeLabel(NSLocalizedString("Reject", comment: "swipe action"), systemImage: "multiply", inverted: oneHandUI) + } + .tint(.red) + } else if let groupDirectInv = contact.groupDirectInv, !groupDirectInv.memberRemoved { + Button { + acceptMemberContactRequest(contact) + } label: { + Label("Accept", systemImage: "checkmark") + } + .tint(theme.colors.primary) + Button { + showRejectMemberContactRequestAlert(contact) + } label: { + Label("Reject", systemImage: "multiply") + } + .tint(.red) + } else { + Button { + deleteContactDialog( + chat, + contact, + dismissToChatList: false, + showAlert: { alert = $0 }, + showActionSheet: { actionSheet = $0 }, + showSheetContent: { sheet = $0 } + ) + } label: { + deleteLabel + } + .tint(.red) + } + } else { + tagChatButton(chat) + if !chat.chatItems.isEmpty { + clearChatButton() + } + Button { + deleteContactDialog( + chat, + contact, + dismissToChatList: false, + showAlert: { alert = $0 }, + showActionSheet: { actionSheet = $0 }, + showSheetContent: { sheet = $0 } + ) + } label: { + deleteLabel + } + .tint(.red) } - Button { - deleteContactDialog( - chat, - contact, - dismissToChatList: false, - showAlert: { alert = $0 }, - showActionSheet: { actionSheet = $0 }, - showSheetContent: { sheet = $0 } - ) - } label: { - deleteLabel - } - .tint(.red) } } } @@ -189,7 +241,7 @@ struct ChatListNavLink: View { } .swipeActions(edge: .trailing) { tagChatButton(chat) - if (groupInfo.membership.memberCurrent) { + if (groupInfo.membership.memberCurrentOrPending) { leaveGroupChatButton(groupInfo) } if groupInfo.canDelete { @@ -214,7 +266,7 @@ struct ChatListNavLink: View { let showReportsButton = chat.chatStats.reportsCount > 0 && groupInfo.membership.memberRole >= .moderator let showClearButton = !chat.chatItems.isEmpty let showDeleteGroup = groupInfo.canDelete - let showLeaveGroup = groupInfo.membership.memberCurrent + let showLeaveGroup = groupInfo.membership.memberCurrentOrPending let totalNumberOfButtons = 1 + (showReportsButton ? 1 : 0) + (showClearButton ? 1 : 0) + (showDeleteGroup ? 1 : 0) + (showLeaveGroup ? 1 : 0) if showClearButton && totalNumberOfButtons <= 3 { @@ -276,7 +328,7 @@ struct ChatListNavLink: View { @ViewBuilder private func markReadButton() -> some View { if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat { Button { - Task { await markChatRead(chat) } + Task { await markChatRead(ItemsModel.shared, chat) } } label: { SwipeLabel(NSLocalizedString("Read", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) } @@ -436,28 +488,32 @@ struct ChatListNavLink: View { .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { - Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } + Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } } label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) } .tint(theme.colors.primary) - Button { - Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } - } label: { - SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI) + if !ChatModel.shared.addressShortLinkDataSet { + Button { + Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } + } label: { + SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI) + } + .tint(.indigo) } - .tint(.indigo) Button { - AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest)) + AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest.apiId)) } label: { - SwipeLabel(NSLocalizedString("Reject", comment: "swipe action"), systemImage: "multiply.fill", inverted: oneHandUI) + SwipeLabel(NSLocalizedString("Reject", comment: "swipe action"), systemImage: "multiply", inverted: oneHandUI) } .tint(.red) } .contentShape(Rectangle()) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { - Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } } - Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } } - Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } } + Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } } + if !ChatModel.shared.addressShortLinkDataSet { + Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } } + } + Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } } } } @@ -482,12 +538,10 @@ struct ChatListNavLink: View { .tint(theme.colors.primary) } .appSheet(isPresented: $showContactConnectionInfo) { - Group { - if case let .contactConnection(contactConnection) = chat.chatInfo { - ContactConnectionInfo(contactConnection: contactConnection) - .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) - .modifier(ThemedBackground(grouped: true)) - } + if case let .contactConnection(contactConnection) = chat.chatInfo { + ContactConnectionInfo(contactConnection: contactConnection) + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) + .modifier(ThemedBackground(grouped: true)) } } .contentShape(Rectangle()) @@ -621,12 +675,12 @@ extension View { } } -func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert { +func rejectContactRequestAlert(_ contactRequestId: Int64) -> Alert { Alert( title: Text("Reject contact request"), message: Text("The sender will NOT be notified"), primaryButton: .destructive(Text("Reject")) { - Task { await rejectContactRequest(contactRequest) } + Task { await rejectContactRequest(contactRequestId) } }, secondaryButton: .cancel() ) @@ -676,16 +730,17 @@ func joinGroup(_ groupId: Int64, _ onComplete: @escaping () async -> Void) { Task { logger.debug("joinGroup") do { - let r = try await apiJoinGroup(groupId) - switch r { - case let .joined(groupInfo): - await MainActor.run { ChatModel.shared.updateGroup(groupInfo) } - case .invitationRemoved: - AlertManager.shared.showAlertMsg(title: "Invitation expired!", message: "Group invitation is no longer valid, it was removed by sender.") - await deleteGroup() - case .groupNotFound: - AlertManager.shared.showAlertMsg(title: "No group!", message: "This group no longer exists.") - await deleteGroup() + if let r = try await apiJoinGroup(groupId) { + switch r { + case let .joined(groupInfo): + await MainActor.run { ChatModel.shared.updateGroup(groupInfo) } + case .invitationRemoved: + AlertManager.shared.showAlertMsg(title: "Invitation expired!", message: "Group invitation is no longer valid, it was removed by sender.") + await deleteGroup() + case .groupNotFound: + AlertManager.shared.showAlertMsg(title: "No group!", message: "This group no longer exists.") + await deleteGroup() + } } await onComplete() } catch let error { diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index f34f930c6f..0450bd439c 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -137,6 +137,7 @@ struct UserPickerSheetView: View { struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel + @StateObject private var connectProgressManager = ConnectProgressManager.shared @EnvironmentObject var theme: AppTheme @Binding var activeUserPickerSheet: UserPickerSheet? @State private var searchMode = false @@ -148,6 +149,7 @@ struct ChatListView: View { @State private var userPickerShown: Bool = false @State private var sheet: SomeSheet? = nil @StateObject private var chatTagsModel = ChatTagsModel.shared + @State private var scrollToItemId: ChatItem.ID? = nil // iOS 15 is required it to show/hide toolbar while chat is hidden/visible @State private var viewOnScreen = true @@ -446,7 +448,14 @@ struct ChatListView: View { @ViewBuilder private func chatView() -> some View { if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) { - ChatView(chat: chat) + let im = ItemsModel.shared + ChatView( + chat: chat, + im: im, + mergedItems: BoxedValue(MergedItems.create(im, [])), + floatingButtonModel: FloatingButtonModel(im: im), + scrollToItemId: $scrollToItemId + ) } } @@ -564,6 +573,7 @@ struct ChatListSearchBar: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @EnvironmentObject var chatTagsModel: ChatTagsModel + @StateObject private var connectProgressManager = ConnectProgressManager.shared @Binding var searchMode: Bool @FocusState.Binding var searchFocussed: Bool @Binding var searchText: String @@ -571,8 +581,6 @@ struct ChatListSearchBar: View { @Binding var searchChatFilteredBySimplexLink: String? @Binding var parentSheet: SomeSheet? @State private var ignoreSearchTextChange = false - @State private var alert: PlanAndConnectAlert? - @State private var sheet: PlanAndConnectActionSheet? var body: some View { VStack(spacing: 12) { @@ -585,6 +593,9 @@ struct ChatListSearchBar: View { .disabled(searchShowingSimplexLink) .focused($searchFocussed) .frame(maxWidth: .infinity) + if connectProgressManager.showConnectProgress != nil { + ProgressView() + } if !searchText.isEmpty { Image(systemName: "xmark.circle.fill") .onTapGesture { @@ -618,7 +629,7 @@ struct ChatListSearchBar: View { } else { if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue searchFocussed = false - if case let .simplexLink(linkType, _, smpHosts) = link.format { + if case let .simplexLink(_, linkType, _, smpHosts) = link.format { ignoreSearchTextChange = true searchText = simplexLinkText(linkType, smpHosts) } @@ -628,6 +639,8 @@ struct ChatListSearchBar: View { } else { if t != "" { // if some other text is pasted, enter search mode searchFocussed = true + } else { + ConnectProgressManager.shared.cancelConnectProgress() } searchShowingSimplexLink = false searchChatFilteredBySimplexLink = nil @@ -637,12 +650,6 @@ struct ChatListSearchBar: View { .onChange(of: chatTagsModel.activeFilter) { _ in searchText = "" } - .alert(item: $alert) { a in - planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" }) - } - .actionSheet(item: $sheet) { s in - planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" }) - } } private func toggleFilterButton() -> some View { @@ -668,10 +675,12 @@ struct ChatListSearchBar: View { private func connect(_ link: String) { planAndConnect( link, - showAlert: { alert = $0 }, - showActionSheet: { sheet = $0 }, + theme: theme, dismiss: false, - incognito: nil, + cleanup: { + searchText = "" + searchFocussed = false + }, filterKnownContact: { searchChatFilteredBySimplexLink = $0.id }, filterKnownGroup: { searchChatFilteredBySimplexLink = $0.id } ) @@ -889,15 +898,15 @@ func presetTagMatchesChat(_ tag: PresetTag, _ chatInfo: ChatInfo, _ chatStats: C chatInfo.chatSettings?.favorite == true case .contacts: switch chatInfo { - case let .direct(contact): !(contact.activeConn == nil && contact.profile.contactLink != nil && contact.active) && !contact.chatDeleted + case let .direct(contact): !contact.isContactCard && !contact.chatDeleted case .contactRequest: true case .contactConnection: true - case let .group(groupInfo): groupInfo.businessChat?.chatType == .customer + case let .group(groupInfo, _): groupInfo.businessChat?.chatType == .customer default: false } case .groups: switch chatInfo { - case let .group(groupInfo): groupInfo.businessChat == nil + case let .group(groupInfo, _): groupInfo.businessChat == nil default: false } case .business: diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index b8c8233e6e..c56d947a5a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -24,7 +24,7 @@ struct ChatPreviewView: View { var dynamicMediaSize: CGFloat { dynamicSize(userFont).mediaSize } var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize } - + var body: some View { let cItem = chat.chatItems.last return ZStack { @@ -35,7 +35,7 @@ struct ChatPreviewView: View { .padding([.bottom, .trailing], 1) } .padding(.leading, 4) - + let chatTs = if let cItem { cItem.meta.itemTs } else { @@ -53,7 +53,7 @@ struct ChatPreviewView: View { } .padding(.bottom, 4) .padding(.horizontal, 8) - + ZStack(alignment: .topTrailing) { let chat = activeContentPreview?.chat ?? chat let ci = activeContentPreview?.ci ?? chat.chatItems.last @@ -88,14 +88,14 @@ struct ChatPreviewView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(.trailing, 8) - + Spacer() } .frame(maxHeight: .infinity) } .opacity(deleting ? 0.4 : 1) .padding(.bottom, -8) - + if deleting { ProgressView() .scaleEffect(2) @@ -141,7 +141,7 @@ struct ChatPreviewView: View { } else { EmptyView() } - case let .group(groupInfo): + case let .group(groupInfo, _): switch (groupInfo.membership.memberStatus) { case .memRejected: inactiveIcon() case .memLeft: inactiveIcon() @@ -164,14 +164,26 @@ struct ChatPreviewView: View { let t = Text(chat.chatInfo.chatViewName).font(.title3).fontWeight(.bold) switch chat.chatInfo { case let .direct(contact): - previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(deleting ? Color.secondary : nil) - case let .group(groupInfo): - let v = previewTitle(t) - switch (groupInfo.membership.memberStatus) { - case .memInvited: v.foregroundColor(deleting ? theme.colors.secondary : chat.chatInfo.incognito ? .indigo : theme.colors.primary) - case .memAccepted, .memRejected: v.foregroundColor(theme.colors.secondary) - default: if deleting { v.foregroundColor(theme.colors.secondary) } else { v } + let color = + deleting + ? theme.colors.secondary + : (contact.nextAcceptContactRequest && !(contact.groupDirectInv?.memberRemoved ?? false)) || contact.sendMsgToConnect + ? theme.colors.primary + : !contact.sndReady + ? theme.colors.secondary + : nil + previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(color) + case let .group(groupInfo, _): + let color = if deleting { + theme.colors.secondary + } else { + switch (groupInfo.membership.memberStatus) { + case .memInvited: chat.chatInfo.incognito ? .indigo : theme.colors.primary + case .memAccepted, .memRejected: theme.colors.secondary + default: groupInfo.nextConnectPrepared ? theme.colors.primary : nil + } } + previewTitle(t).foregroundColor(color) default: previewTitle(t) } } @@ -251,7 +263,7 @@ struct ChatPreviewView: View { Color.clear.frame(width: 0) } } - + private func mentionColor(_ chat: Chat) -> Color { switch chat.chatInfo.chatSettings?.enableNtfs { case .all: theme.colors.primary @@ -262,7 +274,7 @@ struct ChatPreviewView: View { private func messageDraft(_ draft: ComposeState) -> (Text, Bool) { let msg = draft.message - let r = messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(theme.colors.background)) + let r = markdownText(msg, preview: true, mentions: draft.mentions, backgroundColor: theme.colors.background) return (image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) + attachment() + Text(AttributedString(r.string)), @@ -285,7 +297,7 @@ struct ChatPreviewView: View { func chatItemPreview(_ cItem: ChatItem) -> (Text, Bool) { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText() let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - let r = messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, backgroundColor: UIColor(theme.colors.background), prefix: prefix()) + let r = messageText(itemText, itemFormattedText, sender: cItem.meta.showGroupAsSender ? nil : cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, backgroundColor: UIColor(theme.colors.background), prefix: prefix()) return (Text(AttributedString(r.string)), r.hasSecrets) // same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey; @@ -312,7 +324,7 @@ struct ChatPreviewView: View { default: return nil } } - + func prefix() -> NSAttributedString? { switch cItem.content.msgContent { case let .report(_, reason): reason.attrString @@ -325,31 +337,50 @@ struct ChatPreviewView: View { if chatModel.draftChatId == chat.id, let draft = chatModel.draft { let (t, hasSecrets) = messageDraft(draft) chatPreviewLayout(t, draft: true, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) + } else if cItem?.content.hasMsgContent != true, let previewText = chatPreviewInfoText() { + chatPreviewInfoTextLayout(previewText) } else if let cItem = cItem { let (t, hasSecrets) = chatItemPreview(cItem) chatPreviewLayout(itemStatusMark(cItem) + t, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) - } else { - switch (chat.chatInfo) { - case let .direct(contact): - if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { - chatPreviewInfoText("Tap to Connect") - .foregroundColor(theme.colors.primary) - } else if !contact.sndReady && contact.activeConn != nil { - if contact.nextSendGrpInv { - chatPreviewInfoText("send direct message") - } else if contact.active { - chatPreviewInfoText("connecting…") - } - } - case let .group(groupInfo): - switch (groupInfo.membership.memberStatus) { - case .memRejected: chatPreviewInfoText("rejected") - case .memInvited: groupInvitationPreviewText(groupInfo) - case .memAccepted: chatPreviewInfoText("connecting…") - default: EmptyView() - } - default: EmptyView() + } + } + + private func chatPreviewInfoText() -> Text? { + switch (chat.chatInfo) { + case let .direct(contact): + if contact.isContactCard { + Text("Tap to Connect") + .foregroundColor(theme.colors.primary) + } else if contact.isBot && contact.nextConnectPrepared { + Text("Open to use bot") + } else if contact.sendMsgToConnect { + Text("Open to connect") + } else if contact.nextAcceptContactRequest { + Text("Open to accept") + } else if !contact.sndReady && contact.activeConn != nil && contact.active { + (contact.preparedContact?.uiConnLinkType == .con && !contact.isBot) || contact.contactGroupMemberId != nil + ? Text("contact should accept…") + : Text("connecting…") + } else { + nil } + case let .group(groupInfo, _): + if groupInfo.nextConnectPrepared { + if groupInfo.businessChat?.chatType == .business { + Text("Open to connect") + } else { + Text("Open to join") + } + } else { + switch (groupInfo.membership.memberStatus) { + case .memRejected: Text("rejected") + case .memInvited: groupInvitationPreviewText(groupInfo) + case .memAccepted: Text("connecting…") + case .memPendingReview, .memPendingApproval: Text("reviewed by admins") + default: nil + } + } + default: nil } } @@ -399,14 +430,14 @@ struct ChatPreviewView: View { } - @ViewBuilder private func groupInvitationPreviewText(_ groupInfo: GroupInfo) -> some View { + private func groupInvitationPreviewText(_ groupInfo: GroupInfo) -> Text { groupInfo.membership.memberIncognito - ? chatPreviewInfoText("join as \(groupInfo.membership.memberProfile.displayName)") - : chatPreviewInfoText("you are invited to group") + ? Text("Join as \(groupInfo.membership.memberProfile.displayName)") + : Text("You are invited to group") } - private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { - Text(text) + private func chatPreviewInfoTextLayout(_ text: Text) -> some View { + text .frame(maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading) .padding([.leading, .trailing], 8) .padding(.bottom, 4) @@ -430,7 +461,7 @@ struct ChatPreviewView: View { let size = dynamicSize(userFont).incognitoSize switch chat.chatInfo { case let .direct(contact): - if contact.active && contact.activeConn != nil { + if contact.active, let status = contact.activeConn?.connStatus, status == .ready || status == .sndReady { NetworkStatusView(contact: contact, size: size) } else { incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) @@ -439,7 +470,11 @@ struct ChatPreviewView: View { if progressByTimeout { ProgressView() } else if chat.chatStats.reportsCount > 0 { - groupReportsIcon(size: size * 0.8) + flagIcon(size: size * 0.8, color: .red) + } else if chat.supportUnreadCount > 0 { + flagIcon(size: size * 0.8, color: theme.colors.primary) + } else if chat.chatInfo.groupInfo?.membership.memberPending ?? false { + flagIcon(size: size * 0.8, color: theme.colors.secondary) } else { incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size) } @@ -485,12 +520,12 @@ struct ChatPreviewView: View { } } -func groupReportsIcon(size: CGFloat) -> some View { +func flagIcon(size: CGFloat, color: Color) -> some View { Image(systemName: "flag") .resizable() .scaledToFit() .frame(width: size, height: size) - .foregroundColor(.red) + .foregroundColor(color) } func smallContentPreview(size: CGFloat, _ view: @escaping () -> some View) -> some View { diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index b9f5b984e1..124c5ee7ba 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -114,6 +114,7 @@ struct ContactConnectionInfo: View { .onAppear { localAlias = contactConnection.localAlias } + .onDisappear(perform: setConnectionAlias) } private func setConnectionAlias() { diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 8b0a8af888..ee7605dbd2 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -412,7 +412,7 @@ struct SubscriptionStatusIndicatorView: View { var hasSess: Bool var body: some View { - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage( + let (color, variableValue, opacity) = subscriptionStatusInfo( online: m.networkInfo.online, usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, subs: subs, @@ -431,25 +431,19 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel - @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage( - online: m.networkInfo.online, - usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, - subs: subs, - hasSess: hasSess, - primaryColor: theme.colors.primary - ) - Text(verbatim: "\(Int(floor(statusPercent * 100)))%") + let statusPercent = subscriptionStatusPercent(online: m.networkInfo.online, subs: subs, hasSess: hasSess) + let percentText: String = subs.total > 0 || hasSess ? "\(Int(floor(statusPercent * 100)))%" : "%" + Text(percentText) .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double, Double) { +func subscriptionStatusInfo(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 @@ -457,26 +451,28 @@ func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: S } let activeColor: Color = usesProxy ? .indigo : primaryColor - let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0) + let noConnColorAndPercent: (Color, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1) let activeSubsRounded = roundedToQuarter(subs.shareOfActive) return !online ? noConnColorAndPercent - : ( - subs.total == 0 && !hasSess - ? (activeColor, 0, 0.33, 0) // On freshly installed app (without chats) and on app start - : ( - subs.ssActive == 0 - ? ( - hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent - ) - : ( // ssActive > 0 - hasSess - ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) - : (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error - ) - ) + : subs.total == 0 && !hasSess + ? (activeColor, 0, 0.33) // On freshly installed app (without chats) and on app start + : subs.ssActive == 0 + ? ( + hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive) : noConnColorAndPercent ) + : ( // ssActive > 0 + hasSess + ? (activeColor, activeSubsRounded, subs.shareOfActive) + : (.orange, activeSubsRounded, subs.shareOfActive) // This would mean implementation error + ) +} + +func subscriptionStatusPercent(online: Bool, subs: SMPServerSubs, hasSess: Bool) -> Double { + online && (hasSess || (subs.total > 0 && subs.ssActive > 0)) + ? subs.shareOfActive + : 0 } struct SMPServerSummaryView: View { diff --git a/apps/ios/Shared/Views/ChatList/TagListView.swift b/apps/ios/Shared/Views/ChatList/TagListView.swift index 2063fe15de..79d122eabf 100644 --- a/apps/ios/Shared/Views/ChatList/TagListView.swift +++ b/apps/ios/Shared/Views/ChatList/TagListView.swift @@ -63,10 +63,7 @@ struct TagListView: View { NSLocalizedString("Delete list?", comment: "alert title"), message: String.localizedStringWithFormat(NSLocalizedString("All chats will be removed from the list %@, and the list deleted.", comment: "alert message"), text), actions: {[ - UIAlertAction( - title: NSLocalizedString("Cancel", comment: "alert action"), - style: .default - ), + cancelAlertAction, UIAlertAction( title: NSLocalizedString("Delete", comment: "alert action"), style: .destructive, diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index dbe10ad997..b1cd4015c6 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -97,7 +97,7 @@ struct UserPicker: View { } .onAppear { // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { + if case .active = scenePhase, hasChatCtrl() { currentUser = m.currentUser?.userId Task { do { @@ -124,7 +124,7 @@ struct UserPicker: View { ZStack(alignment: .topTrailing) { ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) if (u.unreadCount > 0) { - UnreadBadge(userInfo: u).offset(x: 4, y: -4) + userUnreadBadge(u, theme: theme).offset(x: 4, y: -4) } } .padding(.trailing, 6) @@ -171,19 +171,27 @@ struct UserPicker: View { } } +@inline(__always) +func userUnreadBadge(_ userInfo: UserInfo, theme: AppTheme) -> some View { + UnreadBadge( + count: userInfo.unreadCount, + color: userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary + ) +} + struct UnreadBadge: View { - var userInfo: UserInfo - @EnvironmentObject var theme: AppTheme @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + var count: Int + var color: Color var body: some View { let size = dynamicSize(userFont).chatInfoSize - unreadCountText(userInfo.unreadCount) + unreadCountText(count) .font(userFont <= .xxxLarge ? .caption : .caption2) .foregroundColor(.white) .padding(.horizontal, dynamicSize(userFont).unreadPadding) .frame(minWidth: size, minHeight: size) - .background(userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary) + .background(color) .cornerRadius(dynamicSize(userFont).unreadCorner) } } diff --git a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift index 456c46d318..fcfcde2c07 100644 --- a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift +++ b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift @@ -20,20 +20,17 @@ struct ContactListNavLink: View { @State private var showContactRequestDialog = false var body: some View { - let contactType = chatContactType(chat) - Group { switch (chat.chatInfo) { case let .direct(contact): - switch contactType { - case .recent: - recentContactNavLink(contact) - case .chatDeleted: - deletedChatNavLink(contact) - case .card: + if contact.nextAcceptContactRequest { + contactWithRequestNavLink(contact) + } else if contact.isContactCard { contactCardNavLink(contact) - default: - EmptyView() + } else if contact.chatDeleted { + deletedChatNavLink(contact) + } else if contact.active { + recentContactNavLink(contact) } case let .contactRequest(contactRequest): contactRequestNavLink(contactRequest) @@ -59,7 +56,7 @@ struct ContactListNavLink: View { ItemsModel.shared.loadOpenChat(contact.id) } } label: { - contactPreview(contact, titleColor: theme.colors.onBackground) + contactPreview(contact, titleColor: contact.sendMsgToConnect ? theme.colors.primary : theme.colors.onBackground) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { @@ -78,6 +75,67 @@ struct ContactListNavLink: View { } } + func contactWithRequestNavLink(_ contact: Contact) -> some View { + Button { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(contact.id) + } + } label: { + contactRequestPreview(color: contact.groupDirectInv?.memberRemoved == true ? theme.colors.secondary : theme.colors.primary) + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + if let contactRequestId = contact.contactRequestId { + Button { + Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) } + } label: { + Label("Accept", systemImage: "checkmark") + } + .tint(theme.colors.primary) + if !ChatModel.shared.addressShortLinkDataSet { + Button { + Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) } + } label: { + Label("Accept incognito", systemImage: "theatermasks") + } + .tint(.indigo) + } + Button { + alert = SomeAlert(alert: rejectContactRequestAlert(contactRequestId), id: "rejectContactRequestAlert") + } label: { + Label("Reject", systemImage: "multiply") + } + .tint(.red) + } else if let groupDirectInv = contact.groupDirectInv, !groupDirectInv.memberRemoved { + Button { + acceptMemberContactRequest(contact) + } label: { + Label("Accept", systemImage: "checkmark") + } + .tint(theme.colors.primary) + Button { + showRejectMemberContactRequestAlert(contact) + } label: { + Label("Reject", systemImage: "multiply") + } + .tint(.red) + } else { + Button { + deleteContactDialog( + chat, + contact, + dismissToChatList: false, + showAlert: { alert = $0 }, + showActionSheet: { actionSheet = $0 }, + showSheetContent: { sheet = $0 } + ) + } label: { + Label("Delete", systemImage: "trash") + } + .tint(.red) + } + } + } + func deletedChatNavLink(_ contact: Contact) -> some View { Button { Task { @@ -179,8 +237,14 @@ struct ContactListNavLink: View { .tint(.red) } .confirmationDialog("Connect with \(contact.chatViewName)", isPresented: $showConnectContactViaAddressDialog, titleVisibility: .visible) { - Button("Use current profile") { connectContactViaAddress_(contact, false) } - Button("Use new incognito profile") { connectContactViaAddress_(contact, true) } + if !contact.profileChangeProhibited { + Button("Use current profile") { connectContactViaAddress_(contact, false) } + Button("Use new incognito profile") { connectContactViaAddress_(contact, true) } + } else if !contact.contactConnIncognito { + Button("Use current profile") { connectContactViaAddress_(contact, false) } + } else { + Button("Use incognito profile") { connectContactViaAddress_(contact, true) } + } } } @@ -219,39 +283,43 @@ struct ContactListNavLink: View { Button { showContactRequestDialog = true } label: { - contactRequestPreview(contactRequest) + contactRequestPreview(color: theme.colors.primary) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { - Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } + Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } } label: { Label("Accept", systemImage: "checkmark") } .tint(theme.colors.primary) - Button { - Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } - } label: { - Label("Accept incognito", systemImage: "theatermasks") + if !ChatModel.shared.addressShortLinkDataSet { + Button { + Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } + } label: { + Label("Accept incognito", systemImage: "theatermasks") + } + .tint(.indigo) } - .tint(.indigo) Button { - alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest), id: "rejectContactRequestAlert") + alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest.apiId), id: "rejectContactRequestAlert") } label: { Label("Reject", systemImage: "multiply") } .tint(.red) } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { - Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } } - Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } } - Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } } + Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } } + if !ChatModel.shared.addressShortLinkDataSet { + Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } } + } + Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } } } } - func contactRequestPreview(_ contactRequest: UserContactRequest) -> some View { + func contactRequestPreview(color: Color) -> some View { HStack{ - ProfileImage(imageStr: contactRequest.image, size: 30) + ProfileImage(imageStr: chat.chatInfo.image, size: 30) Text(chat.chatInfo.chatViewName) - .foregroundColor(.accentColor) + .foregroundColor(color) .lineLimit(1) Spacer() @@ -260,7 +328,7 @@ struct ContactListNavLink: View { .resizable() .scaledToFill() .frame(width: 14, height: 14) - .foregroundColor(.accentColor) + .foregroundColor(color) } } } diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 59eee1338b..a7e61b3105 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -21,7 +21,7 @@ enum DatabaseAlert: Identifiable { case deleteLegacyDatabase case deleteFilesAndMedia case setChatItemTTL(ttl: ChatItemTTL) - case error(title: LocalizedStringKey, error: String = "") + case error(title: String, error: String = "") var id: String { switch self { @@ -456,7 +456,7 @@ struct DatabaseView: View { } } catch let error { await MainActor.run { - alert = .error(title: "Error exporting chat database", error: responseError(error)) + alert = .error(title: NSLocalizedString("Error exporting chat database", comment: "alert title"), error: responseError(error)) progressIndicator = false } } @@ -492,10 +492,10 @@ struct DatabaseView: View { return migration } } catch let error { - await operationEnded(.error(title: "Error importing chat database", error: responseError(error)), progressIndicator, alert) + await operationEnded(.error(title: NSLocalizedString("Error importing chat database", comment: "alert title"), error: responseError(error)), progressIndicator, alert) } } catch let error { - await operationEnded(.error(title: "Error deleting chat database", error: responseError(error)), progressIndicator, alert) + await operationEnded(.error(title: NSLocalizedString("Error deleting chat database", comment: "alert title"), error: responseError(error)), progressIndicator, alert) } } else { showAlert("Error accessing database file") @@ -513,7 +513,7 @@ struct DatabaseView: View { await DatabaseView.operationEnded(.chatDeleted, $progressIndicator, $alert) return true } catch let error { - await DatabaseView.operationEnded(.error(title: "Error deleting database", error: responseError(error)), $progressIndicator, $alert) + await DatabaseView.operationEnded(.error(title: NSLocalizedString("Error deleting database", comment: "alert title"), error: responseError(error)), $progressIndicator, $alert) return false } } @@ -522,7 +522,7 @@ struct DatabaseView: View { if removeLegacyDatabaseAndFiles() { legacyDatabase = false } else { - alert = .error(title: "Error deleting old database") + alert = .error(title: NSLocalizedString("Error deleting old database", comment: "alert title")) } } @@ -546,7 +546,7 @@ struct DatabaseView: View { let (title, message) = chatDeletedAlertText() showAlert(title, message: message, actions: { [okAlertActionWaiting] }) } else if case let .error(title, error) = dbAlert { - showAlert("\(title)", message: error, actions: { [okAlertActionWaiting] }) + showAlert(title, message: error, actions: { [okAlertActionWaiting] }) } else { alert.wrappedValue = dbAlert cont.resume() @@ -567,7 +567,7 @@ struct DatabaseView: View { } } catch { await MainActor.run { - alert = .error(title: "Error changing setting", error: responseError(error)) + alert = .error(title: NSLocalizedString("Error changing setting", comment: "alert title"), error: responseError(error)) chatItemTTL = currentChatItemTTL afterSetCiTTL() } diff --git a/apps/ios/Shared/Views/Helpers/AppSheet.swift b/apps/ios/Shared/Views/Helpers/AppSheet.swift index 1e334367e8..17fe95a058 100644 --- a/apps/ios/Shared/Views/Helpers/AppSheet.swift +++ b/apps/ios/Shared/Views/Helpers/AppSheet.swift @@ -33,7 +33,7 @@ extension View { func appSheet( isPresented: Binding, onDismiss: (() -> Void)? = nil, - content: @escaping () -> Content + @ViewBuilder content: @escaping () -> Content ) -> some View where Content: View { sheet(isPresented: isPresented, onDismiss: onDismiss) { content().modifier(PrivacySensitive()) @@ -43,7 +43,7 @@ extension View { func appSheet( item: Binding, onDismiss: (() -> Void)? = nil, - content: @escaping (T) -> Content + @ViewBuilder content: @escaping (T) -> Content ) -> some View where T: Identifiable, Content: View { sheet(item: item, onDismiss: onDismiss) { it in content(it).modifier(PrivacySensitive()) diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index 9aa6ac86cf..980308f13c 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -76,7 +76,7 @@ struct ChatTailPadding: ViewModifier { } } -private let msgRectMaxRadius: Double = 18 +let msgRectMaxRadius: Double = 18 private let msgBubbleMaxRadius: Double = msgRectMaxRadius * 1.2 private let msgTailWidth: Double = 9 private let msgTailMinHeight: Double = msgTailWidth * 1.254 // ~56deg diff --git a/apps/ios/Shared/Views/Helpers/CustomTimePicker.swift b/apps/ios/Shared/Views/Helpers/CustomTimePicker.swift index 09aae1cb15..edb10ef87d 100644 --- a/apps/ios/Shared/Views/Helpers/CustomTimePicker.swift +++ b/apps/ios/Shared/Views/Helpers/CustomTimePicker.swift @@ -220,6 +220,35 @@ struct DropdownCustomTimePicker: View { } } +struct WrappedPicker: View { + var selection: Binding + @ViewBuilder var content: () -> Content + @ViewBuilder var label: () -> Label + + init(_ title: LocalizedStringKey, selection: Binding, @ViewBuilder content: @escaping () -> Content) where Label == Text { + self.selection = selection + self.content = content + self.label = { Text(title) } + } + + init(selection: Binding, @ViewBuilder content: @escaping () -> Content, @ViewBuilder label: @escaping () -> Label) { + self.selection = selection + self.content = content + self.label = label + } + + var body: some View { + HStack(alignment: .firstTextBaseline) { + label() + Spacer() + Picker(selection: selection, content: content) { + EmptyView() + } + .frame(height: 36) + } + } +} + struct CustomTimePicker_Previews: PreviewProvider { static var previews: some View { CustomTimePicker( diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift index 3eedd56441..9c2916880c 100644 --- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift +++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift @@ -27,8 +27,13 @@ struct ProfileImage: View { Image(systemName: iconName) .resizable() .foregroundColor(c) + .scaledToFit() .frame(width: size, height: size) - .background(Circle().fill(backgroundColor != nil ? backgroundColor! : .clear)) + .background( + Circle() + .fill(backgroundColor != nil ? backgroundColor! : .clear) + .frame(width: size - 2, height: size - 2) // less than size of Image to avoid slightly visible border + ) } } } diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index b8de0e4ceb..86a5dc7aaa 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -65,6 +65,252 @@ func showAlert( } } +func showSheet( + _ title: String?, + message: String? = nil, + actions: () -> [UIAlertAction] = { [okAlertAction] }, + sourceView: UIView? = nil // For iPad support +) { + if let topController = getTopViewController() { + let sheet = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) + for action in actions() { sheet.addAction(action) } + + // Required for iPad: Configure popover presentation + if let popover = sheet.popoverPresentationController { + popover.sourceView = sourceView ?? topController.view + popover.sourceRect = sourceView?.bounds ?? CGRect(x: topController.view.bounds.midX, y: topController.view.bounds.midY, width: 0, height: 0) + popover.permittedArrowDirections = [] + } + + topController.present(sheet, animated: true) + } +} + let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default) let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) + +let alertProfileImageSize: CGFloat = 103 + +let alertWidth: CGFloat = 270 + +let alertButtonHeight: CGFloat = 44 + +class OpenChatAlertViewController: UIViewController { + private let profileName: String + private let profileFullName: String + private let profileImage: UIView + private let cancelTitle: String + private let confirmTitle: String + private let onCancel: () -> Void + private let onConfirm: () -> Void + + init( + profileName: String, + profileFullName: String, + profileImage: UIView, + cancelTitle: String = "Cancel", + confirmTitle: String = "Open", + onCancel: @escaping () -> Void, + onConfirm: @escaping () -> Void + ) { + self.profileName = profileName + self.profileFullName = profileFullName + self.profileImage = profileImage + self.cancelTitle = cancelTitle + self.confirmTitle = confirmTitle + self.onCancel = onCancel + self.onConfirm = onConfirm + super.init(nibName: nil, bundle: nil) + + modalPresentationStyle = .overFullScreen + modalTransitionStyle = .crossDissolve + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.black.withAlphaComponent(0.3) + + // Container view + let containerView = UIView() + containerView.backgroundColor = .systemBackground + containerView.layer.cornerRadius = 12 + containerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(containerView) + + // Profile image sizing + profileImage.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + profileImage.widthAnchor.constraint(equalToConstant: alertProfileImageSize), + profileImage.heightAnchor.constraint(equalToConstant: alertProfileImageSize) + ]) + + // Name label + let nameLabel = UILabel() + nameLabel.text = profileName + nameLabel.font = UIFont.preferredFont(forTextStyle: .headline) + nameLabel.textColor = .label + nameLabel.numberOfLines = 2 + nameLabel.textAlignment = .center + nameLabel.translatesAutoresizingMaskIntoConstraints = false + + var profileViews = [profileImage, nameLabel] + + // Full name label + if !profileFullName.isEmpty && profileFullName != profileName { + let fullNameLabel = UILabel() + fullNameLabel.text = profileFullName + fullNameLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + fullNameLabel.textColor = .label + fullNameLabel.numberOfLines = 2 + fullNameLabel.textAlignment = .center + fullNameLabel.translatesAutoresizingMaskIntoConstraints = false + profileViews.append(fullNameLabel) + } + + // Horizontal stack for image + name + let stack = UIStackView(arrangedSubviews: profileViews) + stack.axis = .vertical + stack.spacing = 12 + stack.alignment = .center + stack.translatesAutoresizingMaskIntoConstraints = false + + let topRowContainer = UIView() + topRowContainer.translatesAutoresizingMaskIntoConstraints = false + topRowContainer.addSubview(stack) + + NSLayoutConstraint.activate([ + stack.topAnchor.constraint(equalTo: topRowContainer.topAnchor), + stack.bottomAnchor.constraint(equalTo: topRowContainer.bottomAnchor), + stack.leadingAnchor.constraint(equalTo: topRowContainer.leadingAnchor, constant: 20), + stack.trailingAnchor.constraint(equalTo: topRowContainer.trailingAnchor, constant: -20) + ]) + + // Buttons + let cancelButton = UIButton(type: .system) + cancelButton.setTitle(cancelTitle, for: .normal) + let bodyDescr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + cancelButton.titleLabel?.font = UIFont(descriptor: bodyDescr.withSymbolicTraits(.traitBold) ?? bodyDescr, size: 0) + cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside) + + let confirmButton = UIButton(type: .system) + confirmButton.setTitle(confirmTitle, for: .normal) + confirmButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside) + + let verticalButtons = cancelButton.intrinsicContentSize.width + 20 >= alertWidth / 2 || confirmButton.intrinsicContentSize.width + 20 >= alertWidth / 2 + + // Button stack with equal width buttons + let buttonStack = UIStackView(arrangedSubviews: verticalButtons ? [confirmButton, cancelButton] : [cancelButton, confirmButton]) + buttonStack.axis = verticalButtons ? .vertical : .horizontal + buttonStack.distribution = .fillEqually + buttonStack.spacing = 0 // no spacing, use divider instead + buttonStack.translatesAutoresizingMaskIntoConstraints = false + buttonStack.heightAnchor.constraint(greaterThanOrEqualToConstant: alertButtonHeight * (verticalButtons ? 2 : 1)).isActive = true + + // Vertical stack containing hStack and buttonStack + let vStack = UIStackView(arrangedSubviews: [topRowContainer, buttonStack]) + vStack.axis = .vertical + vStack.spacing = 16 + vStack.alignment = .fill // important: buttons stretch full width + vStack.translatesAutoresizingMaskIntoConstraints = false + + containerView.addSubview(vStack) + + // Add horizontal divider above buttons + let horizontalDivider = UIView() + horizontalDivider.backgroundColor = UIColor.separator + horizontalDivider.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(horizontalDivider) + + // Add divider between buttons + let buttonDivider = UIView() + buttonDivider.backgroundColor = UIColor.separator + buttonDivider.translatesAutoresizingMaskIntoConstraints = false + buttonStack.addSubview(buttonDivider) + + // Constraints + let buttonDividerConstraints = if verticalButtons { + [ + buttonDivider.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + buttonDivider.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + buttonDivider.centerYAnchor.constraint(equalTo: buttonStack.centerYAnchor), + buttonDivider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale) + ] + } else { + [ + buttonDivider.topAnchor.constraint(equalTo: buttonStack.topAnchor), + buttonDivider.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + buttonDivider.centerXAnchor.constraint(equalTo: buttonStack.centerXAnchor), + buttonDivider.widthAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale) + ] + } + + NSLayoutConstraint.activate([ + // Container view centering and fixed width + containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + containerView.widthAnchor.constraint(equalToConstant: alertWidth), + + // Vertical stack padding inside containerView + vStack.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20), + vStack.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0), + vStack.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0), + vStack.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0), + + // Center hStack horizontally inside vStack's padded width + stack.centerXAnchor.constraint(equalTo: vStack.centerXAnchor), + + // Horizontal divider above buttons + horizontalDivider.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + horizontalDivider.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + horizontalDivider.bottomAnchor.constraint(equalTo: buttonStack.topAnchor), + horizontalDivider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale) + ] + buttonDividerConstraints) + } + + @objc private func cancelTapped() { + dismiss(animated: true) { + self.onCancel() + } + } + + @objc private func confirmTapped() { + dismiss(animated: true) { + self.onConfirm() + } + } +} + + +func showOpenChatAlert( + profileName: String, + profileFullName: String, + profileImage: Content, + theme: AppTheme, + cancelTitle: String = "Cancel", + confirmTitle: String = "Open", + onCancel: @escaping () -> Void = {}, + onConfirm: @escaping () -> Void +) { + let themedView = profileImage.environmentObject(theme) + let hostingController = UIHostingController(rootView: themedView) + let hostedView = hostingController.view! + hostedView.backgroundColor = .clear + + if let topVC = getTopViewController() { + let alertVC = OpenChatAlertViewController( + profileName: profileName, + profileFullName: profileFullName, + profileImage: hostedView, + cancelTitle: cancelTitle, + confirmTitle: confirmTitle, + onCancel: onCancel, + onConfirm: onConfirm + ) + topVC.present(alertVC, animated: true) + } +} diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift index 16ab26eff7..c21ff9be8b 100644 --- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift +++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift @@ -66,6 +66,8 @@ struct LocalAuthView: View { m.chatId = nil ItemsModel.shared.reversedChatItems = [] ItemsModel.shared.chatState.clear() + ChatModel.shared.secondaryIM?.reversedChatItems = [] + ChatModel.shared.secondaryIM?.chatState.clear() m.updateChats([]) m.users = [] _ = kcAppPassword.set(password) diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 87c0b80372..901b2deeab 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -23,7 +23,7 @@ struct AddGroupView: View { @State private var showTakePhoto = false @State private var chosenImage: UIImage? = nil @State private var showInvalidNameAlert = false - @State private var groupLink: CreatedConnLink? + @State private var groupLink: GroupLink? @State private var groupLinkMemberRole: GroupMemberRole = .member var body: some View { @@ -104,7 +104,9 @@ struct AddGroupView: View { } .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity, alignment: .leading) - .onTapGesture(perform: hideKeyboard) + .onTapGesture { + focusDisplayName = false + } } } .onAppear() { @@ -161,7 +163,8 @@ struct AddGroupView: View { } else { Image(systemName: "pencil").foregroundColor(theme.colors.secondary) } - textField("Enter group name…", text: $profile.displayName) + TextField("Enter group name…", text: $profile.displayName) + .padding(.leading, 36) .focused($focusDisplayName) .submitLabel(.continue) .onSubmit { @@ -170,11 +173,6 @@ struct AddGroupView: View { } } - func textField(_ placeholder: LocalizedStringKey, text: Binding) -> some View { - TextField(placeholder, text: text) - .padding(.leading, 36) - } - func sharedGroupProfileInfo(_ incognito: Bool) -> Text { let name = ChatModel.shared.currentUser?.displayName ?? "" return Text( @@ -185,7 +183,7 @@ struct AddGroupView: View { } func createGroup() { - hideKeyboard() + focusDisplayName = false do { profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on)) @@ -193,7 +191,7 @@ struct AddGroupView: View { Task { await m.loadGroupMembers(gInfo) } - let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: []) + let c = Chat(chatInfo: .group(groupInfo: gInfo, groupChatScope: nil), chatItems: []) m.addChat(c) withAnimation { groupInfo = gInfo @@ -217,6 +215,8 @@ struct AddGroupView: View { } } +// Using this method may freeze the app in some cases, so it should be avoided when possible, especially when combined with .focussed modifier. +// It also must only be called from background thread. func hideKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index e5263813fa..2e3119a8b8 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -9,10 +9,6 @@ import SwiftUI import SimpleXChat -enum ContactType: Int { - case card, request, recent, chatDeleted, unlisted -} - struct NewChatMenuButton: View { // do not use chatModel here because it prevents showing AddGroupMembersView after group creation and QR code after link creation on iOS 16 // @EnvironmentObject var chatModel: ChatModel @@ -20,7 +16,8 @@ struct NewChatMenuButton: View { @State private var alert: SomeAlert? = nil var body: some View { - Button { + Button { + ConnectProgressManager.shared.cancelConnectProgress() showNewChatSheet = true } label: { Image(systemName: "square.and.pencil") @@ -42,7 +39,6 @@ private var indent: CGFloat = 36 struct NewChatSheet: View { @EnvironmentObject var theme: AppTheme - @State private var baseContactTypes: [ContactType] = [.card, .request, .recent] @EnvironmentObject var chatModel: ChatModel @State private var searchMode = false @FocusState var searchFocussed: Bool @@ -60,7 +56,7 @@ struct NewChatSheet: View { @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true var body: some View { - let showArchive = !filterContactTypes(chats: chatModel.chats, contactTypes: [.chatDeleted]).isEmpty + let showArchive = chatModel.chats.contains { $0.chatInfo.contact?.chatDeleted == true } let v = NavigationView { viewBody(showArchive) .navigationTitle("New message") @@ -70,6 +66,8 @@ struct NewChatSheet: View { .alert(item: $alert) { a in return a.alert } + }.onDisappear { + ConnectProgressManager.shared.cancelConnectProgress() } if #available(iOS 16.0, *), oneHandUI { let sheetHeight: CGFloat = showArchive ? 575 : 500 @@ -125,7 +123,7 @@ struct NewChatSheet: View { } NavigationLink { AddGroupView() - .navigationTitle("Create secret group") + .navigationTitle("Create group") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { @@ -145,7 +143,7 @@ struct NewChatSheet: View { } ContactsList( - baseContactTypes: $baseContactTypes, + chatPredicate: contactListChatPredicate, searchMode: $searchMode, searchText: $searchText, header: "Your Contacts", @@ -156,7 +154,15 @@ struct NewChatSheet: View { ) } } - + + private func contactListChatPredicate(_ chat: Chat, _ withSearch: Bool) -> Bool { + switch chat.chatInfo { + case .contactRequest: true + case let .direct(contact): contact.isContactCard || contact.active || (contact.chatDeleted && withSearch) + default: false + } + } + /// Extends label's tap area to match `.insetGrouped` list row insets private func navigateOnTap(_ label: L, setActive: @escaping () -> Void) -> some View { label @@ -186,35 +192,24 @@ struct NewChatSheet: View { } } -func chatContactType(_ chat: Chat) -> ContactType { +func chatOrderRank(_ chat: Chat) -> Int { switch chat.chatInfo { - case .contactRequest: - return .request + case .contactRequest: 4 case let .direct(contact): - if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { - return .card - } else if contact.chatDeleted { - return .chatDeleted - } else if contact.contactStatus == .active { - return .recent - } else { - return .unlisted - } - default: - return .unlisted - } -} - -private func filterContactTypes(chats: [Chat], contactTypes: [ContactType]) -> [Chat] { - return chats.filter { chat in - contactTypes.contains(chatContactType(chat)) + contact.isContactCard ? 5 + : contact.nextAcceptContactRequest ? 4 + : contact.nextConnectPrepared ? 3 + : contact.active ? 2 + : contact.chatDeleted ? 1 + : 0 + default: 0 } } struct ContactsList: View { @EnvironmentObject var theme: AppTheme @EnvironmentObject var chatModel: ChatModel - @Binding var baseContactTypes: [ContactType] + var chatPredicate: (Chat, Bool) -> Bool // (chat, search) -> show @Binding var searchMode: Bool @Binding var searchText: String var header: String? = nil @@ -225,8 +220,7 @@ struct ContactsList: View { @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false var body: some View { - let contactTypes = contactTypesSearchTargets(baseContactTypes: baseContactTypes, searchEmpty: searchText.isEmpty) - let contactChats = filterContactTypes(chats: chatModel.chats, contactTypes: contactTypes) + let contactChats = chatModel.chats.filter { chat in chatPredicate(chat, !searchText.isEmpty) } let filteredContactChats = filteredContactChats( showUnreadAndFavorites: showUnreadAndFavorites, searchShowingSimplexLink: searchShowingSimplexLink, @@ -269,26 +263,11 @@ struct ContactsList: View { .listRowBackground(Color.clear) .listRowInsets(EdgeInsets(top: 7, leading: 0, bottom: 7, trailing: 0)) } - - private func contactTypesSearchTargets(baseContactTypes: [ContactType], searchEmpty: Bool) -> [ContactType] { - if baseContactTypes.contains(.chatDeleted) || searchEmpty { - return baseContactTypes - } else { - return baseContactTypes + [.chatDeleted] - } - } - - private func chatsByTypeComparator(chat1: Chat, chat2: Chat) -> Bool { - let chat1Type = chatContactType(chat1) - let chat2Type = chatContactType(chat2) - if chat1Type.rawValue < chat2Type.rawValue { - return true - } else if chat1Type.rawValue > chat2Type.rawValue { - return false - } else { - return chat2.chatInfo.chatTs < chat1.chatInfo.chatTs - } + private func chatComparator(chat1: Chat, chat2: Chat) -> Bool { + let r1 = chatOrderRank(chat1) + let r2 = chatOrderRank(chat2) + return r1 > r2 ? true : r1 < r2 ? false : chat1.chatInfo.chatTs > chat2.chatInfo.chatTs } private func filterChat(chat: Chat, searchText: String, showUnreadAndFavorites: Bool) -> Bool { @@ -333,12 +312,13 @@ struct ContactsList: View { } } - return filteredChats.sorted(by: chatsByTypeComparator) + return filteredChats.sorted(by: chatComparator) } } struct ContactsListSearchBar: View { @EnvironmentObject var m: ChatModel + @StateObject private var connectProgressManager = ConnectProgressManager.shared @EnvironmentObject var theme: AppTheme @Binding var searchMode: Bool @FocusState.Binding var searchFocussed: Bool @@ -346,8 +326,6 @@ struct ContactsListSearchBar: View { @Binding var searchShowingSimplexLink: Bool @Binding var searchChatFilteredBySimplexLink: String? @State private var ignoreSearchTextChange = false - @State private var alert: PlanAndConnectAlert? - @State private var sheet: PlanAndConnectActionSheet? @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false var body: some View { @@ -364,6 +342,9 @@ struct ContactsListSearchBar: View { .disabled(searchShowingSimplexLink) .focused($searchFocussed) .frame(maxWidth: .infinity) + if connectProgressManager.showConnectProgress != nil { + ProgressView() + } if !searchText.isEmpty { Image(systemName: "xmark.circle.fill") .resizable() @@ -400,7 +381,7 @@ struct ContactsListSearchBar: View { } else { if let link = strHasSingleSimplexLink(t.trimmingCharacters(in: .whitespaces)) { // if SimpleX link is pasted, show connection dialogue searchFocussed = false - if case let .simplexLink(linkType, _, smpHosts) = link.format { + if case let .simplexLink(_, linkType, _, smpHosts) = link.format { ignoreSearchTextChange = true searchText = simplexLinkText(linkType, smpHosts) } @@ -410,18 +391,14 @@ struct ContactsListSearchBar: View { } else { if t != "" { // if some other text is pasted, enter search mode searchFocussed = true + } else { + connectProgressManager.cancelConnectProgress() } searchShowingSimplexLink = false searchChatFilteredBySimplexLink = nil } } } - .alert(item: $alert) { a in - planAndConnectAlert(a, dismiss: true, cleanup: { searchText = "" }) - } - .actionSheet(item: $sheet) { s in - planAndConnectActionSheet(s, dismiss: true, cleanup: { searchText = "" }) - } } private func toggleFilterButton() -> some View { @@ -442,10 +419,12 @@ struct ContactsListSearchBar: View { private func connect(_ link: String) { planAndConnect( link, - showAlert: { alert = $0 }, - showActionSheet: { sheet = $0 }, + theme: theme, dismiss: true, - incognito: nil, + cleanup: { + searchText = "" + searchFocussed = false + }, filterKnownContact: { searchChatFilteredBySimplexLink = $0.id } ) } @@ -453,7 +432,6 @@ struct ContactsListSearchBar: View { struct DeletedChats: View { - @State private var baseContactTypes: [ContactType] = [.chatDeleted] @State private var searchMode = false @FocusState var searchFocussed: Bool @State private var searchText = "" @@ -475,7 +453,7 @@ struct DeletedChats: View { .frame(maxWidth: .infinity) ContactsList( - baseContactTypes: $baseContactTypes, + chatPredicate: { chat, _ in chat.chatInfo.contact?.chatDeleted == true }, searchMode: $searchMode, searchText: $searchText, searchFocussed: $searchFocussed, diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 110eda7882..3de1fdb972 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -29,11 +29,9 @@ struct SomeSheet: Identifiable { } private enum NewChatViewAlert: Identifiable { - case planAndConnectAlert(alert: PlanAndConnectAlert) case newChatSomeAlert(alert: SomeAlert) var id: String { switch self { - case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)" case let .newChatSomeAlert(alert): return "newChatSomeAlert \(alert.id)" } } @@ -165,8 +163,6 @@ struct NewChatView: View { } .alert(item: $alert) { a in switch(a) { - case let .planAndConnectAlert(alert): - return planAndConnectAlert(alert, dismiss: true, cleanup: { pastedLink = "" }) case let .newChatSomeAlert(a): return a.alert } @@ -371,7 +367,6 @@ private struct ActiveProfilePicker: View { .onAppear { profiles = chatModel.users .map { $0.user } - .sorted { u, _ in u.activeUser } } .onChange(of: incognitoEnabled) { incognito in if profileSwitchStatus != .switchingIncognito { @@ -420,8 +415,8 @@ private struct ActiveProfilePicker: View { } Task { do { - if let contactConn = contactConnection { - let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) + if let contactConn = contactConnection, + let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) { await MainActor.run { contactConnection = conn connLinkInvitation = conn.connLinkInv ?? CreatedConnLink(connFullLink: "", connShortLink: nil) @@ -429,7 +424,7 @@ private struct ActiveProfilePicker: View { chatModel.updateContactConnection(conn) } do { - try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil ) + try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil) await MainActor.run { profileSwitchStatus = .idle dismiss() @@ -440,7 +435,7 @@ private struct ActiveProfilePicker: View { alert = SomeAlert( alert: Alert( title: Text("Error switching profile"), - message: Text("Your connection was moved to \(profile.chatViewName) but an unexpected error occurred while redirecting you to the profile.") + message: Text("Your connection was moved to \(profile.chatViewName) but an error happened when switching profile.") ), id: "switchingProfileError" ) @@ -479,8 +474,8 @@ private struct ActiveProfilePicker: View { IncognitoHelp() } } - - + + @ViewBuilder private func viewBody() -> some View { profilePicker() .allowsHitTesting(!switchingProfileByTimeout) @@ -493,11 +488,11 @@ private struct ActiveProfilePicker: View { } } } - + private func filteredProfiles() -> [User] { let s = trimmedSearchTextOrPassword let lower = s.localizedLowercase - + return profiles.filter { u in if (u.activeUser || !u.hidden) && (s == "" || u.chatViewName.localizedLowercase.contains(lower)) { return true @@ -505,7 +500,7 @@ private struct ActiveProfilePicker: View { return correctPassword(u, s) } } - + private func profilerPickerUserOption(_ user: User) -> some View { Button { if selectedProfile == user && incognitoEnabled { @@ -531,7 +526,7 @@ private struct ActiveProfilePicker: View { } } } - + @ViewBuilder private func profilePicker() -> some View { let incognitoOption = Button { if !incognitoEnabled { @@ -557,14 +552,16 @@ private struct ActiveProfilePicker: View { } } } - + List { let filteredProfiles = filteredProfiles() let activeProfile = filteredProfiles.first { u in u.activeUser } - + if let selectedProfile = activeProfile { - let otherProfiles = filteredProfiles.filter { u in u.userId != activeProfile?.userId } - + let otherProfiles = filteredProfiles + .filter { u in u.userId != activeProfile?.userId } + .sorted(using: KeyPathComparator(\.activeOrder, order: .reverse)) + if incognitoFirst { incognitoOption profilerPickerUserOption(selectedProfile) @@ -572,7 +569,7 @@ private struct ActiveProfilePicker: View { profilerPickerUserOption(selectedProfile) incognitoOption } - + ForEach(otherProfiles) { p in profilerPickerUserOption(p) } @@ -588,12 +585,13 @@ private struct ActiveProfilePicker: View { } private struct ConnectView: View { + @StateObject private var connectProgressManager = ConnectProgressManager.shared @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @Binding var showQRCodeScanner: Bool @Binding var pastedLink: String @Binding var alert: NewChatViewAlert? - @State private var sheet: PlanAndConnectActionSheet? + @State var scannerPaused: Bool = false @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings var body: some View { @@ -602,39 +600,49 @@ private struct ConnectView: View { pasteLinkView() } Section(header: Text("Or scan QR code").foregroundColor(theme.colors.secondary)) { - ScannerInView(showQRCodeScanner: $showQRCodeScanner, processQRCode: processQRCode) + ScannerInView(showQRCodeScanner: $showQRCodeScanner, scannerPaused: $scannerPaused, processQRCode: processQRCode) } } - .actionSheet(item: $sheet) { s in - planAndConnectActionSheet(s, dismiss: true, cleanup: { pastedLink = "" }) + .onDisappear { + connectProgressManager.cancelConnectProgress() } } @ViewBuilder private func pasteLinkView() -> some View { if pastedLink == "" { - Button { - if let str = UIPasteboard.general.string { - if let link = strHasSingleSimplexLink(str.trimmingCharacters(in: .whitespaces)) { - pastedLink = link.text - // It would be good to hide it, but right now it is not clear how to release camera in CodeScanner - // https://github.com/twostraws/CodeScanner/issues/121 - // No known tricks worked (changing view ID, wrapping it in another view, etc.) - // showQRCodeScanner = false - connect(pastedLink) - } else { - alert = .newChatSomeAlert(alert: SomeAlert( - alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."), - id: "pasteLinkView: code is not a SimpleX link" - )) + ZStack(alignment: .trailing) { + Button { + if let str = UIPasteboard.general.string { + if let link = strHasSingleSimplexLink(str.trimmingCharacters(in: .whitespaces)) { + pastedLink = link.text + // It would be good to hide it, but right now it is not clear how to release camera in CodeScanner + // https://github.com/twostraws/CodeScanner/issues/121 + // No known tricks worked (changing view ID, wrapping it in another view, etc.) + // showQRCodeScanner = false + connect(pastedLink) + } else { + alert = .newChatSomeAlert(alert: SomeAlert( + alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."), + id: "pasteLinkView: code is not a SimpleX link" + )) + } } + } label: { + Text("Tap to paste link") + } + .disabled(!pasteboardHasStrings) + .frame(maxWidth: .infinity, alignment: .center) + if connectProgressManager.showConnectProgress != nil { + ProgressView() } - } label: { - Text("Tap to paste link") } - .disabled(!pasteboardHasStrings) - .frame(maxWidth: .infinity, alignment: .center) } else { - linkTextView(pastedLink) + HStack { + linkTextView(pastedLink) + if connectProgressManager.showConnectProgress != nil { + ProgressView() + } + } } } @@ -660,18 +668,22 @@ private struct ConnectView: View { } private func connect(_ link: String) { + scannerPaused = true planAndConnect( link, - showAlert: { alert = .planAndConnectAlert(alert: $0) }, - showActionSheet: { sheet = $0 }, + theme: theme, dismiss: true, - incognito: nil + cleanup: { + pastedLink = "" + scannerPaused = false + } ) } } struct ScannerInView: View { @Binding var showQRCodeScanner: Bool + var scannerPaused: Binding? = nil let processQRCode: (_ resp: Result) -> Void @State private var cameraAuthorizationStatus: AVAuthorizationStatus? var scanMode: ScanMode = .continuous @@ -679,7 +691,7 @@ struct ScannerInView: View { var body: some View { Group { if showQRCodeScanner, case .authorized = cameraAuthorizationStatus { - CodeScannerView(codeTypes: [.qr], scanMode: scanMode, completion: processQRCode) + CodeScannerView(codeTypes: [.qr], scanMode: scanMode, isPaused: scannerPaused?.wrappedValue ?? false, completion: processQRCode) .aspectRatio(1, contentMode: .fit) .cornerRadius(12) .listRowBackground(Color.clear) @@ -839,348 +851,570 @@ func sharedProfileInfo(_ incognito: Bool) -> Text { ) } -enum PlanAndConnectAlert: Identifiable { - case ownInvitationLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) - case invitationLinkConnecting(connectionLink: CreatedConnLink) - case ownContactAddressConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) - case contactAddressConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnecting(connectionLink: CreatedConnLink, groupInfo: GroupInfo?) - case error(shortOrFullLink: String, alert: Alert) - - var id: String { - switch self { - case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink.connFullLink)" - case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink.connFullLink)" - case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink.connFullLink)" - case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink.connFullLink)" - case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink.connFullLink)" - case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink.connFullLink)" - case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink.connFullLink)" - case let .error(shortOrFullLink, alert): return "error \(shortOrFullLink)" - } - } +private func showInvitationLinkConnectingAlert(cleanup: (() -> Void)?) { + showAlert( + NSLocalizedString("Already connecting!", comment: "new chat sheet title"), + message: NSLocalizedString("You are already connecting via this one-time link!", comment: "new chat sheet message"), + actions: {[ + okCleanupAlertAction(cleanup: cleanup) + ]} + ) } -func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: (() -> Void)? = nil) -> Alert { - switch alert { - case let .ownInvitationLinkConfirmConnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Connect to yourself?"), - message: Text("This is your own one-time link!"), - primaryButton: .destructive( - Text(incognito ? "Connect incognito" : "Connect"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } - ), - secondaryButton: .cancel() { cleanup?() } - ) - case .invitationLinkConnecting: - return Alert( - title: Text("Already connecting!"), - message: Text("You are already connecting via this one-time link!"), - dismissButton: .default(Text("OK")) { cleanup?() } - ) - case let .ownContactAddressConfirmConnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Connect to yourself?"), - message: Text("This is your own SimpleX address!"), - primaryButton: .destructive( - Text(incognito ? "Connect incognito" : "Connect"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } - ), - secondaryButton: .cancel() { cleanup?() } - ) - case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Repeat connection request?"), - message: Text("You have already requested connection via this address!"), - primaryButton: .destructive( - Text(incognito ? "Connect incognito" : "Connect"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } - ), - secondaryButton: .cancel() { cleanup?() } - ) - case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Join group?"), - message: Text("You will connect to all group members."), - primaryButton: .default( - Text(incognito ? "Join incognito" : "Join"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } - ), - secondaryButton: .cancel() { cleanup?() } - ) - case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): - return Alert( - title: Text("Repeat join request?"), - message: Text("You are already joining the group via this link!"), - primaryButton: .destructive( - Text(incognito ? "Join incognito" : "Join"), - action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } - ), - secondaryButton: .cancel() { cleanup?() } - ) - case let .groupLinkConnecting(_, groupInfo): - if let groupInfo = groupInfo { - return groupInfo.businessChat == nil - ? Alert( - title: Text("Group already exists!"), - message: Text("You are already joining the group \(groupInfo.displayName)."), - dismissButton: .default(Text("OK")) { cleanup?() } - ) - : Alert( - title: Text("Chat already exists!"), - message: Text("You are already connecting to \(groupInfo.displayName)."), - dismissButton: .default(Text("OK")) { cleanup?() } +private func showGroupLinkConnectingAlert(groupInfo: GroupInfo?, cleanup: (() -> Void)?) { + if let groupInfo = groupInfo { + if groupInfo.businessChat == nil { + showAlert( + NSLocalizedString("Group already exists!", comment: "new chat sheet title"), + message: + String.localizedStringWithFormat( + NSLocalizedString("You are already joining the group %@.", comment: "new chat sheet message"), + groupInfo.displayName + ), + actions: {[ + okCleanupAlertAction(cleanup: cleanup) + ]} ) } else { - return Alert( - title: Text("Already joining the group!"), - message: Text("You are already joining the group via this link."), - dismissButton: .default(Text("OK")) { cleanup?() } + showAlert( + NSLocalizedString("Chat already exists!", comment: "new chat sheet title"), + message: + String.localizedStringWithFormat( + NSLocalizedString("You are already connecting to %@.", comment: "new chat sheet message"), + groupInfo.displayName + ), + actions: {[ + okCleanupAlertAction(cleanup: cleanup) + ]} ) } - case let .error(_, alert): return alert + } else { + showAlert( + NSLocalizedString("Already joining the group!", comment: "new chat sheet title"), + message: NSLocalizedString("You are already joining the group via this link.", comment: "new chat sheet message"), + actions: {[ + okCleanupAlertAction(cleanup: cleanup) + ]} + ) } } -enum PlanAndConnectActionSheet: Identifiable { - case askCurrentOrIncognitoProfile(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) - case askCurrentOrIncognitoProfileDestructive(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, title: LocalizedStringKey) - case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact) - case ownGroupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) - - var id: String { - switch self { - case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink.connFullLink)" - case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink.connFullLink)" - case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)" - case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink.connFullLink)" +private func okCleanupAlertAction(cleanup: (() -> Void)?) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Ok", comment: "new chat action"), + style: .default, + handler: { _ in + cleanup?() } - } + ) } -func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool, cleanup: (() -> Void)? = nil) -> ActionSheet { - switch sheet { - case let .askCurrentOrIncognitoProfile(connectionLink, connectionPlan, title): - return ActionSheet( - title: Text(title), - buttons: [ - .default(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) }, - .default(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) }, - .cancel() { cleanup?() } - ] - ) - case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title): - return ActionSheet( - title: Text(title), - buttons: [ - .destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) }, - .destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) }, - .cancel() { cleanup?() } - ] - ) - case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): - return ActionSheet( - title: Text("Connect with \(contact.chatViewName)"), - buttons: [ - .default(Text("Use current profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: false, cleanup: cleanup) }, - .default(Text("Use new incognito profile")) { connectContactViaAddress_(contact, dismiss: dismiss, incognito: true, cleanup: cleanup) }, - .cancel() { cleanup?() } - ] - ) - case let .ownGroupLinkConfirmConnect(connectionLink, connectionPlan, incognito, groupInfo): - if let incognito = incognito { - return ActionSheet( - title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"), - buttons: [ - .default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) }, - .destructive(Text(incognito ? "Join incognito" : "Join with current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) }, - .cancel() { cleanup?() } - ] +private func showAskCurrentOrIncognitoProfileSheet( + title: String, + actionStyle: UIAlertAction.Style = .default, + connectionLink: CreatedConnLink, + connectionPlan: ConnectionPlan?, + dismiss: Bool, + cleanup: (() -> Void)? +) { + showSheet( + title, + actions: {[ + UIAlertAction( + title: NSLocalizedString("Use current profile", comment: "new chat action"), + style: actionStyle, + handler: { _ in + connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Use new incognito profile", comment: "new chat action"), + style: actionStyle, + handler: { _ in + connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "new chat action"), + style: .default, + handler: { _ in + cleanup?() + } ) - } else { - return ActionSheet( - title: Text("Join your group?\nThis is your link for group \(groupInfo.displayName)!"), - buttons: [ - .default(Text("Open group")) { openKnownGroup(groupInfo, dismiss: dismiss, showAlreadyExistsAlert: nil) }, - .destructive(Text("Use current profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) }, - .destructive(Text("Use new incognito profile")) { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) }, - .cancel() { cleanup?() } - ] + ]} + ) +} + +private func showAskCurrentOrIncognitoProfileConnectContactViaAddressSheet( + contact: Contact, + dismiss: Bool, + cleanup: (() -> Void)? +) { + showSheet( + String.localizedStringWithFormat( + NSLocalizedString("Connect with %@", comment: "new chat action"), + contact.chatViewName + ), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Use current profile", comment: "new chat action"), + style: .default, + handler: { _ in + connectContactViaAddress_(contact, dismiss: dismiss, incognito: false, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Use new incognito profile", comment: "new chat action"), + style: .default, + handler: { _ in + connectContactViaAddress_(contact, dismiss: dismiss, incognito: true, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "new chat action"), + style: .default, + handler: { _ in + cleanup?() + } ) + ]} + ) +} + +private func showOwnGroupLinkConfirmConnectSheet( + groupInfo: GroupInfo, + connectionLink: CreatedConnLink, + connectionPlan: ConnectionPlan?, + dismiss: Bool, + cleanup: (() -> Void)? +) { + showSheet( + String.localizedStringWithFormat( + NSLocalizedString("Join your group?\nThis is your link for group %@!", comment: "new chat action"), + groupInfo.displayName + ), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Open group", comment: "new chat action"), + style: .default, + handler: { _ in + openKnownGroup(groupInfo, dismiss: dismiss, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Use current profile", comment: "new chat action"), + style: .destructive, + handler: { _ in + connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: false, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Use new incognito profile", comment: "new chat action"), + style: .destructive, + handler: { _ in + connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: true, cleanup: cleanup) + } + ), + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "new chat action"), + style: .default, + handler: { _ in + cleanup?() + } + ) + ]} + ) +} + +private func showPrepareContactAlert( + connectionLink: CreatedConnLink, + contactShortLinkData: ContactShortLinkData, + theme: AppTheme, + dismiss: Bool, + cleanup: (() -> Void)? +) { + showOpenChatAlert( + profileName: contactShortLinkData.profile.displayName, + profileFullName: contactShortLinkData.profile.fullName, + profileImage: + ProfileImage( + imageStr: contactShortLinkData.profile.image, + iconName: contactShortLinkData.business + ? "briefcase.circle.fill" + : contactShortLinkData.profile.peerType == .bot + ? "cube.fill" + : "person.crop.circle.fill", + size: alertProfileImageSize + ), + theme: theme, + cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"), + confirmTitle: NSLocalizedString("Open new chat", comment: "new chat action"), + onCancel: { cleanup?() }, + onConfirm: { + Task { + do { + let chat = try await apiPrepareContact(connLink: connectionLink, contactShortLinkData: contactShortLinkData) + await MainActor.run { + ChatModel.shared.addChat(Chat(chat)) + openKnownChat(chat.id, dismiss: dismiss, cleanup: cleanup) + } + } catch let error { + logger.error("showPrepareContactAlert apiPrepareContact error: \(error.localizedDescription)") + showAlert(NSLocalizedString("Error opening chat", comment: ""), message: responseError(error)) + await MainActor.run { + cleanup?() + } + } + } } - } + ) +} + +private func showPrepareGroupAlert( + connectionLink: CreatedConnLink, + groupShortLinkData: GroupShortLinkData, + theme: AppTheme, + dismiss: Bool, + cleanup: (() -> Void)? +) { + showOpenChatAlert( + profileName: groupShortLinkData.groupProfile.displayName, + profileFullName: groupShortLinkData.groupProfile.fullName, + profileImage: ProfileImage(imageStr: groupShortLinkData.groupProfile.image, iconName: "person.2.circle.fill", size: alertProfileImageSize), + theme: theme, + cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"), + confirmTitle: NSLocalizedString("Open new group", comment: "new chat action"), + onCancel: { cleanup?() }, + onConfirm: { + Task { + do { + let chat = try await apiPrepareGroup(connLink: connectionLink, groupShortLinkData: groupShortLinkData) + await MainActor.run { + ChatModel.shared.addChat(Chat(chat)) + openKnownChat(chat.id, dismiss: dismiss, cleanup: cleanup) + } + } catch let error { + logger.error("showPrepareGroupAlert apiPrepareGroup error: \(error.localizedDescription)") + showAlert(NSLocalizedString("Error opening group", comment: ""), message: responseError(error)) + await MainActor.run { + cleanup?() + } + } + } + } + ) +} + +private func showOpenKnownContactAlert( + _ contact: Contact, + theme: AppTheme, + dismiss: Bool +) { + showOpenChatAlert( + profileName: contact.profile.displayName, + profileFullName: contact.profile.fullName, + profileImage: + ProfileImage( + imageStr: contact.profile.image, + iconName: contact.chatIconName, + size: alertProfileImageSize + ), + theme: theme, + cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"), + confirmTitle: + contact.nextConnectPrepared + ? NSLocalizedString("Open new chat", comment: "new chat action") + : NSLocalizedString("Open chat", comment: "new chat action"), + onConfirm: { + openKnownContact(contact, dismiss: dismiss, cleanup: nil) + } + ) +} + +private func showOpenKnownGroupAlert( + _ groupInfo: GroupInfo, + theme: AppTheme, + dismiss: Bool +) { + showOpenChatAlert( + profileName: groupInfo.groupProfile.displayName, + profileFullName: groupInfo.groupProfile.fullName, + profileImage: + ProfileImage( + imageStr: groupInfo.groupProfile.image, + iconName: groupInfo.chatIconName, + size: alertProfileImageSize + ), + theme: theme, + cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"), + confirmTitle: + groupInfo.businessChat == nil + ? ( groupInfo.nextConnectPrepared + ? NSLocalizedString("Open new group", comment: "new chat action") + : NSLocalizedString("Open group", comment: "new chat action") + ) + : ( groupInfo.nextConnectPrepared + ? NSLocalizedString("Open new chat", comment: "new chat action") + : NSLocalizedString("Open chat", comment: "new chat action") + ), + onConfirm: { + openKnownGroup(groupInfo, dismiss: dismiss, cleanup: nil) + } + ) } func planAndConnect( _ shortOrFullLink: String, - showAlert: @escaping (PlanAndConnectAlert) -> Void, - showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void, + theme: AppTheme, dismiss: Bool, - incognito: Bool?, cleanup: (() -> Void)? = nil, filterKnownContact: ((Contact) -> Void)? = nil, filterKnownGroup: ((GroupInfo) -> Void)? = nil ) { - Task { - let (result, alert) = await apiConnectPlan(connLink: shortOrFullLink) - if let (connectionLink, connectionPlan) = result { - switch connectionPlan { - case let .invitationLink(ilp): - switch ilp { - case .ok: - logger.debug("planAndConnect, .invitationLink, .ok, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) - } else { - await MainActor.run { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) - } - } - case .ownLink: - logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let incognito = incognito { - showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + ConnectProgressManager.shared.cancelConnectProgress() + let inProgress = BoxedValue(true) + connectTask(inProgress) + ConnectProgressManager.shared.startConnectProgress(NSLocalizedString("Loading profile…", comment: "in progress text")) { + inProgress.boxedValue = false + cleanup?() + } + + func connectTask(_ inProgress: BoxedValue) { + Task { + let (result, alert) = await apiConnectPlan(connLink: shortOrFullLink, inProgress: inProgress) + await MainActor.run { + ConnectProgressManager.shared.stopConnectProgress() + } + if !inProgress.boxedValue { return } + if let (connectionLink, connectionPlan) = result { + switch connectionPlan { + case let .invitationLink(ilp): + switch ilp { + case let .ok(contactSLinkData_): + if let contactSLinkData = contactSLinkData_ { + logger.debug("planAndConnect, .invitationLink, .ok, short link data present") + await MainActor.run { + showPrepareContactAlert( + connectionLink: connectionLink, + contactShortLinkData: contactSLinkData, + theme: theme, + dismiss: dismiss, + cleanup: cleanup + ) + } } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + logger.debug("planAndConnect, .invitationLink, .ok, no short link data") + await MainActor.run { + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("Connect via one-time link", comment: "new chat sheet title"), + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) + } } - } - case let .connecting(contact_): - logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let contact = contact_ { + case .ownLink: + logger.debug("planAndConnect, .invitationLink, .ownLink") + await MainActor.run { + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("Connect to yourself?\nThis is your own one-time link!", comment: "new chat sheet title"), + actionStyle: .destructive, + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) + } + case let .connecting(contact_): + logger.debug("planAndConnect, .invitationLink, .connecting") + await MainActor.run { + if let contact = contact_ { + if let f = filterKnownContact { + f(contact) + } else { + showOpenKnownContactAlert(contact, theme: theme, dismiss: dismiss) + } + } else { + showInvitationLinkConnectingAlert(cleanup: cleanup) + } + } + case let .known(contact): + logger.debug("planAndConnect, .invitationLink, .known") + await MainActor.run { if let f = filterKnownContact { f(contact) } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + showOpenKnownContactAlert(contact, theme: theme, dismiss: dismiss) + } + } + } + case let .contactAddress(cap): + switch cap { + case let .ok(contactSLinkData_): + if let contactSLinkData = contactSLinkData_ { + logger.debug("planAndConnect, .contactAddress, .ok, short link data present") + await MainActor.run { + showPrepareContactAlert( + connectionLink: connectionLink, + contactShortLinkData: contactSLinkData, + theme: theme, + dismiss: dismiss, + cleanup: cleanup + ) } } else { - showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) + logger.debug("planAndConnect, .contactAddress, .ok, no short link data") + await MainActor.run { + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("Connect via contact address", comment: "new chat sheet title"), + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) + } } - } - case let .known(contact): - logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } - } - } - } - case let .contactAddress(cap): - switch cap { - case .ok: - logger.debug("planAndConnect, .contactAddress, .ok, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) - } else { + case .ownLink: + logger.debug("planAndConnect, .contactAddress, .ownLink") await MainActor.run { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("Connect to yourself?\nThis is your own SimpleX address!", comment: "new chat sheet title"), + actionStyle: .destructive, + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) } - } - case .ownLink: - logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let incognito = incognito { - showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) - } - } - case .connectingConfirmReconnect: - logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let incognito = incognito { - showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) - } - } - case let .connectingProhibit(contact): - logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } - } - } - case let .known(contact): - logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } - } - } - case let .contactViaAddress(contact): - logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito, cleanup: cleanup) - } else { + case .connectingConfirmReconnect: + logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect") await MainActor.run { - showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("You have already requested connection!\nRepeat connection request?", comment: "new chat sheet title"), + actionStyle: .destructive, + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) } + case let .connectingProhibit(contact): + logger.debug("planAndConnect, .contactAddress, .connectingProhibit") + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + showOpenKnownContactAlert(contact, theme: theme, dismiss: dismiss) + } + } + case let .known(contact): + logger.debug("planAndConnect, .contactAddress, .known") + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + showOpenKnownContactAlert(contact, theme: theme, dismiss: dismiss) + } + } + case let .contactViaAddress(contact): + logger.debug("planAndConnect, .contactAddress, .contactViaAddress") + await MainActor.run { + showAskCurrentOrIncognitoProfileConnectContactViaAddressSheet( + contact: contact, + dismiss: dismiss, + cleanup: cleanup + ) + } + } + case let .groupLink(glp): + switch glp { + case let .ok(groupSLinkData_): + if let groupSLinkData = groupSLinkData_ { + logger.debug("planAndConnect, .groupLink, .ok, short link data present") + await MainActor.run { + showPrepareGroupAlert( + connectionLink: connectionLink, + groupShortLinkData: groupSLinkData, + theme: theme, + dismiss: dismiss, + cleanup: cleanup + ) + } + } else { + logger.debug("planAndConnect, .groupLink, .ok, no short link data") + await MainActor.run { + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("Join group", comment: "new chat sheet title"), + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) + } + } + case let .ownLink(groupInfo): + logger.debug("planAndConnect, .groupLink, .ownLink") + await MainActor.run { + if let f = filterKnownGroup { + f(groupInfo) + } + showOwnGroupLinkConfirmConnectSheet( + groupInfo: groupInfo, + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) + } + case .connectingConfirmReconnect: + logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect") + await MainActor.run { + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("You are already joining the group!\nRepeat join request?", comment: "new chat sheet title"), + actionStyle: .destructive, + connectionLink: connectionLink, + connectionPlan: connectionPlan, + dismiss: dismiss, + cleanup: cleanup + ) + } + case let .connectingProhibit(groupInfo_): + logger.debug("planAndConnect, .groupLink, .connectingProhibit") + await MainActor.run { + showGroupLinkConnectingAlert(groupInfo: groupInfo_, cleanup: cleanup) + } + case let .known(groupInfo): + logger.debug("planAndConnect, .groupLink, .known") + await MainActor.run { + if let f = filterKnownGroup { + f(groupInfo) + } else { + showOpenKnownGroupAlert(groupInfo, theme: theme, dismiss: dismiss) + } + } + } + case let .error(chatError): + logger.debug("planAndConnect, .error \(chatErrorString(chatError))") + showAskCurrentOrIncognitoProfileSheet( + title: NSLocalizedString("Connect via link", comment: "new chat sheet title"), + connectionLink: connectionLink, + connectionPlan: nil, + dismiss: dismiss, + cleanup: cleanup + ) + } + } else { + await MainActor.run { + if let alert { + dismissAllSheets(animated: true) { + AlertManager.shared.showAlert(alert) + cleanup?() + } + } else { + cleanup?() } } - case let .groupLink(glp): - switch glp { - case .ok: - await MainActor.run { - if let incognito = incognito { - showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) - } - } - case let .ownLink(groupInfo): - logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let f = filterKnownGroup { - f(groupInfo) - } - showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) - } - case .connectingConfirmReconnect: - logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let incognito = incognito { - showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) - } - } - case let .connectingProhibit(groupInfo_): - logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) - } - case let .known(groupInfo): - logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")") - await MainActor.run { - if let f = filterKnownGroup { - f(groupInfo) - } else { - openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } - } - } - } - case let .error(chatError): - logger.debug("planAndConnect, .error \(chatErrorString(chatError))") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) - } - } - } else if let alert { - await MainActor.run { - showAlert(.error(shortOrFullLink: shortOrFullLink, alert: alert)) } } } @@ -1239,37 +1473,29 @@ private func connectViaLink( } } -func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - let m = ChatModel.shared - if let c = m.getContactChat(contact.contactId) { - if dismiss { - dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(c.id) { - showAlreadyExistsAlert?() - } - } - } else { - ItemsModel.shared.loadOpenChat(c.id) { - showAlreadyExistsAlert?() - } - } +func openKnownContact(_ contact: Contact, dismiss: Bool, cleanup: (() -> Void)?) { + if let c = ChatModel.shared.getContactChat(contact.contactId) { + openKnownChat(c.id, dismiss: dismiss, cleanup: cleanup) } } -func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - let m = ChatModel.shared - if let g = m.getGroupChat(groupInfo.groupId) { - if dismiss { - dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(g.id) { - showAlreadyExistsAlert?() - } - } - } else { - ItemsModel.shared.loadOpenChat(g.id) { - showAlreadyExistsAlert?() +func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, cleanup: (() -> Void)?) { + if let g = ChatModel.shared.getGroupChat(groupInfo.groupId) { + openKnownChat(g.id, dismiss: dismiss, cleanup: cleanup) + } +} + +func openKnownChat(_ chatId: ChatId, dismiss: Bool, cleanup: (() -> Void)?) { + if dismiss { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(chatId) { + cleanup?() } } + } else { + ItemsModel.shared.loadOpenChat(chatId) { + cleanup?() + } } } diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 453149198b..c9054f30da 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -12,11 +12,12 @@ import SimpleXChat struct MutableQRCode: View { @Binding var uri: String + var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var body: some View { - QRCode(uri: uri, withLogo: withLogo, tintColor: tintColor) + QRCode(uri: uri, small: small, withLogo: withLogo, tintColor: tintColor) .id("simplex-qrcode-view-for-\(uri)") } } @@ -27,7 +28,7 @@ struct SimpleXCreatedLinkQRCode: View { var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: link.simplexChatUri(short: short), onShare: onShare) + QRCode(uri: link.simplexChatUri(short: short), small: short && link.connShortLink != nil, onShare: onShare) } } @@ -38,50 +39,57 @@ struct SimpleXLinkQRCode: View { var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor, onShare: onShare) + QRCode(uri: simplexChatLink(uri), small: uri.count < 200, withLogo: withLogo, tintColor: tintColor, onShare: onShare) } } +private let smallQRRatio: CGFloat = 0.63 + struct QRCode: View { let uri: String + var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var onShare: (() -> Void)? = nil @State private var image: UIImage? = nil @State private var makeScreenshotFunc: () -> Void = {} + @State private var width: CGFloat = .infinity var body: some View { ZStack { if let image = image { - qrCodeImage(image) - GeometryReader { geo in + qrCodeImage(image).frame(width: width, height: width) + GeometryReader { g in + let w = g.size.width * (small ? smallQRRatio : 1) + let l = w * (small ? 0.195 : 0.16) + let m = w * 0.005 ZStack { if withLogo { - let w = geo.size.width Image("icon-light") .resizable() .scaledToFit() - .frame(width: w * 0.16, height: w * 0.16) - .frame(width: w * 0.165, height: w * 0.165) + .frame(width: l, height: l) + .frame(width: l + m, height: l + m) .background(.white) .clipShape(Circle()) } } .onAppear { + width = w makeScreenshotFunc = { let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale) - showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)]) + showShareSheet(items: [makeScreenshot(g.frame(in: .local).origin, size)]) onShare?() } } - .frame(width: geo.size.width, height: geo.size.height) + .frame(width: g.size.width, height: g.size.height) } } else { - Color.clear.aspectRatio(1, contentMode: .fit) + Color.clear.aspectRatio(small ? 1 / smallQRRatio : 1, contentMode: .fit) } } .onTapGesture(perform: makeScreenshotFunc) - .task { image = await generateImage(uri, tintColor: tintColor) } + .task { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") } .frame(maxWidth: .infinity, maxHeight: .infinity) } } @@ -94,10 +102,11 @@ private func qrCodeImage(_ image: UIImage) -> some View { .textSelection(.enabled) } -private func generateImage(_ uri: String, tintColor: UIColor) async -> UIImage? { +private func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(uri.utf8) + filter.correctionLevel = errorLevel if let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent) { return UIImage(cgImage: cgImage).replaceColor(UIColor.black, tintColor) diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index ae72cb1be5..f119beec50 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -25,10 +25,13 @@ enum UserProfileAlert: Identifiable { } } +let MAX_BIO_LENGTH_BYTES = 160 + struct CreateProfile: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var theme: AppTheme @State private var displayName: String = "" + @State private var profileBio: String = "" @FocusState private var focusDisplayName @State private var alert: UserProfileAlert? @@ -37,12 +40,13 @@ struct CreateProfile: View { Section { TextField("Enter your name…", text: $displayName) .focused($focusDisplayName) + TextField("Bio", text: $profileBio) Button { createProfile() } label: { Label("Create profile", systemImage: "checkmark") } - .disabled(!canCreateProfile(displayName)) + .disabled(!canCreateProfile(displayName) || !bioFitsLimit()) } header: { HStack { Text("Your profile") @@ -52,11 +56,14 @@ struct CreateProfile: View { let validName = mkValidName(name) if name != validName { Spacer() - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .onTapGesture { - alert = .invalidNameError(validName: validName) - } + validationErrorIndicator { + alert = .invalidNameError(validName: validName) + } + } else if !bioFitsLimit() { + Spacer() + validationErrorIndicator { + showAlert(NSLocalizedString("Bio too large", comment: "alert title")) + } } } .frame(height: 20) @@ -78,11 +85,25 @@ struct CreateProfile: View { } } + private func validationErrorIndicator(_ onTap: @escaping () -> Void) -> some View { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .onTapGesture { + onTap() + } + } + + private func bioFitsLimit() -> Bool { + chatJsonLength(profileBio) <= MAX_BIO_LENGTH_BYTES + } + private func createProfile() { hideKeyboard() + let shortDescr: String? = if profileBio.isEmpty { nil } else { profileBio } let profile = Profile( displayName: displayName.trimmingCharacters(in: .whitespaces), - fullName: "" + fullName: "", + shortDescr: shortDescr ) let m = ChatModel.shared do { diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift index a2f5db7f03..03b0fcba1a 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift @@ -77,9 +77,10 @@ struct CreateSimpleXAddress: View { progressIndicator = true Task { do { - let connLinkContact = try await apiCreateUserAddress(short: false) - DispatchQueue.main.async { - m.userAddress = UserContactLink(connLinkContact: connLinkContact) + if let connLinkContact = try await apiCreateUserAddress() { + DispatchQueue.main.async { + m.userAddress = UserContactLink(connLinkContact) + } } await MainActor.run { progressIndicator = false } } catch let error { diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index f65a21623a..916e3f9e78 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -579,6 +579,58 @@ private let versionDescriptions: [VersionDescription] = [ )), ] ), + VersionDescription( + version: "v6.4", + post: URL(string: "https://simplex.chat/blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.html"), + features: [ + .feature(Description( + icon: "person", + title: "Connect faster! 🚀", + description: "Message instantly once you tap Connect." + )), + .feature(Description( + icon: { if #available(iOS 17, *) {"person.bubble"} else {"person.crop.square"} }(), + title: "Review group members", + description: "Chat with members before they join." + )), + .feature(Description( + icon: { if #available(iOS 16, *) {"questionmark.bubble"} else {"questionmark.square"} }(), + title: "Chat with admins", + description: "Send your private feedback to groups." + )), + .feature(Description( + icon: "flag", + title: "New group role: Moderator", + description: "Removes messages and blocks members." + )), + .feature(Description( + icon: "battery.50", + title: "Improved message delivery", + description: "Less traffic on mobile networks." + )), + ] + ), + VersionDescription( + version: "v6.4.1", + post: URL(string: "https://simplex.chat/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.html"), + features: [ + .feature(Description( + icon: "hand.wave", + title: "Welcome your contacts 👋", + description: "Set profile bio and welcome message." + )), + .feature(Description( + icon: "stopwatch", + title: "Keep your chats clean", + description: "Enable disappearing messages by default." + )), + .view(FeatureView( + icon: nil, + title: "Short SimpleX address", + view: { CreateUpdateAddressShortLink() } + )) + ] + ), ] private let lastVersion = versionDescriptions.last!.version @@ -610,6 +662,51 @@ fileprivate struct NewOperatorsView: View { } } +fileprivate struct CreateUpdateAddressShortLink: View { + @EnvironmentObject private var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @State private var showAddressSheet = false + @State private var progressIndicator = false + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .center, spacing: 4) { + Image(systemName: "link") + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(minWidth: 30, alignment: .center) + Text("Short SimpleX address").font(.title3).bold() + } + Group { + if let addr = chatModel.userAddress { + if addr.shouldBeUpgraded { + HStack(spacing: 8) { + Button("Upgrade your address") { upgradeAndShareAddressAlert(progressIndicator: $progressIndicator) } + if progressIndicator { + ProgressView() + } + } + } else { + Button("Share your address") { addr.shareAddress(short: true) } + } + } else { + Button("Create your address") { showAddressSheet = true } + } + } + .multilineTextAlignment(.leading) + .lineLimit(10) + } + .sheet(isPresented: $showAddressSheet) { + NavigationView { + UserAddressView(autoCreate: true) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + } + } +} + private enum WhatsNewViewSheet: Identifiable { case showConditions diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index 554219eb69..ac143fe044 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -167,7 +167,7 @@ struct TerminalView: View { func sendTerminalCmd(_ cmd: String) async { let start: Date = .now await withCheckedContinuation { (cont: CheckedContinuation) in - let d = sendSimpleXCmdStr(cmd) + let d = sendSimpleXCmdStr(cmd, retryNum: 0) Task { guard let d else { await TerminalItems.shared.addCommand(start, ChatCommand.string(cmd), APIResult.error(.invalidJSON(json: nil))) diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index c6d0e27289..02dec5a618 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -367,13 +367,13 @@ struct ChatThemePreview: View { let alice = ChatItem.getSample(1, CIDirection.directRcv, Date.now, NSLocalizedString("Good afternoon!", comment: "message preview")) let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir)) HStack { - ChatItemView(chat: Chat.sampleData, chatItem: alice, scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, im: ItemsModel.shared, chatItem: alice, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) .modifier(ChatItemClipped(alice, tailVisible: true)) Spacer() } HStack { Spacer() - ChatItemView(chat: Chat.sampleData, chatItem: bob, scrollToItemId: { _ in }) + ChatItemView(chat: Chat.sampleData, im: ItemsModel.shared, chatItem: bob, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)) .modifier(ChatItemClipped(bob, tailVisible: true)) .frame(alignment: .trailing) } diff --git a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift index 54454b7cef..6df2d5422e 100644 --- a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift +++ b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift @@ -14,6 +14,7 @@ struct DeveloperView: View { @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @AppStorage(GROUP_DEFAULT_CONFIRM_DB_UPGRADES, store: groupDefaults) private var confirmDatabaseUpgrades = false @State private var hintsUnchanged = hintDefaultsUnchanged() + @State private var simplexLinkMode = privacySimplexLinkModeDefault.get() @Environment(\.colorScheme) var colorScheme @@ -65,6 +66,21 @@ struct DeveloperView: View { Text("Developer options") } } + Section("Deprecated options") { + settingsRow("link", color: theme.colors.secondary) { + Picker("SimpleX links", selection: $simplexLinkMode) { + ForEach( + SimpleXLinkMode.values + (SimpleXLinkMode.values.contains(simplexLinkMode) ? [] : [simplexLinkMode]) + ) { mode in + Text(mode.text) + } + } + } + .frame(height: 36) + .onChange(of: simplexLinkMode) { mode in + privacySimplexLinkModeDefault.set(mode) + } + } } } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift index fa698f8b7c..3a536c7b17 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift @@ -67,7 +67,7 @@ struct AdvancedNetworkSettings: View { Text(netCfg.smpProxyMode.label) } } - + NavigationLink { List { Section { @@ -192,16 +192,15 @@ struct AdvancedNetworkSettings: View { netCfg.requiredHostMode = requiredHostMode } } - + if developerTools { Section { - Picker("Transport isolation", selection: $netCfg.sessionMode) { + WrappedPicker("Transport isolation", selection: $netCfg.sessionMode) { let modes = TransportSessionMode.values.contains(netCfg.sessionMode) ? TransportSessionMode.values : TransportSessionMode.values + [netCfg.sessionMode] ForEach(modes, id: \.self) { Text($0.text) } } - .frame(height: 36) } footer: { sessionModeInfo(netCfg.sessionMode) .foregroundColor(theme.colors.secondary) @@ -209,10 +208,9 @@ struct AdvancedNetworkSettings: View { } Section { - Picker("Use web port", selection: $netCfg.smpWebPortServers) { + WrappedPicker("Use web port", selection: $netCfg.smpWebPortServers) { ForEach(SMPWebPortServers.allCases, id: \.self) { Text($0.text) } } - .frame(height: 36) } header: { Text("TCP port for messaging") } footer: { @@ -220,10 +218,12 @@ struct AdvancedNetworkSettings: View { ? Text("Use TCP port 443 for preset servers only.") : Text("Use TCP port \(netCfg.smpWebPortServers == .all ? "443" : "5223") when no port is specified.") } - + Section("TCP connection") { - timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_000000], label: secondsLabel) - timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel) + timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout.interactiveTimeout, values: [10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel) + timeoutSettingPicker("TCP connection bg timeout", selection: $netCfg.tcpConnectTimeout.backgroundTimeout, values: [30_000000, 45_000000, 60_000000, 90_000000], label: secondsLabel) + timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout.interactiveTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000], label: secondsLabel) + timeoutSettingPicker("Protocol background timeout", selection: $netCfg.tcpTimeout.backgroundTimeout, values: [15_000000, 20_000000, 30_000000, 45_000000, 60_000000], label: secondsLabel) timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [2_500, 5_000, 10_000, 15_000, 20_000, 30_000], label: secondsLabel) // intSettingPicker("Receiving concurrency", selection: $netCfg.rcvConcurrency, values: [1, 2, 4, 8, 12, 16, 24], label: "") timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel) @@ -243,7 +243,7 @@ struct AdvancedNetworkSettings: View { .foregroundColor(theme.colors.secondary) } } - + Section { Button("Reset to defaults") { updateNetCfgView(NetCfg.defaults, NetworkProxy.def) @@ -254,7 +254,7 @@ struct AdvancedNetworkSettings: View { updateNetCfgView(netCfg.withProxyTimeouts, netProxy) } .disabled(netCfg.hasProxyTimeouts) - + Button("Save and reconnect") { showSettingsAlert = .update } @@ -351,16 +351,15 @@ struct AdvancedNetworkSettings: View { } private func timeoutSettingPicker(_ title: LocalizedStringKey, selection: Binding, values: [Int], label: String) -> some View { - Picker(title, selection: selection) { + WrappedPicker(title, selection: selection) { let v = selection.wrappedValue let vs = values.contains(v) ? values : values + [v] ForEach(vs, id: \.self) { value in Text("\(String(format: "%g", (Double(value) / 1000000))) \(secondsLabel)") } } - .frame(height: 36) } - + private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey { switch hosts { case .no: return "Onion hosts will not be used." @@ -378,7 +377,7 @@ struct AdvancedNetworkSettings: View { case .entity: Text("A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.") } } - + private func proxyModeInfo(_ mode: SMPProxyMode) -> LocalizedStringKey { switch mode { case .always: return "Always use private routing." @@ -387,7 +386,7 @@ struct AdvancedNetworkSettings: View { case .never: return "Do NOT use private routing." } } - + private func proxyFallbackInfo(_ proxyFallback: SMPProxyFallback) -> LocalizedStringKey { switch proxyFallback { case .allow: return "Send messages directly when your or destination server does not support private routing." diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift index 17a0ffdd1c..c8cb2349e7 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift @@ -65,7 +65,7 @@ struct NewServerView: View { useServerSection(valid) if valid { Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { - MutableQRCode(uri: $serverToEdit.server) + MutableQRCode(uri: $serverToEdit.server, small: true) .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift index 13d01874ed..97bfd360cb 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift @@ -110,7 +110,7 @@ struct ProtocolServerView: View { useServerSection(valid) if valid { Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { - MutableQRCode(uri: $serverToEdit.server) + MutableQRCode(uri: $serverToEdit.server, small: true) .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } } diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index bd8171623a..eced372124 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -19,7 +19,7 @@ struct PreferencesView: View { var body: some View { VStack { List { - timedMessagesFeatureSection($preferences.timedMessages.allow) + timedMessagesFeatureSection($preferences.timedMessages.allow, $preferences.timedMessages.ttl) featureSection(.fullDelete, $preferences.fullDelete.allow) featureSection(.reactions, $preferences.reactions.allow) featureSection(.voice, $preferences.voice.allow) @@ -60,20 +60,35 @@ struct PreferencesView: View { } - private func timedMessagesFeatureSection(_ allowFeature: Binding) -> some View { + @ViewBuilder private func timedMessagesFeatureSection(_ allowFeature: Binding, _ ttl: Binding) -> some View { + let allow = Binding( + get: { allowFeature.wrappedValue == .always || allowFeature.wrappedValue == .yes }, + set: { yes, _ in allowFeature.wrappedValue = yes ? .yes : .no } + ) Section { - let allow = Binding( - get: { allowFeature.wrappedValue == .always || allowFeature.wrappedValue == .yes }, - set: { yes, _ in allowFeature.wrappedValue = yes ? .yes : .no } - ) settingsRow(ChatFeature.timedMessages.icon, color: theme.colors.secondary) { Toggle(ChatFeature.timedMessages.text, isOn: allow) } + if allow.wrappedValue { + Picker("Delete after", selection: ttl) { + ForEach(TimedMessagesPreference.profileLevelTTLValues, id: \.self) { value in + Text(timeText(value)).tag(value) + } + } + .frame(height: 36) + } + } + footer: { + let featureFooterText = featureFooter(.timedMessages, allowFeature).foregroundColor(theme.colors.secondary) + if allow.wrappedValue && ttl.wrappedValue != nil { + featureFooterText + textNewLine + Text("Time to disappear is set only for new contacts.") + } else { + featureFooterText + } } - footer: { featureFooter(.timedMessages, allowFeature).foregroundColor(theme.colors.secondary) } } - private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding) -> some View { + private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding) -> Text { Text(feature.allowDescription(allowFeature.wrappedValue)) } diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index eba7f8066a..eec820833c 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -14,13 +14,12 @@ struct PrivacySettings: View { @EnvironmentObject var theme: AppTheme @AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true @AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true + @AppStorage(GROUP_DEFAULT_PRIVACY_SANITIZE_LINKS, store: groupDefaults) private var privacySanitizeLinks = false @AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true @AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true - @State private var simplexLinkMode = privacySimplexLinkModeDefault.get() @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false - @AppStorage(DEFAULT_PRIVACY_SHORT_LINKS) private var shortSimplexLinks = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @State private var currentLAMode = privacyLocalAuthModeDefault.get() @@ -33,6 +32,8 @@ struct PrivacySettings: View { @State private var groupReceiptsReset = false @State private var groupReceiptsOverrides = 0 @State private var groupReceiptsDialogue = false + @State private var autoAcceptMemberContacts = false + @State private var autoAcceptMemberContactsReset = false @State private var alert: PrivacySettingsViewAlert? enum PrivacySettingsViewAlert: Identifiable { @@ -74,8 +75,12 @@ struct PrivacySettings: View { Toggle("Send link previews", isOn: $useLinkPreviews) .onChange(of: useLinkPreviews) { linkPreviews in privacyLinkPreviewsGroupDefault.set(linkPreviews) + privacyLinkPreviewsShowAlertGroupDefault.set(false) // to avoid showing alert to current users, show alert in v6.5 } } + settingsRow("link", color: theme.colors.secondary) { + Toggle("Remove link tracking", isOn: $privacySanitizeLinks) + } settingsRow("message", color: theme.colors.secondary) { Toggle("Show last messages", isOn: $showChatPreviews) } @@ -88,24 +93,6 @@ struct PrivacySettings: View { m.draftChatId = nil } } - settingsRow("link", color: theme.colors.secondary) { - Picker("SimpleX links", selection: $simplexLinkMode) { - ForEach( - SimpleXLinkMode.values + (SimpleXLinkMode.values.contains(simplexLinkMode) ? [] : [simplexLinkMode]) - ) { mode in - Text(mode.text) - } - } - } - .frame(height: 36) - .onChange(of: simplexLinkMode) { mode in - privacySimplexLinkModeDefault.set(mode) - } - if developerTools { - settingsRow("link.badge.plus", color: theme.colors.secondary) { - Toggle("Use short links (BETA)", isOn: $shortSimplexLinks) - } - } } header: { Text("Chats") .foregroundColor(theme.colors.secondary) @@ -125,7 +112,7 @@ struct PrivacySettings: View { } } settingsRow("circle.filled.pattern.diagonalline.rectangle", color: theme.colors.secondary) { - Picker("Blur media", selection: $privacyMediaBlurRadius) { + WrappedPicker("Blur media", selection: $privacyMediaBlurRadius) { let values = [0, 12, 24, 48] + ([0, 12, 24, 48].contains(privacyMediaBlurRadius) ? [] : [privacyMediaBlurRadius]) ForEach(values, id: \.self) { radius in let text: String = switch radius { @@ -139,7 +126,6 @@ struct PrivacySettings: View { } } } - .frame(height: 36) settingsRow("network.badge.shield.half.filled", color: theme.colors.secondary) { Toggle("Protect IP address", isOn: $askToApproveRelays) } @@ -156,6 +142,18 @@ struct PrivacySettings: View { } } + Section { + settingsRow("checkmark", color: theme.colors.secondary) { + Toggle("Auto-accept", isOn: $autoAcceptMemberContacts) + } + } header: { + Text("Contact requests from groups") + .foregroundColor(theme.colors.secondary) + } footer: { + Text("This setting is for your current profile **\(m.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + } + Section { settingsRow("person", color: theme.colors.secondary) { Toggle("Contacts", isOn: $contactReceipts) @@ -214,6 +212,13 @@ struct PrivacySettings: View { setOrAskSendReceiptsGroups(groupReceipts) } } + .onChange(of: autoAcceptMemberContacts) { _ in + if autoAcceptMemberContactsReset { + autoAcceptMemberContactsReset = false + } else { + setAutoAcceptGrpDirectInvs(autoAcceptMemberContacts) + } + } .onAppear { if let u = m.currentUser { if contactReceipts != u.sendRcptsContacts { @@ -224,6 +229,10 @@ struct PrivacySettings: View { groupReceiptsReset = true groupReceipts = u.sendRcptsSmallGroups } + if autoAcceptMemberContacts != u.autoAcceptMemberContacts { + autoAcceptMemberContactsReset = true + autoAcceptMemberContacts = u.autoAcceptMemberContacts + } } } .alert(item: $alert) { alert in @@ -340,6 +349,23 @@ struct PrivacySettings: View { } } + private func setAutoAcceptGrpDirectInvs(_ enable: Bool) { + Task { + do { + if let currentUser = m.currentUser { + try await apiSetUserAutoAcceptMemberContacts(currentUser.userId, enable: enable) + await MainActor.run { + var updatedUser = currentUser + updatedUser.autoAcceptMemberContacts = enable + m.updateUser(updatedUser) + } + } + } catch let error { + alert = .error(title: "Error setting auto-accept", error: "Error: \(responseError(error))") + } + } + } + private func simplexLockRow(_ value: LocalizedStringKey) -> some View { HStack { Text("SimpleX Lock") @@ -452,7 +478,7 @@ struct SimplexLockView: View { Toggle("Allow sharing", isOn: $allowShareExtension) } } - + if performLA && laMode == .passcode { Section(header: Text("Self-destruct passcode").foregroundColor(theme.colors.secondary)) { Toggle(isOn: $selfDestruct) { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index e06b1c4dd3..cb6fdf8597 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -32,7 +32,6 @@ let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // deprecated, moved t let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode" let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews" let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft" -let DEFAULT_PRIVACY_SHORT_LINKS = "privacyShortLinks" let DEFAULT_PRIVACY_PROTECT_SCREEN = "privacyProtectScreen" let DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET = "privacyDeliveryReceiptsSet" let DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS = "privacyMediaBlurRadius" @@ -58,6 +57,7 @@ let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab" let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown" let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice" let DEFAULT_SHOW_MUTE_PROFILE_ALERT = "showMuteProfileAlert" +let DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "showReportsInSupportChatAlert" let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion" let DEFAULT_ONBOARDING_STAGE = "onboardingStage" let DEFAULT_MIGRATION_TO_STAGE = "migrationToStage" @@ -99,7 +99,6 @@ let appDefaults: [String: Any] = [ DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: SimpleXLinkMode.description.rawValue, DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS: true, DEFAULT_PRIVACY_SAVE_LAST_DRAFT: true, - DEFAULT_PRIVACY_SHORT_LINKS: false, DEFAULT_PRIVACY_PROTECT_SCREEN: false, DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false, DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS: 0, @@ -117,6 +116,7 @@ let appDefaults: [String: Any] = [ DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false, DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true, DEFAULT_SHOW_MUTE_PROFILE_ALERT: true, + DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT: true, DEFAULT_ONBOARDING_STAGE: OnboardingStage.onboardingComplete.rawValue, DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME: 300, DEFAULT_SHOW_UNREAD_AND_FAVORITES: false, @@ -144,6 +144,7 @@ let hintDefaults = [ DEFAULT_LIVE_MESSAGE_ALERT_SHOWN, DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE, DEFAULT_SHOW_MUTE_PROFILE_ALERT, + DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT, DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE, DEFAULT_SHOW_DELETE_CONTACT_NOTICE ] @@ -195,6 +196,8 @@ let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.sta let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE) let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE) +let showReportsInSupportChatAlertDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT) + /// after importing new database, this flag will be set and unset only after importing app settings in `initializeChat` */ let shouldImportAppSettingsDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOULD_IMPORT_APP_SETTINGS) let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME) diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 4813edf96c..1e5b4bff16 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -17,8 +17,8 @@ struct UserAddressView: View { @State var shareViaProfile = false @State var autoCreate = false @State private var showShortLink = true - @State private var aas = AutoAcceptState() - @State private var savedAAS = AutoAcceptState() + @State private var settings = AddressSettingsState() + @State private var savedSettings = AddressSettingsState() @State private var showMailView = false @State private var mailViewResult: Result? = nil @State private var alert: UserAddressAlert? @@ -66,8 +66,8 @@ struct UserAddressView: View { if let userAddress = chatModel.userAddress { existingAddressView(userAddress) .onAppear { - aas = AutoAcceptState(userAddress: userAddress) - savedAAS = aas + settings = AddressSettingsState(settings: userAddress.addressSettings) + savedSettings = AddressSettingsState(settings: userAddress.addressSettings) } } else { Section { @@ -138,25 +138,28 @@ struct UserAddressView: View { Section { SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: $showShortLink) .id("simplex-contact-address-qrcode-\(userAddress.connLinkContact.simplexChatUri(short: showShortLink))") - shareQRCodeButton(userAddress) + if userAddress.shouldBeUpgraded { + upgradeAddressButton() + } + shareAddressButton(userAddress) // if MFMailComposeViewController.canSendMail() { // shareViaEmailButton(userAddress) // } settingsRow("briefcase", color: theme.colors.secondary) { - Toggle("Business address", isOn: $aas.business) - .onChange(of: aas.business) { ba in + Toggle("Business address", isOn: $settings.businessAddress) + .onChange(of: settings.businessAddress) { ba in if ba { - aas.enable = true - aas.incognito = false + settings.autoAccept = true + settings.autoAcceptIncognito = false } - saveAAS($aas, $savedAAS) + saveAddressSettings(settings, $savedSettings) } } addressSettingsButton(userAddress) } header: { ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink) } footer: { - if aas.business { + if settings.businessAddress { Text("Add your team members to the conversations.") .foregroundColor(theme.colors.secondary) } @@ -193,12 +196,12 @@ struct UserAddressView: View { progressIndicator = true Task { do { - let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) - let connLinkContact = try await apiCreateUserAddress(short: short) - DispatchQueue.main.async { - chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact) - alert = .shareOnCreate - progressIndicator = false + if let connLinkContact = try await apiCreateUserAddress() { + DispatchQueue.main.async { + chatModel.userAddress = UserContactLink(connLinkContact) + alert = .shareOnCreate + progressIndicator = false + } } } catch let error { logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") @@ -209,6 +212,16 @@ struct UserAddressView: View { } } + private func upgradeAddressButton() -> some View { + Button { + upgradeAndShareAddressAlert(progressIndicator: $progressIndicator) + } label: { + settingsRow("arrow.up", color: theme.colors.primary) { + Text("Upgrade address") + } + } + } + private func createOneTimeLinkButton() -> some View { NavigationLink { NewChatView(selection: .invite) @@ -230,9 +243,13 @@ struct UserAddressView: View { } } - private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { - Button { - showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: showShortLink))]) + private func shareAddressButton(_ userAddress: UserContactLink) -> some View { + return Button { + if userAddress.shouldBeUpgraded { + upgradeAndShareAddressAlert(progressIndicator: $progressIndicator, shareAddress: { userAddress.shareAddress(short: showShortLink) }) + } else { + userAddress.shareAddress(short: showShortLink) + } } label: { settingsRow("square.and.arrow.up", color: theme.colors.secondary) { Text("Share address") @@ -295,14 +312,55 @@ struct UserAddressView: View { } } +func upgradeAndShareAddressAlert(progressIndicator: Binding, shareAddress: (() -> Void)? = nil) { + showAlert( + NSLocalizedString("Upgrade address?", comment: "alert message"), + message: NSLocalizedString("The address will be short, and your profile will be shared via the address.", comment: "alert message"), + actions: { + var actions = [UIAlertAction(title: NSLocalizedString("Upgrade", comment: "alert button"), style: .default) { _ in + addShortLink(progressIndicator: progressIndicator, shareOnCompletion: shareAddress != nil) + }] + if let shareAddress { + actions.append(UIAlertAction(title: NSLocalizedString("Share old address", comment: "alert button"), style: .default) { _ in + shareAddress() + }) + } + actions.append(cancelAlertAction) + return actions + } + ) +} + +private func addShortLink(progressIndicator: Binding, shareOnCompletion: Bool = false) { + progressIndicator.wrappedValue = true + Task { + do { + let userAddress = try await apiAddMyAddressShortLink() + await MainActor.run { + ChatModel.shared.userAddress = userAddress + progressIndicator.wrappedValue = false + if shareOnCompletion, let userAddress { + userAddress.shareAddress(short: true) + } + } + } catch let error { + logger.error("apiAddMyAddressShortLink: \(responseError(error))") + showAlert("Error adding short link", message: responseError(error)) + await MainActor.run { progressIndicator.wrappedValue = false } + } + } +} + + struct ToggleShortLinkHeader: View { @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false let text: Text var link: CreatedConnLink @Binding var short: Bool - + var body: some View { - if link.connShortLink == nil { + if link.connShortLink == nil || !developerTools { text.foregroundColor(theme.colors.secondary) } else { HStack { @@ -317,45 +375,27 @@ struct ToggleShortLinkHeader: View { } } -private struct AutoAcceptState: Equatable { - var enable = false - var incognito = false - var business = false - var welcomeText = "" +struct AddressSettingsState: Equatable { + var businessAddress = false + var autoAccept = false + var autoAcceptIncognito = false + var autoReply = "" - init(enable: Bool = false, incognito: Bool = false, business: Bool = false, welcomeText: String = "") { - self.enable = enable - self.incognito = incognito - self.business = business - self.welcomeText = welcomeText + init() {} + + init(settings: AddressSettings) { + self.businessAddress = settings.businessAddress + self.autoAccept = settings.autoAccept != nil + self.autoAcceptIncognito = settings.autoAccept?.acceptIncognito == true + self.autoReply = settings.autoReply?.text ?? "" } - init(userAddress: UserContactLink) { - if let aa = userAddress.autoAccept { - enable = true - incognito = aa.acceptIncognito - business = aa.businessAddress - if let msg = aa.autoReply { - welcomeText = msg.text - } else { - welcomeText = "" - } - } else { - enable = false - incognito = false - business = false - welcomeText = "" - } - } - - var autoAccept: AutoAccept? { - if enable { - var autoReply: MsgContent? = nil - let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) - if s != "" { autoReply = .text(s) } - return AutoAccept(businessAddress: business, acceptIncognito: incognito, autoReply: autoReply) - } - return nil + var addressSettings: AddressSettings { + AddressSettings( + businessAddress: self.businessAddress, + autoAccept: self.autoAccept ? AutoAccept(acceptIncognito: self.autoAcceptIncognito) : nil, + autoReply: self.autoReply.isEmpty ? nil : MsgContent.text(self.autoReply) + ) } } @@ -380,30 +420,32 @@ struct UserAddressSettingsView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @Binding var shareViaProfile: Bool - @State private var aas = AutoAcceptState() - @State private var savedAAS = AutoAcceptState() + @State private var settings = AddressSettingsState() + @State private var savedSettings = AddressSettingsState() @State private var ignoreShareViaProfileChange = false @State private var progressIndicator = false - @FocusState private var keyboardVisible: Bool var body: some View { ZStack { if let userAddress = ChatModel.shared.userAddress { userAddressSettingsView() .onAppear { - aas = AutoAcceptState(userAddress: userAddress) - savedAAS = aas + settings = AddressSettingsState(settings: userAddress.addressSettings) + savedSettings = AddressSettingsState(settings: userAddress.addressSettings) } - .onChange(of: aas.enable) { aasEnabled in - if !aasEnabled { aas = AutoAcceptState() } + .onChange(of: settings.autoAccept) { autoAccept in + if !autoAccept { + settings.businessAddress = false + settings.autoReply = "" + } } .onDisappear { - if savedAAS != aas { + if savedSettings != settings { showAlert( - title: NSLocalizedString("Auto-accept settings", comment: "alert title"), + title: NSLocalizedString("SimpleX address settings", comment: "alert title"), message: NSLocalizedString("Settings were changed.", comment: "alert message"), buttonTitle: NSLocalizedString("Save", comment: "alert button"), - buttonAction: { saveAAS($aas, $savedAAS) }, + buttonAction: { saveAddressSettings(settings, $savedSettings) }, cancelButton: true ) } @@ -421,11 +463,22 @@ struct UserAddressSettingsView: View { List { Section { shareWithContactsButton() - autoAcceptToggle().disabled(aas.business) + autoAcceptToggle().disabled(settings.businessAddress) + if settings.autoAccept && !ChatModel.shared.addressShortLinkDataSet && !settings.businessAddress { + acceptIncognitoToggle() + } } - if aas.enable { - autoAcceptSection() + Section { + messageEditor(placeholder: NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"), text: $settings.autoReply) + } header: { + Text("Welcome message") + .foregroundColor(theme.colors.secondary) + } + + Section { + saveAddressSettingsButton() + .disabled(settings == savedSettings) } } } @@ -444,7 +497,7 @@ struct UserAddressSettingsView: View { actions: {[ UIAlertAction( title: NSLocalizedString("Cancel", comment: "alert action"), - style: .default, + style: .cancel, handler: { _ in ignoreShareViaProfileChange = true shareViaProfile = !on @@ -466,7 +519,7 @@ struct UserAddressSettingsView: View { actions: {[ UIAlertAction( title: NSLocalizedString("Cancel", comment: "alert action"), - style: .default, + style: .cancel, handler: { _ in ignoreShareViaProfileChange = true shareViaProfile = !on @@ -489,46 +542,31 @@ struct UserAddressSettingsView: View { private func autoAcceptToggle() -> some View { settingsRow("checkmark", color: theme.colors.secondary) { - Toggle("Auto-accept", isOn: $aas.enable) - .onChange(of: aas.enable) { _ in - saveAAS($aas, $savedAAS) + Toggle("Auto-accept", isOn: $settings.autoAccept) + .onChange(of: settings.autoAccept) { _ in + saveAddressSettings(settings, $savedSettings) } } } - private func autoAcceptSection() -> some View { - Section { - if !aas.business { - acceptIncognitoToggle() - } - welcomeMessageEditor() - saveAASButton() - .disabled(aas == savedAAS) - } header: { - Text("Auto-accept") - .foregroundColor(theme.colors.secondary) - } - } - private func acceptIncognitoToggle() -> some View { settingsRow( - aas.incognito ? "theatermasks.fill" : "theatermasks", - color: aas.incognito ? .indigo : theme.colors.secondary + settings.autoAcceptIncognito ? "theatermasks.fill" : "theatermasks", + color: settings.autoAcceptIncognito ? .indigo : theme.colors.secondary ) { - Toggle("Accept incognito", isOn: $aas.incognito) + Toggle("Accept incognito", isOn: $settings.autoAcceptIncognito) } } - private func welcomeMessageEditor() -> some View { + private func messageEditor(placeholder: String, text: Binding) -> some View { ZStack { Group { - if aas.welcomeText.isEmpty { - TextEditor(text: Binding.constant(NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"))) + if text.wrappedValue.isEmpty { + TextEditor(text: Binding.constant(placeholder)) .foregroundColor(theme.colors.secondary) .disabled(true) } - TextEditor(text: $aas.welcomeText) - .focused($keyboardVisible) + TextEditor(text: text) } .padding(.horizontal, -5) .padding(.top, -8) @@ -537,27 +575,27 @@ struct UserAddressSettingsView: View { } } - private func saveAASButton() -> some View { + private func saveAddressSettingsButton() -> some View { Button { - keyboardVisible = false - saveAAS($aas, $savedAAS) + hideKeyboard() + saveAddressSettings(settings, $savedSettings) } label: { Text("Save") } } } -private func saveAAS(_ aas: Binding, _ savedAAS: Binding) { +private func saveAddressSettings(_ settings: AddressSettingsState, _ savedSettings: Binding) { Task { do { - if let address = try await userAddressAutoAccept(aas.wrappedValue.autoAccept) { + if let address = try await apiSetUserAddressSettings(settings.addressSettings) { await MainActor.run { ChatModel.shared.userAddress = address - savedAAS.wrappedValue = aas.wrappedValue + savedSettings.wrappedValue = settings } } } catch let error { - logger.error("userAddressAutoAccept error: \(responseError(error))") + logger.error("apiSetUserAddressSettings error: \(responseError(error))") } } } @@ -565,9 +603,8 @@ private func saveAAS(_ aas: Binding, _ savedAAS: Binding Void - ) -> some View { - Image(systemName: systemName) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(height: 12) - .foregroundColor(theme.colors.primary) - .padding(6) - .frame(width: 36, height: 36, alignment: .center) - .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) - .clipShape(Circle()) - .contentShape(Circle()) - .padding([.trailing, edge], -12) - .onTapGesture(perform: action) - } - private func showFullName(_ user: User) -> Bool { user.profile.fullName != "" && user.profile.fullName != user.profile.displayName } - + + private func bioFitsLimit() -> Bool { + chatJsonLength(shortDescr) <= MAX_BIO_LENGTH_BYTES + } + private var canSaveProfile: Bool { - currentProfileHash != profile.hashValue && + ( + currentProfileHash != profile.hashValue || + (chatModel.currentUser?.profile.shortDescr ?? "") != shortDescr.trimmingCharacters(in: .whitespaces) + ) && profile.displayName.trimmingCharacters(in: .whitespaces) != "" && - validDisplayName(profile.displayName) + validDisplayName(profile.displayName) && + bioFitsLimit() } private func saveProfile() { @@ -167,6 +150,7 @@ struct UserProfile: View { Task { do { profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) + profile.shortDescr = shortDescr.trimmingCharacters(in: .whitespaces) if let (newProfile, _) = try await apiUpdateProfile(profile: profile) { await MainActor.run { chatModel.updateCurrentUser(newProfile) @@ -185,12 +169,59 @@ struct UserProfile: View { if let user = chatModel.currentUser { profile = fromLocalProfile(user.profile) currentProfileHash = profile.hashValue + shortDescr = profile.shortDescr ?? "" } } } -func profileImageView(_ imageStr: String?) -> some View { - ProfileImage(imageStr: imageStr, size: 192) +struct EditProfileImage: View { + @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner + @Binding var profileImage: String? + @Binding var showChooseSource: Bool + + var body: some View { + Group { + if profileImage != nil { + ZStack(alignment: .bottomTrailing) { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: profileImage, size: 160) + .onTapGesture { showChooseSource = true } + overlayButton("multiply", edge: .top) { profileImage = nil } + } + overlayButton("camera", edge: .bottom) { showChooseSource = true } + } + } else { + ZStack(alignment: .center) { + ProfileImage(imageStr: profileImage, size: 160) + editImageButton { showChooseSource = true } + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .contentShape(Rectangle()) + } + + private func overlayButton( + _ systemName: String, + edge: Edge.Set, + action: @escaping () -> Void + ) -> some View { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 12) + .foregroundColor(theme.colors.primary) + .padding(6) + .frame(width: 36, height: 36, alignment: .center) + .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) + .clipShape(Circle()) + .contentShape(Circle()) + .padding([.trailing, edge], -12) + .onTapGesture(perform: action) + } } func editImageButton(action: @escaping () -> Void) -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 887023b670..ddfe59e719 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -350,7 +350,7 @@ struct UserProfilesView: View { Image(systemName: "checkmark").foregroundColor(theme.colors.onBackground) } else { if userInfo.unreadCount > 0 { - UnreadBadge(userInfo: userInfo) + userUnreadBadge(userInfo, theme: theme) } if user.hidden { Image(systemName: "lock").foregroundColor(theme.colors.secondary) diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index e965e5a1a5..427430b833 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -1374,12 +1374,14 @@ خطأ في تغيير الإعدادات No comment provided by engineer. - + Error creating address + خطأ في إنشاء العنوان No comment provided by engineer. - + Error creating group + خطأ في إنشاء المجموعة No comment provided by engineer. @@ -2229,8 +2231,8 @@ We will be adding server redundancy to prevent lost messages. Please store passphrase securely, you will NOT be able to change it if you lose it. No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -2557,8 +2559,8 @@ We will be adding server redundancy to prevent lost messages. Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور server test error @@ -3618,7 +3620,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -3800,8 +3802,8 @@ SimpleX servers cannot see your profile. نعم pref value - - you are invited to group + + You are invited to group أنت مدعو إلى المجموعة No comment provided by engineer. @@ -4247,8 +4249,8 @@ SimpleX servers cannot see your profile. Server type نوع الخادم - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور @@ -4587,8 +4589,8 @@ SimpleX servers cannot see your profile. Ask اسأل - - Auto-accept settings + + SimpleX address settings إعدادات القبول التلقائي @@ -5757,6 +5759,102 @@ This is your own one-time link! Encryption re-negotiation failed. فشل إعادة التفاوض على التشفير. + + Accept as member + اقبل كعضو + + + Accept as observer + اقبل كمراقب + + + Accept contact request + اقبل طلب الاتصال + + + Accept member + اقبل العضو + + + Add message + أضف رسالة + + + All servers + كل الخوادم + + + Bio + نبذة + + + Bio too large + النبذة كبيرة جدًا + + + Can't change profile + لا يمكن تغيير الحساب + + + Chat with admins + تحدث مع المدراء + + + Chat with member + تحدث مع العضو + + + Chat with members before they join. + تحدث مع الأعضاء قبل انضمامهم. + + + Chats with members + تحدث مع الأعضاء + + + Connect faster! 🚀 + اتصل بسرعة! 🚀 + + + Contact requests from groups + طلبات الاتصال من المجموعات + + + Create your address + أنشئ عنوانك + + + Delete chat with member? + حذف المحادثة مع العضو؟ + + + Description too large + الوصف كبير جدًا + + + Empty message! + رسالة فارغة! + + + Enable disappearing messages by default. + فعّل حذف الرسائل تلقائيا. + + + Error accepting member + خطأ في قبول العضو + + + Error adding short link + خطأ في إضافة الرابط القصير + + + Error changing chat profile + خطأ في تغيير حساب المحادثات + + + Error connecting to forwarding server %@. Please try later. + خطأ في الإتصال بخادم التحويل @%. حاول مجددا بعد حين. + diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 776199ac1f..e3ca0e6a43 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -102,10 +102,12 @@ %@ server + %@ сървър No comment provided by engineer. %@ servers + %@ сървъри No comment provided by engineer. @@ -145,18 +147,22 @@ %d file(s) are still being downloaded. + %d файл(ове) все още се теглят. forward confirmation reason %d file(s) failed to download. + Тегленето неуспешно за %d файл(ове). forward confirmation reason %d file(s) were deleted. + %d файл(ове) бяха изтрити. forward confirmation reason %d file(s) were not downloaded. + %d файл(ове) не бяха изтеглени. forward confirmation reason @@ -166,6 +172,7 @@ %d messages not forwarded + %d непрепратени съобщения alert title @@ -185,6 +192,7 @@ %d seconds(s) + %d секунди delete after time @@ -354,6 +362,7 @@ **Scan / Paste link**: to connect via a link you received. + **Сканирай / Постави линк**: за свързване чрез получения линк. No comment provided by engineer. @@ -457,6 +466,7 @@ time interval 1 year + 1 година delete after time @@ -466,6 +476,7 @@ time interval 1-time link can be used *with one contact only* - share in person or via any messenger. + Еднократният линк може да се използва само веднъж *с един контакт* - споделете го лично или чрез някой месинджър. No comment provided by engineer. @@ -552,8 +563,19 @@ time interval Приеми accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Приеми като член + alert action + + + Accept as observer + Приеми като наблюдател + alert action + Accept conditions Приеми условията @@ -564,6 +586,11 @@ swipe action Приемане на заявка за връзка? No comment provided by engineer. + + Accept contact request + Приеми заявка за контакт + alert title + Accept contact request from %@? Приемане на заявка за контакт от %@? @@ -572,9 +599,14 @@ swipe action Accept incognito Приеми инкогнито - accept contact request via notification + alert action swipe action + + Accept member + Приеми член + alert title + Accepted conditions Приети условия @@ -592,6 +624,7 @@ swipe action Active + Активен token status text @@ -611,8 +644,14 @@ swipe action Add list + Добави списък No comment provided by engineer. + + Add message + Добави съобщение + placeholder for sending contact request + Add profile Добави профил @@ -640,6 +679,7 @@ swipe action Add to list + Добави към списъка No comment provided by engineer. @@ -719,6 +759,7 @@ swipe action All + Всички No comment provided by engineer. @@ -733,6 +774,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. + Всички чатове ще бъдат премахнати от списъка %@, а списъкът ще бъде изтрит. alert message @@ -752,6 +794,7 @@ swipe action All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Всички съобщения и файлове се изпращат с **криптиране от край до край**, с постквантова сигурност в директните съобщения. No comment provided by engineer. @@ -776,10 +819,12 @@ swipe action All reports will be archived for you. + Всички доклади за нарушения ще бъдат архивирани за вас. No comment provided by engineer. All servers + Всички сървъри No comment provided by engineer. @@ -822,6 +867,11 @@ swipe action Позволи понижаване No comment provided by engineer. + + Allow files and media only if your contact allows them. + Разреши файлове и медия само ако вашият контакт ги разрешава. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа) @@ -859,6 +909,7 @@ swipe action Allow to report messsages to moderators. + Позволи докладването на съобщения на модераторите. No comment provided by engineer. @@ -906,6 +957,11 @@ swipe action Позволи на вашите контакти да изпращат изчезващи съобщения. No comment provided by engineer. + + Allow your contacts to send files and media. + Позволи на вашите контактите да изпращат файлове и медия. + No comment provided by engineer. + Allow your contacts to send voice messages. Позволи на вашите контакти да изпращат гласови съобщения. @@ -919,12 +975,12 @@ swipe action Already connecting! В процес на свързване! - No comment provided by engineer. + new chat sheet title Already joining the group! Вече се присъединихте към групата! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -943,6 +999,7 @@ swipe action Another reason + Друга причина report reason @@ -972,6 +1029,7 @@ swipe action App group: + Група приложения: No comment provided by engineer. @@ -1021,14 +1079,17 @@ swipe action Archive + Архивирай No comment provided by engineer. Archive %lld reports? + Архивирай %lld доклад(а)? No comment provided by engineer. Archive all reports? + Архивиране на всички доклади за нарушения? No comment provided by engineer. @@ -1043,14 +1104,17 @@ swipe action Archive report + Архивирай доклад за нарушения No comment provided by engineer. Archive report? + Архивирай доклад за нарушения? No comment provided by engineer. Archive reports + Архивирай докладите за нарушения swipe action @@ -1123,11 +1187,6 @@ swipe action Автоматично приемане на изображения No comment provided by engineer. - - Auto-accept settings - Автоматично приемане на настройки - alert title - Back Назад @@ -1165,6 +1224,7 @@ swipe action Better groups performance + По-добра производителност на групите No comment provided by engineer. @@ -1189,6 +1249,7 @@ swipe action Better privacy and security + По-добра поверителност и сигурност No comment provided by engineer. @@ -1201,6 +1262,16 @@ swipe action Подобрен интерфейс No comment provided by engineer. + + Bio + Био + No comment provided by engineer. + + + Bio too large + Биографията е твърде дълга + alert title + Black Черна @@ -1251,6 +1322,11 @@ swipe action Размазване на медия No comment provided by engineer. + + Bot + Бот + No comment provided by engineer. + Both you and your contact can add message reactions. И вие, и вашият контакт можете да добавяте реакции към съобщението. @@ -1271,6 +1347,11 @@ swipe action И вие, и вашият контакт можете да изпращате изчезващи съобщения. No comment provided by engineer. + + Both you and your contact can send files and media. + И вие, и вашият контакт можете да изпращате файлове и медия. + No comment provided by engineer. + Both you and your contact can send voice messages. И вие, и вашият контакт можете да изпращате гласови съобщения. @@ -1291,8 +1372,14 @@ swipe action Бизнес чатове No comment provided by engineer. + + Business connection + Бизнес връзка + No comment provided by engineer. + Businesses + Бизнеси No comment provided by engineer. @@ -1304,6 +1391,9 @@ swipe action By using SimpleX Chat you agree to: - send only legal content in public groups. - respect other users – no spam. + С използването на SimpleX Chat вие се съгласявате със: +- изпращане само на легално съдържание в публични групи. +- уважение към другите потребители – без спам. No comment provided by engineer. @@ -1336,6 +1426,11 @@ swipe action Обаждането на члена не е позволено No comment provided by engineer. + + Can't change profile + Промяната на профила е невъзможна + alert title + Can't invite contact! Не може да покани контакта! @@ -1348,13 +1443,15 @@ swipe action Can't message member + Изпращането на съобщения на груповия член не е налично No comment provided by engineer. Cancel Отказ alert action -alert button +alert button +new chat action Cancel migration @@ -1368,6 +1465,7 @@ alert button Cannot forward message + Съобщение не може да бъде препратено No comment provided by engineer. @@ -1392,6 +1490,7 @@ alert button Change automatic message deletion? + Промяна на автоматичното изтриване на съобщения? alert title @@ -1447,18 +1546,22 @@ set passcode view Chat + Чат No comment provided by engineer. Chat already exists + Чатът вече съществува No comment provided by engineer. Chat already exists! - No comment provided by engineer. + Чатът вече съществува! + new chat sheet title Chat colors + Цветове на чата No comment provided by engineer. @@ -1478,6 +1581,7 @@ set passcode view Chat database exported + Базата данни е експортирана No comment provided by engineer. @@ -1502,6 +1606,7 @@ set passcode view Chat list + Списък с чатове No comment provided by engineer. @@ -1516,6 +1621,7 @@ set passcode view Chat preferences were changed. + Настройките на чата бяха променени. alert message @@ -1525,14 +1631,32 @@ set passcode view Chat theme + Тема на чата No comment provided by engineer. Chat will be deleted for all members - this cannot be undone! + Чатът ще бъде изтрит за всички членове - това не може да бъде отменено! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + Чатът ще бъде изтрит за вас - това не може да бъде отменено! + No comment provided by engineer. + + + Chat with admins + Чат с администраторите + chat toolbar + + + Chat with member + Чат с член + No comment provided by engineer. + + + Chat with members before they join. + Разговаряйте с членовете, преди да се присъединят. No comment provided by engineer. @@ -1540,12 +1664,19 @@ set passcode view Чатове No comment provided by engineer. + + Chats with members + Чатове с членовете + No comment provided by engineer. + Check messages every 20 min. + Проверявай за съобщенията на всеки 20 минути. No comment provided by engineer. Check messages when allowed. + Проверявай за съобщенията, когато е разрешено. No comment provided by engineer. @@ -1575,14 +1706,17 @@ set passcode view Chunks deleted + Изтрити парчета No comment provided by engineer. Chunks downloaded + Изтеглени парчета No comment provided by engineer. Chunks uploaded + Качени парчета No comment provided by engineer. @@ -1602,10 +1736,12 @@ set passcode view Clear group? + Изчисти група? No comment provided by engineer. Clear or delete group? + Изчисти или изтрий група? No comment provided by engineer. @@ -1620,14 +1756,17 @@ set passcode view Color chats with the new themes. + Цветни чатове с нови теми. No comment provided by engineer. Color mode + Цветен режим No comment provided by engineer. Community guidelines violation + Нарушение на правилата на общността report reason @@ -1642,34 +1781,42 @@ set passcode view Completed + Завършен No comment provided by engineer. Conditions accepted on: %@. + Условия, приети на: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Условията са приети за оператора(ите): **%@**. No comment provided by engineer. Conditions are already accepted for these operator(s): **%@**. + Условията вече са приети за тези оператори: **%@**. No comment provided by engineer. Conditions of use + Условия за ползване No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Условията ще бъдат приети за операторите: **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Условията ще бъдат приети на: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Условията ще бъдат автоматично приети за активираните оператори на: %@. No comment provided by engineer. @@ -1679,6 +1826,7 @@ set passcode view Configure server operators + Конфигуриране на сървърни оператори No comment provided by engineer. @@ -1693,6 +1841,7 @@ set passcode view Confirm contact deletion? + Потвърди изтриването на контакта? No comment provided by engineer. @@ -1702,6 +1851,7 @@ set passcode view Confirm files from unknown servers. + Потвърди файлове от неизвестни сървъри. No comment provided by engineer. @@ -1731,6 +1881,7 @@ set passcode view Confirmed + Потвърдено token status text @@ -1743,9 +1894,9 @@ set passcode view Автоматично свъзрване No comment provided by engineer. - - Connect incognito - Свързване инкогнито + + Connect faster! 🚀 + Свържете се по-бързо! 🚀 No comment provided by engineer. @@ -1755,11 +1906,7 @@ set passcode view Connect to your friends faster. - No comment provided by engineer. - - - Connect to yourself? - Свърване със себе си? + Свържете се с приятелите си по-бързо. No comment provided by engineer. @@ -1767,37 +1914,38 @@ set passcode view This is your own SimpleX address! Свърване със себе си? Това е вашият личен SimpleX адрес! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Свърване със себе си? Това е вашят еднократен линк за връзка! - No comment provided by engineer. + new chat sheet title Connect via contact address Свързване чрез адрес за контакт - No comment provided by engineer. + new chat sheet title Connect via link Свърване чрез линк - No comment provided by engineer. + new chat sheet title Connect via one-time link Свързване чрез еднократен линк за връзка - No comment provided by engineer. + new chat sheet title Connect with %@ Свързване с %@ - No comment provided by engineer. + new chat action Connected + Свързан No comment provided by engineer. @@ -1807,6 +1955,7 @@ This is your own one-time link! Connected servers + Свързани сървъри No comment provided by engineer. @@ -1816,6 +1965,7 @@ This is your own one-time link! Connecting + Свързване No comment provided by engineer. @@ -1830,6 +1980,7 @@ This is your own one-time link! Connecting to contact, please wait or check later! + Тече свързване с контакт, моля изчакайте или проверете по-късно! No comment provided by engineer. @@ -1844,16 +1995,18 @@ This is your own one-time link! Connection and servers status. + Състояние на връзката и сървърите. No comment provided by engineer. Connection blocked + Връзката е блокирана No comment provided by engineer. Connection error Грешка при свързване - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1863,14 +2016,18 @@ This is your own one-time link! Connection is blocked by server operator: %@ + Връзката е блокирана от оператора на сървъра: +%@ No comment provided by engineer. Connection not ready. + Връзката не е готова. No comment provided by engineer. Connection notifications + Известия за връзката No comment provided by engineer. @@ -1880,6 +2037,7 @@ This is your own one-time link! Connection requires encryption renegotiation. + Връзката изисква предоговаряне на криптирането. No comment provided by engineer. @@ -1894,7 +2052,7 @@ This is your own one-time link! Connection timeout Времето на изчакване за установяване на връзката изтече - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1942,6 +2100,10 @@ This is your own one-time link! Настройки за контакт No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! No comment provided by engineer. @@ -2051,9 +2213,8 @@ This is your own one-time link! Създай опашка server test step - - Create secret group - Създай тайна група + + Create your address No comment provided by engineer. @@ -2300,6 +2461,10 @@ swipe action Изтриване на чат профила? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? No comment provided by engineer. @@ -2485,11 +2650,19 @@ swipe action Потвърждениe за доставка! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Описание No comment provided by engineer. + + Description too large + alert title + Desktop address Адрес на настолно устройство @@ -2698,7 +2871,7 @@ swipe action Don't show again Не показвай отново - No comment provided by engineer. + alert action Done @@ -2775,6 +2948,10 @@ chat item action Редактирай групов профил No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable Активирай @@ -2809,6 +2986,10 @@ chat item action Разреши достъпа до камерата No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Активиране за всички @@ -3006,6 +3187,10 @@ chat item action Грешка при приемане на заявка за контакт No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) Грешка при добавяне на член(ове) @@ -3015,11 +3200,19 @@ chat item action Error adding server alert title + + Error adding short link + No comment provided by engineer. + Error changing address Грешка при промяна на адреса No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile No comment provided by engineer. @@ -3032,7 +3225,7 @@ chat item action Error changing setting Грешка при промяна на настройката - No comment provided by engineer. + alert title Error changing to incognito! @@ -3044,7 +3237,7 @@ chat item action Error connecting to forwarding server %@. Please try later. - No comment provided by engineer. + alert message Error creating address @@ -3089,15 +3282,19 @@ chat item action Грешка при декриптирането на файла No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database Грешка при изтриване на базата данни - No comment provided by engineer. + alert title Error deleting chat! Грешка при изтриването на чата! - No comment provided by engineer. + alert title Error deleting connection @@ -3107,12 +3304,12 @@ chat item action Error deleting database Грешка при изтриване на базата данни - No comment provided by engineer. + alert title Error deleting old database Грешка при изтриване на старата база данни - No comment provided by engineer. + alert title Error deleting token @@ -3147,7 +3344,7 @@ chat item action Error exporting chat database Грешка при експортиране на базата данни - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3156,7 +3353,7 @@ chat item action Error importing chat database Грешка при импортиране на базата данни - No comment provided by engineer. + alert title Error joining group @@ -3176,6 +3373,10 @@ chat item action Грешка при отваряне на чата No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file Грешка при получаване на файл @@ -3193,10 +3394,14 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + alert title + Error removing member Грешка при отстраняване на член - No comment provided by engineer. + alert title Error reordering lists @@ -3264,6 +3469,10 @@ chat item action Грешка при изпращане на съобщение No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Грешка при настройването на потвърждениeто за доставка!! @@ -3281,7 +3490,7 @@ chat item action Error switching profile - No comment provided by engineer. + alert title Error switching profile! @@ -3343,6 +3552,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Грешка: URL адресът е невалиден @@ -3505,6 +3718,10 @@ snd error text Файлове и медия chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Файловете и медията са забранени в тази група. @@ -3545,6 +3762,23 @@ snd error text Намирайте чатове по-бързо No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Поправи @@ -3645,8 +3879,8 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3731,7 +3965,7 @@ Error: %2$@ Group already exists! Групата вече съществува! - No comment provided by engineer. + new chat sheet title Group display name @@ -3798,6 +4032,10 @@ Error: %2$@ Груповият профил се съхранява на устройствата на членовете, а не на сървърите. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Съобщение при посрещане в групата @@ -4163,7 +4401,7 @@ More improvements are coming soon! Invalid link Невалиден линк - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4274,37 +4512,32 @@ More improvements are coming soon! Присъединяване swipe action + + Join as %@ + присъединяване като %@ + No comment provided by engineer. + Join group Влез в групата - No comment provided by engineer. + new chat sheet title Join group conversations Присъединяване към групи No comment provided by engineer. - - Join group? - Влез в групата? - No comment provided by engineer. - Join incognito Влез инкогнито No comment provided by engineer. - - Join with current profile - Присъединяване с текущия профил - No comment provided by engineer. - Join your group? This is your link for group %@! Влез в твоята група? Това е вашят линк за група %@! - No comment provided by engineer. + new chat action Joining group @@ -4330,6 +4563,10 @@ This is your link for group %@! Запази неизползваната покана за връзка? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections Запазете връзките си @@ -4383,6 +4620,10 @@ This is your link for group %@! Напусни групата? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Нека да поговорим в SimpleX Chat @@ -4435,6 +4676,10 @@ This is your link for group %@! Съобщения на живо No comment provided by engineer. + + Loading profile… + in progress text + Local name Локално име @@ -4508,10 +4753,22 @@ This is your link for group %@! Член No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4539,6 +4796,10 @@ This is your link for group %@! Членът ще бъде премахнат от групата - това не може да бъде отменено! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. Членовете на групата могат да добавят реакции към съобщенията. @@ -4609,6 +4870,10 @@ This is your link for group %@! Message forwarded item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. item status description @@ -4677,6 +4942,10 @@ This is your link for group %@! Съобщения и файлове No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! Съобщенията от %@ ще бъдат показани! @@ -4917,6 +5186,10 @@ This is your link for group %@! New events notification + + New group role: Moderator + No comment provided by engineer. + New in %@ Ново в %@ @@ -4931,6 +5204,10 @@ This is your link for group %@! Нова членска роля No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message Ново съобщение @@ -4967,6 +5244,10 @@ This is your link for group %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected Няма избрани контакти @@ -5040,6 +5321,10 @@ This is your link for group %@! Няма разрешение за запис на гласово съобщение No comment provided by engineer. + + No private routing session + alert title + No push server Локално @@ -5140,7 +5425,9 @@ This is your link for group %@! Ok Ок - alert button + alert action +alert button +new chat action Old database @@ -5227,6 +5514,10 @@ Requires compatible VPN. Само вие можете да изпращате изчезващи съобщения. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Само вие можете да изпращате гласови съобщения. @@ -5252,6 +5543,10 @@ Requires compatible VPN. Само вашият контакт може да изпраща изчезващи съобщения. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Само вашият контакт може да изпраща гласови съобщения. @@ -5274,21 +5569,29 @@ Requires compatible VPN. Open chat Отвори чат - No comment provided by engineer. + new chat action Open chat console Отвори конзолата authentication reason + + Open clean link + alert action + Open conditions No comment provided by engineer. + + Open full link + alert action + Open group Отвори група - No comment provided by engineer. + new chat action Open link? @@ -5299,6 +5602,30 @@ Requires compatible VPN. Отвори миграцията към друго устройство authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… Приложението се отваря… @@ -5398,11 +5725,6 @@ Requires compatible VPN. Парола за показване No comment provided by engineer. - - Past member %@ - Бивш член %@ - past/unknown group member - Paste desktop address Постави адрес на настолно устройство @@ -5468,7 +5790,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. Моля, проверете мрежовата си връзка с %@ и опитайте отново. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5531,6 +5853,10 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5548,11 +5874,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен - server test error - Preserve the last message draft, with attachments. Запазете последната чернова на съобщението с прикачени файлове. @@ -5626,7 +5947,11 @@ Error: %@ Private routing error - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5725,6 +6050,10 @@ Enable in *Network & servers* settings. Защитете чат профилите с парола! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout Време за изчакване на протокола @@ -5937,7 +6266,8 @@ Enable in *Network & servers* settings. Reject Отхвърляне - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -5948,7 +6278,11 @@ swipe action Reject contact request Отхвърли заявката за контакт - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -5973,6 +6307,10 @@ swipe action Remove image No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Острани член @@ -5988,6 +6326,10 @@ swipe action Премахване на паролата от keychain? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Предоговоряне @@ -6003,11 +6345,6 @@ swipe action Предоговори криптирането? No comment provided by engineer. - - Repeat connection request? - Изпрати отново заявката за свързване? - No comment provided by engineer. - Repeat download Повтори изтеглянето @@ -6018,11 +6355,6 @@ swipe action Повтори импортирането No comment provided by engineer. - - Repeat join request? - Изпрати отново заявката за присъединяване? - No comment provided by engineer. - Repeat upload Повтори качването @@ -6053,6 +6385,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -6146,7 +6482,7 @@ swipe action Retry Опитай отново - No comment provided by engineer. + alert action Reveal @@ -6157,6 +6493,18 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke Отзови @@ -6210,6 +6558,14 @@ chat item action Запази (и уведоми контактите) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact Запази и уведоми контакта @@ -6234,6 +6590,10 @@ chat item action Запази профила на групата No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6419,6 +6779,10 @@ chat item action Изпратете съобщение на живо - то ще се актуализира за получателя(ите), докато го пишете No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to Изпращайте потвърждениe за доставка на @@ -6479,6 +6843,14 @@ chat item action Изпращане на потвърждениe за доставка No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Изпрати от галерия или персонализирани клавиатури. @@ -6489,6 +6861,10 @@ chat item action Изпращане до последните 100 съобщения на нови членове. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. Подателят отмени прехвърлянето на файла. @@ -6616,13 +6992,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Сървърът изисква оторизация за създаване на опашки, проверете паролата server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Сървърът изисква оторизация за качване, проверете паролата server test error @@ -6689,6 +7065,10 @@ chat item action Задайте го вместо системната идентификация. No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -6708,6 +7088,10 @@ chat item action Задай парола за експортиране No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Задай съобщението, показано на новите членове! @@ -6774,6 +7158,14 @@ chat item action Сподели линк No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile No comment provided by engineer. @@ -6792,6 +7184,18 @@ chat item action Сподели с контактите No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -6889,6 +7293,11 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + Автоматично приемане на настройки + alert title + SimpleX channel link simplex link type @@ -6932,6 +7341,10 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode Опростен режим инкогнито @@ -7127,6 +7540,10 @@ report reason TCP connection No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout Времето на изчакване за установяване на TCP връзка @@ -7160,10 +7577,26 @@ report reason Направи снимка No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button Докосни бутона @@ -7249,6 +7682,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате. @@ -7306,6 +7743,10 @@ It can happen because of some bug or when the connection is compromised.Хешът на предишното съобщение е различен. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. Съобщението ще бъде изтрито за всички членове. @@ -7345,7 +7786,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified Подателят НЯМА да бъде уведомен - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7432,16 +7873,6 @@ It can happen because of some bug or when the connection is compromised.Тази група вече не съществува. No comment provided by engineer. - - This is your own SimpleX address! - Това е вашият личен SimpleX адрес! - No comment provided by engineer. - - - This is your own one-time link! - Това е вашят еднократен линк за връзка! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7459,6 +7890,14 @@ It can happen because of some bug or when the connection is compromised.Тази настройка се прилага за съобщения в текущия ви профил **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title No comment provided by engineer. @@ -7534,11 +7973,19 @@ You will be prompted to complete authentication before this feature is enabled.< To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. За поддръжка на незабавни push известия, базата данни за чат трябва да бъде мигрирана. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -7765,11 +8212,35 @@ To connect, please ask your contact to create another connection link and check Актуализирането на настройките ще свърже отново клиента към всички сървъри. No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat Актуализирай и отвори чата No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors No comment provided by engineer. @@ -7831,7 +8302,7 @@ To connect, please ask your contact to create another connection link and check Use current profile Използвай текущия профил - No comment provided by engineer. + new chat action Use for files @@ -7856,10 +8327,14 @@ To connect, please ask your contact to create another connection link and check Използвай интерфейса за повикване на iOS No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile Използвай нов инкогнито профил - No comment provided by engineer. + new chat action Use only local notifications? @@ -7883,10 +8358,6 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Използвайте приложението по време на разговора. @@ -8084,6 +8555,10 @@ To connect, please ask your contact to create another connection link and check Съобщението при посрещане е твърде дълго No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new Какво е новото @@ -8200,12 +8675,12 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. Вече се свързвате с %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Вече се свързвате чрез този еднократен линк за връзка! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8215,24 +8690,19 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. Вече се присъединявате към групата %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Вие вече се присъединявате към групата чрез този линк! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Вие вече се присъединявате към групата чрез този линк. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Вече се присъединихте към групата! Изпрати отново заявката за присъединяване? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8343,10 +8813,14 @@ Repeat join request? Можете да видите отново линкът за покана в подробностите за връзката. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! Не може да изпращате съобщения! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8358,17 +8832,12 @@ Repeat join request? Хората могат да се свържат с вас само чрез ликовете, които споделяте. No comment provided by engineer. - - You have already requested connection via this address! - Вече сте заявили връзка през този адрес! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Вече сте направили заявката за връзка! Изпрати отново заявката за свързване? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8426,6 +8895,10 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Ще бъдете свързани с групата, когато устройството на домакина на групата е онлайн, моля, изчакайте или проверете по-късно! @@ -8451,11 +8924,6 @@ Repeat connection request? Ще трябва да се идентифицирате, когато стартирате или възобновите приложението след 30 секунди във фонов режим. No comment provided by engineer. - - You will connect to all group members. - Ще се свържете с всички членове на групата. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Все още ще получавате обаждания и известия от заглушени профили, когато са активни. @@ -8495,6 +8963,10 @@ Repeat connection request? Вашият адрес в SimpleX No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls Вашите обаждания @@ -8519,8 +8991,16 @@ Repeat connection request? Вашите чат профили No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact No comment provided by engineer. @@ -8552,6 +9032,10 @@ Repeat connection request? Вашият текущ профил No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences Вашите настройки @@ -8635,6 +9119,10 @@ Repeat connection request? по-горе, след това избери: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call обаждането прието @@ -8644,6 +9132,10 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin админ @@ -8664,6 +9156,10 @@ Repeat connection request? съгласуване на криптиране… chat item text + + all + member criteria value + all members всички членове @@ -8747,6 +9243,10 @@ marked deleted chat item preview text повикване… call status + + can't send messages + No comment provided by engineer. + cancelled %@ отменен %@ @@ -8797,11 +9297,6 @@ marked deleted chat item preview text свързан No comment provided by engineer. - - connected directly - свързан директно - rcv group event chat item - connecting свързване @@ -8852,6 +9347,14 @@ marked deleted chat item preview text името на контакта %1$@ е променено на %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption контактът има e2e криптиране @@ -8862,6 +9365,14 @@ marked deleted chat item preview text контактът няма e2e криптиране No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator създател @@ -9025,11 +9536,19 @@ pref value препратено No comment provided by engineer. + + group + shown on group welcome message + group deleted групата е изтрита No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated профилът на групата е актуализиран @@ -9123,11 +9642,6 @@ pref value курсив No comment provided by engineer. - - join as %@ - присъединяване като %@ - No comment provided by engineer. - left напусна @@ -9153,6 +9667,10 @@ pref value свързан rcv group event chat item + + member has old version + No comment provided by engineer. + message No comment provided by engineer. @@ -9216,6 +9734,10 @@ pref value няма текст copied message info in history + + not synchronized + No comment provided by engineer. + observer наблюдател @@ -9226,6 +9748,7 @@ pref value изключено enabled status group pref value +member criteria value time to disappear @@ -9274,6 +9797,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption квантово устойчиво e2e криптиране @@ -9313,6 +9840,10 @@ time to disappear премахнат адрес за контакт profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture премахната профилна снимка @@ -9323,10 +9854,34 @@ time to disappear ви острани rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved запазено @@ -9361,11 +9916,6 @@ time to disappear кодът за сигурност е променен chat item text - - send direct message - изпрати лично съобщение - No comment provided by engineer. - server queue info: %1$@ @@ -9508,10 +10058,9 @@ last received msg: %2$@ вие No comment provided by engineer. - - you are invited to group - вие сте поканени в групата - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index bf7753675e..fbda1abd29 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -2597,8 +2597,8 @@ Polish interface No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -3073,12 +3073,12 @@ Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. server test error @@ -4351,7 +4351,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -4548,8 +4548,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 0400839cb0..244cbdf946 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -112,6 +112,7 @@ %@ uploaded + %@ nahrán No comment provided by engineer. @@ -126,6 +127,7 @@ %@, %@ and %lld members + %@, %@ a %lld členů No comment provided by engineer. @@ -170,6 +172,7 @@ %d messages not forwarded + %d zprávy nebyly přeposlány alert title @@ -189,6 +192,7 @@ %d seconds(s) + %d sekund delete after time @@ -247,6 +251,7 @@ %lld messages moderated by %@ + %lld zprávy moderované %@ No comment provided by engineer. @@ -311,6 +316,7 @@ (new) + (nový) No comment provided by engineer. @@ -320,10 +326,12 @@ **Create 1-time link**: to create and share a new invitation link. + **Vytvořit jednorázový odkaz**: pro vytvoření a sdílení nové pozvánky. No comment provided by engineer. **Create group**: to create a new group. + **Vytvořit skupinu**: pro vytvoření nové skupiny. No comment provided by engineer. @@ -352,6 +360,7 @@ **Scan / Paste link**: to connect via a link you received. + **Skenovat / Vložit odkaz**: pro připojení pomocí odkazu který jste obdrželi. No comment provided by engineer. @@ -361,6 +370,7 @@ **Warning**: the archive will be removed. + **Varování**: archiv bude odstraněn. No comment provided by engineer. @@ -400,6 +410,9 @@ - optionally notify deleted contacts. - profile names with spaces. - and more! + - volitelně informuje smazané kontakty. +- profilová jména s mezerami. +- a více! No comment provided by engineer. @@ -413,6 +426,7 @@ 0 sec + 0 sek time to disappear @@ -450,6 +464,7 @@ time interval 1 year + 1 rok delete after time @@ -458,6 +473,7 @@ time interval 1-time link can be used *with one contact only* - share in person or via any messenger. + Jednorázový odkaz lze použít *pouze s jedním kontaktem* - sdílejte osobně nebo prostřednictvím libovolného komunikátoru. No comment provided by engineer. @@ -531,6 +547,7 @@ time interval About operators + O operátorech No comment provided by engineer. @@ -542,10 +559,22 @@ time interval Přijmout accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Přijmout za člena + alert action + + + Accept as observer + Přijmout jako pozorovatele + alert action + Accept conditions + Přijmout podmínky No comment provided by engineer. @@ -553,6 +582,11 @@ swipe action Přijmout kontakt? No comment provided by engineer. + + Accept contact request + Přijmout žádost o kontakt + alert title + Accept contact request from %@? Přijmout žádost o kontakt od %@? @@ -561,11 +595,17 @@ swipe action Accept incognito Přijmout inkognito - accept contact request via notification + alert action swipe action + + Accept member + Přijmout člena + alert title + Accepted conditions + Přijaté podmínky No comment provided by engineer. @@ -578,10 +618,12 @@ swipe action Active + Aktivní token status text Active connections + Aktivní spojení No comment provided by engineer. @@ -591,12 +633,17 @@ swipe action Add friends + Přidat přátele No comment provided by engineer. Add list No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile Přidat profil @@ -614,6 +661,7 @@ swipe action Add team members + Přidat členy týmu No comment provided by engineer. @@ -623,6 +671,7 @@ swipe action Add to list + Přidat do seznamu No comment provided by engineer. @@ -632,6 +681,7 @@ swipe action Add your team members to the conversations. + Přidat členy týmu do konverzace. No comment provided by engineer. @@ -640,14 +690,17 @@ swipe action Added message servers + Přidané servery zpráv No comment provided by engineer. Additional accent + Další zbarvení No comment provided by engineer. Additional accent 2 + Další zbarvení 2 No comment provided by engineer. @@ -666,14 +719,17 @@ swipe action Address or 1-time link? + Adresa nebo jednorázový odkaz? No comment provided by engineer. Address settings + Nastavení adresy No comment provided by engineer. Admins can block a member for all. + Správci mohou blokovat člena pro všechny. No comment provided by engineer. @@ -688,10 +744,12 @@ swipe action Advanced settings + Pokročilá nastavení No comment provided by engineer. All + Vše No comment provided by engineer. @@ -706,6 +764,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. + Všechny chaty budou odstraněny ze seznamu %@ a seznam bude odstraněn. alert message @@ -715,6 +774,7 @@ swipe action All data is kept private on your device. + Všechna data jsou uchována ve vašem zařízení. No comment provided by engineer. @@ -737,10 +797,12 @@ swipe action All new messages from %@ will be hidden! + Všechny nové zprávy od %@ budou skryté! No comment provided by engineer. All profiles + Všechny profily profile dropdown @@ -749,6 +811,7 @@ swipe action All servers + Všechny servery No comment provided by engineer. @@ -777,6 +840,7 @@ swipe action Allow calls? + Povolit volání? No comment provided by engineer. @@ -788,6 +852,10 @@ swipe action Allow downgrade No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin) @@ -815,6 +883,7 @@ swipe action Allow sharing + Povolit sdílení No comment provided by engineer. @@ -824,10 +893,12 @@ swipe action Allow to report messsages to moderators. + Povolit nahlášení zpráv moderátorům. No comment provided by engineer. Allow to send SimpleX links. + Povolit odesílání SimpleX odkazů. No comment provided by engineer. @@ -870,6 +941,11 @@ swipe action Umožněte svým kontaktům odesílat mizící zprávy. No comment provided by engineer. + + Allow your contacts to send files and media. + Povolit vašim kontaktům odesílání souborů a médii. + No comment provided by engineer. + Allow your contacts to send voice messages. Povolte svým kontaktům odesílání hlasových zpráv. @@ -882,14 +958,17 @@ swipe action Already connecting! - No comment provided by engineer. + Již připojováno! + new chat sheet title Already joining the group! - No comment provided by engineer. + Již se ke skupině připojujete! + new chat sheet title Always use private routing. + Vždy používat soukromé směrování. No comment provided by engineer. @@ -904,6 +983,7 @@ swipe action Another reason + Jiný důvod report reason @@ -923,6 +1003,7 @@ swipe action App data migration + Přenos dat aplikace No comment provided by engineer. @@ -970,6 +1051,7 @@ swipe action Apply + Použít No comment provided by engineer. @@ -978,6 +1060,7 @@ swipe action Archive + Archiv No comment provided by engineer. @@ -986,10 +1069,12 @@ swipe action Archive all reports? + Archivovat všechny hlášení? No comment provided by engineer. Archive and upload + Archivovat a nahrát No comment provided by engineer. @@ -1010,6 +1095,7 @@ swipe action Archived contacts + Archivované kontakty No comment provided by engineer. @@ -1076,10 +1162,6 @@ swipe action Automaticky přijímat obrázky No comment provided by engineer. - - Auto-accept settings - alert title - Back Zpět @@ -1087,6 +1169,7 @@ swipe action Background + Pozadí No comment provided by engineer. @@ -1105,18 +1188,22 @@ swipe action Better calls + Lepší volání No comment provided by engineer. Better groups + Lepší skupiny No comment provided by engineer. Better groups performance + Lepší výkon skupin No comment provided by engineer. Better message dates. + Lepší datumy zpráv. No comment provided by engineer. @@ -1126,24 +1213,36 @@ swipe action Better networking + Lepší sítě No comment provided by engineer. Better notifications + Lepší upozornění No comment provided by engineer. Better privacy and security + Lepší soukromí a zabezpečení No comment provided by engineer. Better security ✅ + Lepší zabezpečení ✅ No comment provided by engineer. Better user experience No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black No comment provided by engineer. @@ -1162,6 +1261,7 @@ swipe action Block member + Blokovat člena No comment provided by engineer. @@ -1170,10 +1270,12 @@ swipe action Block member? + Blokovat člena? No comment provided by engineer. Blocked by admin + Blokován správcem No comment provided by engineer. @@ -1182,6 +1284,11 @@ swipe action Blur media + Rozmazat média + No comment provided by engineer. + + + Bot No comment provided by engineer. @@ -1204,6 +1311,11 @@ swipe action Vy i váš kontakt můžete posílat mizící zprávy. No comment provided by engineer. + + Both you and your contact can send files and media. + Vy i vaše kontakty můžete posílat soubory a média. + No comment provided by engineer. + Both you and your contact can send voice messages. Hlasové zprávy můžete posílat vy i váš kontakt. @@ -1216,12 +1328,17 @@ swipe action Business address + Obchodní adresa No comment provided by engineer. Business chats No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses No comment provided by engineer. @@ -1249,20 +1366,29 @@ swipe action Calls prohibited! + Volání zakázáno! No comment provided by engineer. Camera not available + Kamera není k dispozici No comment provided by engineer. Can't call contact + Kontaktu nelze volat No comment provided by engineer. Can't call member + Členu nelze volat No comment provided by engineer. + + Can't change profile + Nelze změnit profil + alert title + Can't invite contact! Nelze pozvat kontakt! @@ -1275,16 +1401,19 @@ swipe action Can't message member + Členu nelze poslat zprávu No comment provided by engineer. Cancel Zrušit alert action -alert button +alert button +new chat action Cancel migration + Zrušit přesun No comment provided by engineer. @@ -1294,6 +1423,7 @@ alert button Cannot forward message + Zprávu nelze přeposlat No comment provided by engineer. @@ -1303,10 +1433,12 @@ alert button Capacity exceeded - recipient did not receive previously sent messages. + Kapacita překročena - příjemce neobdrží dříve poslané zprávy. snd error text Cellular + Mobilní No comment provided by engineer. @@ -1316,10 +1448,12 @@ alert button Change automatic message deletion? + Změnit automatické mazání zpráv? alert title Change chat profiles + Změnit chat profily authentication reason @@ -1378,10 +1512,11 @@ set passcode view Chat already exists! - No comment provided by engineer. + new chat sheet title Chat colors + Barvy chatu No comment provided by engineer. @@ -1401,6 +1536,7 @@ set passcode view Chat database exported + Chat databáze exportována No comment provided by engineer. @@ -1428,6 +1564,7 @@ set passcode view Chat migrated! + Chat přesunut! No comment provided by engineer. @@ -1456,11 +1593,27 @@ set passcode view Chat will be deleted for you - this cannot be undone! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats Chaty No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1643,6 +1796,7 @@ set passcode view Confirm upload + Potvrdit nahrání No comment provided by engineer. @@ -1656,11 +1810,11 @@ set passcode view Connect automatically + Připojit automaticky No comment provided by engineer. - - Connect incognito - Spojit se inkognito + + Connect faster! 🚀 No comment provided by engineer. @@ -1671,37 +1825,35 @@ set passcode view Connect to your friends faster. No comment provided by engineer. - - Connect to yourself? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! - No comment provided by engineer. + Připojit se k sobě? +Toto je váš vlastní jednorázový odkaz! + new chat sheet title Connect via contact address - No comment provided by engineer. + new chat sheet title Connect via link Připojte se prostřednictvím odkazu - No comment provided by engineer. + new chat sheet title Connect via one-time link Připojit se jednorázovým odkazem - No comment provided by engineer. + new chat sheet title Connect with %@ - No comment provided by engineer. + new chat action Connected @@ -1757,7 +1909,7 @@ This is your own one-time link! Connection error Chyba připojení - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1797,7 +1949,7 @@ This is your own one-time link! Connection timeout Časový limit připojení - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1845,6 +1997,10 @@ This is your own one-time link! Předvolby kontaktů No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! No comment provided by engineer. @@ -1950,9 +2106,8 @@ This is your own one-time link! Vytvořit frontu server test step - - Create secret group - Vytvořit tajnou skupinu + + Create your address No comment provided by engineer. @@ -2193,6 +2348,10 @@ swipe action Smazat chat profil? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? No comment provided by engineer. @@ -2377,11 +2536,19 @@ swipe action Potvrzení o doručení! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Popis No comment provided by engineer. + + Description too large + alert title + Desktop address No comment provided by engineer. @@ -2584,7 +2751,7 @@ swipe action Don't show again Znovu neukazuj - No comment provided by engineer. + alert action Done @@ -2657,6 +2824,10 @@ chat item action Upravit profil skupiny No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable Zapnout @@ -2690,6 +2861,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Povolit pro všechny @@ -2878,6 +3053,10 @@ chat item action Chyba při přijímání žádosti o kontakt No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) Chyba přidávání člena(ů) @@ -2887,11 +3066,19 @@ chat item action Error adding server alert title + + Error adding short link + No comment provided by engineer. + Error changing address Chuba změny adresy No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile No comment provided by engineer. @@ -2904,7 +3091,7 @@ chat item action Error changing setting Chyba změny nastavení - No comment provided by engineer. + alert title Error changing to incognito! @@ -2916,7 +3103,7 @@ chat item action Error connecting to forwarding server %@. Please try later. - No comment provided by engineer. + alert message Error creating address @@ -2960,15 +3147,19 @@ chat item action Chyba dešifrování souboru No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database Chyba při mazání databáze chatu - No comment provided by engineer. + alert title Error deleting chat! Chyba při mazání chatu! - No comment provided by engineer. + alert title Error deleting connection @@ -2978,12 +3169,12 @@ chat item action Error deleting database Chyba při mazání databáze - No comment provided by engineer. + alert title Error deleting old database Chyba při mazání staré databáze - No comment provided by engineer. + alert title Error deleting token @@ -3017,7 +3208,7 @@ chat item action Error exporting chat database Chyba při exportu databáze chatu - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3026,7 +3217,7 @@ chat item action Error importing chat database Chyba při importu databáze chatu - No comment provided by engineer. + alert title Error joining group @@ -3045,6 +3236,10 @@ chat item action Error opening chat No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file Chyba při příjmu souboru @@ -3062,10 +3257,14 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + alert title + Error removing member Chyba při odebrání člena - No comment provided by engineer. + alert title Error reordering lists @@ -3131,6 +3330,10 @@ chat item action Chyba při odesílání zprávy No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Chyba nastavování potvrzení o doručení! @@ -3148,7 +3351,7 @@ chat item action Error switching profile - No comment provided by engineer. + alert title Error switching profile! @@ -3208,6 +3411,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Chyba: Adresa URL je neplatná @@ -3367,6 +3574,10 @@ snd error text Soubory a média chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Soubory a média jsou zakázány v této skupině. @@ -3404,6 +3615,23 @@ snd error text Najděte chaty rychleji No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Je možné, že otisk certifikátu v adrese serveru je nesprávný + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Opravit @@ -3500,8 +3728,8 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3582,7 +3810,7 @@ Error: %2$@ Group already exists! - No comment provided by engineer. + new chat sheet title Group display name @@ -3649,6 +3877,10 @@ Error: %2$@ Profil skupiny je uložen v zařízeních členů, nikoli na serverech. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Uvítací zpráva skupin @@ -4002,7 +4234,7 @@ More improvements are coming soon! Invalid link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4110,32 +4342,29 @@ More improvements are coming soon! Připojte se na swipe action + + Join as %@ + připojit se jako %@ + No comment provided by engineer. + Join group Připojit ke skupině - No comment provided by engineer. + new chat sheet title Join group conversations No comment provided by engineer. - - Join group? - No comment provided by engineer. - Join incognito Připojit se inkognito No comment provided by engineer. - - Join with current profile - No comment provided by engineer. - Join your group? This is your link for group %@! - No comment provided by engineer. + new chat action Joining group @@ -4158,6 +4387,10 @@ This is your link for group %@! Keep unused invitation? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections Zachovat vaše připojení @@ -4211,6 +4444,10 @@ This is your link for group %@! Opustit skupinu? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Promluvme si v SimpleX Chatu @@ -4260,6 +4497,10 @@ This is your link for group %@! Živé zprávy No comment provided by engineer. + + Loading profile… + in progress text + Local name Místní název @@ -4333,10 +4574,22 @@ This is your link for group %@! Člen No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4364,6 +4617,10 @@ This is your link for group %@! Člen bude odstraněn ze skupiny - toto nelze vzít zpět! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. Členové skupin mohou přidávat reakce na zprávy. @@ -4433,6 +4690,10 @@ This is your link for group %@! Message forwarded item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. item status description @@ -4499,6 +4760,10 @@ This is your link for group %@! Zprávy No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! No comment provided by engineer. @@ -4725,6 +4990,10 @@ This is your link for group %@! New events notification + + New group role: Moderator + No comment provided by engineer. + New in %@ Nový V %@ @@ -4739,6 +5008,10 @@ This is your link for group %@! Nová role člena No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message Nová zpráva @@ -4775,6 +5048,10 @@ This is your link for group %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected Nebyl vybrán žádný kontakt @@ -4847,6 +5124,10 @@ This is your link for group %@! Nemáte oprávnění nahrávat hlasové zprávy No comment provided by engineer. + + No private routing session + alert title + No push server Místní @@ -4945,7 +5226,9 @@ This is your link for group %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5032,6 +5315,10 @@ Vyžaduje povolení sítě VPN. Mizící zprávy můžete odesílat pouze vy. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Hlasové zprávy můžete posílat pouze vy. @@ -5057,6 +5344,10 @@ Vyžaduje povolení sítě VPN. Zmizelé zprávy může odesílat pouze váš kontakt. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Hlasové zprávy může odesílat pouze váš kontakt. @@ -5079,20 +5370,28 @@ Vyžaduje povolení sítě VPN. Open chat Otevřete chat - No comment provided by engineer. + new chat action Open chat console Otevřete konzolu chatu authentication reason + + Open clean link + alert action + Open conditions No comment provided by engineer. + + Open full link + alert action + Open group - No comment provided by engineer. + new chat action Open link? @@ -5102,6 +5401,30 @@ Vyžaduje povolení sítě VPN. Open migration to another device authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… No comment provided by engineer. @@ -5195,10 +5518,6 @@ Vyžaduje povolení sítě VPN. Heslo k zobrazení No comment provided by engineer. - - Past member %@ - past/unknown group member - Paste desktop address No comment provided by engineer. @@ -5260,7 +5579,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. Zkontrolujte síťové připojení pomocí %@ a zkuste to znovu. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5320,6 +5639,10 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5337,11 +5660,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Je možné, že otisk certifikátu v adrese serveru je nesprávný - server test error - Preserve the last message draft, with attachments. Zachování posledního návrhu zprávy s přílohami. @@ -5414,7 +5732,11 @@ Error: %@ Private routing error - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5511,6 +5833,10 @@ Enable in *Network & servers* settings. Chraňte své chat profily heslem! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout Časový limit protokolu @@ -5718,7 +6044,8 @@ Enable in *Network & servers* settings. Reject Odmítnout - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -5729,7 +6056,11 @@ swipe action Reject contact request Odmítnout žádost o kontakt - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -5754,6 +6085,10 @@ swipe action Remove image No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Odstranit člena @@ -5769,6 +6104,10 @@ swipe action Odstranit přístupovou frázi z klíčenek? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Znovu vyjednat @@ -5784,10 +6123,6 @@ swipe action Znovu vyjednat šifrování? No comment provided by engineer. - - Repeat connection request? - No comment provided by engineer. - Repeat download No comment provided by engineer. @@ -5796,10 +6131,6 @@ swipe action Repeat import No comment provided by engineer. - - Repeat join request? - No comment provided by engineer. - Repeat upload No comment provided by engineer. @@ -5829,6 +6160,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -5921,7 +6256,7 @@ swipe action Retry - No comment provided by engineer. + alert action Reveal @@ -5932,6 +6267,18 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke Odvolat @@ -5984,6 +6331,14 @@ chat item action Uložit (a informovat kontakty) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact Uložit a upozornit kontakt @@ -6008,6 +6363,10 @@ chat item action Uložení profilu skupiny No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6187,6 +6546,10 @@ chat item action Poslat živou zprávu - zpráva se bude aktualizovat pro příjemce během psaní No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to Potvrzení o doručení zasílat na @@ -6247,6 +6610,14 @@ chat item action Odeslat potvrzení No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Odeslat je z galerie nebo vlastní klávesnice. @@ -6256,6 +6627,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. Odesílatel zrušil přenos souboru. @@ -6383,13 +6758,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Server vyžaduje autorizaci pro nahrávání, zkontrolujte heslo server test error @@ -6455,6 +6830,10 @@ chat item action Nastavte jej namísto ověřování systému. No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -6473,6 +6852,10 @@ chat item action Nastavení přístupové fráze pro export No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Nastavte zprávu zobrazenou novým členům! @@ -6538,6 +6921,14 @@ chat item action Sdílet odkaz No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile No comment provided by engineer. @@ -6555,6 +6946,18 @@ chat item action Sdílet s kontakty No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -6651,6 +7054,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + alert title + SimpleX channel link simplex link type @@ -6692,6 +7099,10 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode Zjednodušený inkognito režim @@ -6883,6 +7294,10 @@ report reason TCP connection No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout Časový limit připojení TCP @@ -6916,10 +7331,26 @@ report reason Vyfotit No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button Klepněte na tlačítko @@ -7002,6 +7433,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení. @@ -7058,6 +7493,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Hash předchozí zprávy se liší. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. Zpráva bude smazána pro všechny členy. @@ -7097,7 +7536,7 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The sender will NOT be notified Odesílatel NEBUDE informován - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7179,14 +7618,6 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Tato skupina již neexistuje. No comment provided by engineer. - - This is your own SimpleX address! - No comment provided by engineer. - - - This is your own one-time link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7204,6 +7635,14 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Toto nastavení platí pro zprávy ve vašem aktuálním chat profilu **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title No comment provided by engineer. @@ -7278,11 +7717,19 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. Pro podporu doručování okamžitých upozornění musí být přenesena chat databáze. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -7500,11 +7947,35 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Aktualizací nastavení se klient znovu připojí ke všem serverům. No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat Zvýšit a otevřít chat No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors No comment provided by engineer. @@ -7564,7 +8035,7 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use current profile Použít aktuální profil - No comment provided by engineer. + new chat action Use for files @@ -7588,10 +8059,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít rozhraní volání iOS No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile Použít nový inkognito profil - No comment provided by engineer. + new chat action Use only local notifications? @@ -7614,10 +8089,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. No comment provided by engineer. @@ -7803,6 +8274,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Welcome message is too long No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new Co je nového @@ -7911,11 +8386,11 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu You are already connecting to %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -7923,20 +8398,16 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu You are already joining the group %@. - No comment provided by engineer. - - - You are already joining the group via this link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8044,10 +8515,14 @@ Repeat join request? You can view invitation link again in connection details. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! Nemůžete posílat zprávy! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8059,14 +8534,10 @@ Repeat join request? Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte. No comment provided by engineer. - - You have already requested connection via this address! - No comment provided by engineer. - You have already requested connection! Repeat connection request? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8124,6 +8595,10 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později! @@ -8148,10 +8623,6 @@ Repeat connection request? Při spuštění nebo obnovení aplikace po 30 sekundách na pozadí budete požádáni o ověření. No comment provided by engineer. - - You will connect to all group members. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Stále budete přijímat volání a upozornění od umlčených profilů pokud budou aktivní. @@ -8191,6 +8662,10 @@ Repeat connection request? Vaše SimpleX adresa No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls Vaše hovory @@ -8215,8 +8690,16 @@ Repeat connection request? Vaše chat profily No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact No comment provided by engineer. @@ -8248,6 +8731,10 @@ Repeat connection request? Váš současný profil No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences Vaše preference @@ -8330,6 +8817,10 @@ Repeat connection request? výše, pak vyberte: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call přijatý hovor @@ -8339,6 +8830,10 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin správce @@ -8358,6 +8853,10 @@ Repeat connection request? povoluji šifrování… chat item text + + all + member criteria value + all members feature role @@ -8435,6 +8934,10 @@ marked deleted chat item preview text volání… call status + + can't send messages + No comment provided by engineer. + cancelled %@ zrušeno %@ @@ -8485,11 +8988,6 @@ marked deleted chat item preview text připojeno No comment provided by engineer. - - connected directly - připojeno přímo - rcv group event chat item - connecting připojování @@ -8539,6 +9037,14 @@ marked deleted chat item preview text contact %1$@ changed to %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption kontakt má šifrování e2e @@ -8549,6 +9055,14 @@ marked deleted chat item preview text kontakt nemá šifrování e2e No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator tvůrce @@ -8710,11 +9224,19 @@ pref value forwarded No comment provided by engineer. + + group + shown on group welcome message + group deleted skupina smazána No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated profil skupiny aktualizován @@ -8808,11 +9330,6 @@ pref value kurzíva No comment provided by engineer. - - join as %@ - připojit se jako %@ - No comment provided by engineer. - left opustil @@ -8837,6 +9354,10 @@ pref value připojeno rcv group event chat item + + member has old version + No comment provided by engineer. + message No comment provided by engineer. @@ -8900,6 +9421,10 @@ pref value žádný text copied message info in history + + not synchronized + No comment provided by engineer. + observer pozorovatel @@ -8910,6 +9435,7 @@ pref value vypnuto enabled status group pref value +member criteria value time to disappear @@ -8957,6 +9483,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -8994,6 +9524,10 @@ time to disappear removed contact address profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture profile update event chat item @@ -9003,10 +9537,34 @@ time to disappear odstranil vás rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved No comment provided by engineer. @@ -9039,11 +9597,6 @@ time to disappear bezpečnostní kód změněn chat item text - - send direct message - odeslat přímou zprávu - No comment provided by engineer. - server queue info: %1$@ @@ -9178,10 +9731,9 @@ last received msg: %2$@ you No comment provided by engineer. - - you are invited to group - jste pozváni do skupiny - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 06fd7c5a1d..e6131264af 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -563,8 +563,19 @@ time interval Annehmen accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Als Mitglied übernehmen + alert action + + + Accept as observer + Als Beobachter übernehmen + alert action + Accept conditions Nutzungsbedingungen akzeptieren @@ -575,6 +586,11 @@ swipe action Kontaktanfrage annehmen? No comment provided by engineer. + + Accept contact request + Kontaktanfrage annehmen + alert title + Accept contact request from %@? Die Kontaktanfrage von %@ annehmen? @@ -583,9 +599,14 @@ swipe action Accept incognito Inkognito akzeptieren - accept contact request via notification + alert action swipe action + + Accept member + Mitglied annehmen + alert title + Accepted conditions Akzeptierte Nutzungsbedingungen @@ -626,6 +647,11 @@ swipe action Liste hinzufügen No comment provided by engineer. + + Add message + Nachricht hinzufügen + placeholder for sending contact request + Add profile Profil hinzufügen @@ -841,6 +867,11 @@ swipe action Herabstufung erlauben No comment provided by engineer. + + Allow files and media only if your contact allows them. + Erlauben Sie Dateien und Medien nur dann, wenn es Ihr Kontakt ebenfalls erlaubt. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden) @@ -903,7 +934,7 @@ swipe action Allow voice messages? - Sprachnachricht erlauben? + Sprachnachrichten erlauben? No comment provided by engineer. @@ -926,6 +957,11 @@ swipe action Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten. No comment provided by engineer. + + Allow your contacts to send files and media. + Erlauben Sie Ihren Kontakten Dateien und Medien zu senden. + No comment provided by engineer. + Allow your contacts to send voice messages. Erlauben Sie Ihren Kontakten Sprachnachrichten zu senden. @@ -939,12 +975,12 @@ swipe action Already connecting! Bereits verbunden! - No comment provided by engineer. + new chat sheet title Already joining the group! Sie sind bereits Mitglied der Gruppe! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1151,11 +1187,6 @@ swipe action Bilder automatisch akzeptieren No comment provided by engineer. - - Auto-accept settings - Einstellungen automatisch akzeptieren - alert title - Back Zurück @@ -1231,6 +1262,16 @@ swipe action Verbesserte Nutzer-Erfahrung No comment provided by engineer. + + Bio + Biografie + No comment provided by engineer. + + + Bio too large + Biografie zu lang + alert title + Black Schwarz @@ -1278,7 +1319,12 @@ swipe action Blur media - Medium verpixeln + Medien verpixeln + No comment provided by engineer. + + + Bot + Bot No comment provided by engineer. @@ -1301,6 +1347,11 @@ swipe action Ihr Kontakt und Sie können beide verschwindende Nachrichten senden. No comment provided by engineer. + + Both you and your contact can send files and media. + Sowohl Sie, als auch Ihr Kontakt können Dateien und Medien senden. + No comment provided by engineer. + Both you and your contact can send voice messages. Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden. @@ -1321,6 +1372,11 @@ swipe action Geschäftliche Chats No comment provided by engineer. + + Business connection + Geschäftliche Verbindung + No comment provided by engineer. + Businesses Unternehmen @@ -1370,6 +1426,11 @@ swipe action Mitglied kann nicht angerufen werden No comment provided by engineer. + + Can't change profile + Änderung des Profils nicht möglich + alert title + Can't invite contact! Kontakt kann nicht eingeladen werden! @@ -1389,7 +1450,8 @@ swipe action Cancel Abbrechen alert action -alert button +alert button +new chat action Cancel migration @@ -1495,7 +1557,7 @@ set passcode view Chat already exists! Chat besteht bereits! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1582,11 +1644,31 @@ set passcode view Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. + + Chat with admins + Chat mit Administratoren + chat toolbar + + + Chat with member + Chat mit einem Mitglied + No comment provided by engineer. + + + Chat with members before they join. + Mit Mitgliedern chatten bevor sie beitreten. + No comment provided by engineer. + Chats Chats No comment provided by engineer. + + Chats with members + Chats mit Mitgliedern + No comment provided by engineer. + Check messages every 20 min. Alle 20min Nachrichten überprüfen. @@ -1812,9 +1894,9 @@ set passcode view Automatisch verbinden No comment provided by engineer. - - Connect incognito - Inkognito verbinden + + Connect faster! 🚀 + Schneller miteinander verbinden! 🚀 No comment provided by engineer. @@ -1827,44 +1909,39 @@ set passcode view Schneller mit Ihren Freunden verbinden. No comment provided by engineer. - - Connect to yourself? - Mit Ihnen selbst verbinden? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Sich mit Ihnen selbst verbinden? Das ist Ihre eigene SimpleX-Adresse! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Mit Ihnen selbst verbinden? Das ist Ihr eigener Einmal-Link! - No comment provided by engineer. + new chat sheet title Connect via contact address Über die Kontakt-Adresse verbinden - No comment provided by engineer. + new chat sheet title Connect via link Über einen Link verbinden - No comment provided by engineer. + new chat sheet title Connect via one-time link Über einen Einmal-Link verbinden - No comment provided by engineer. + new chat sheet title Connect with %@ Mit %@ verbinden - No comment provided by engineer. + new chat action Connected @@ -1929,7 +2006,7 @@ Das ist Ihr eigener Einmal-Link! Connection error Verbindungsfehler - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1976,7 +2053,7 @@ Das ist Ihr eigener Einmal-Link! Connection timeout Verbindungszeitüberschreitung - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2028,6 +2105,11 @@ Das ist Ihr eigener Einmal-Link! Kontakt-Präferenzen No comment provided by engineer. + + Contact requests from groups + KONTAKTANFRAGEN VON GRUPPEN + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Kontakt wird gelöscht. Dies kann nicht rückgängig gemacht werden! @@ -2143,9 +2225,9 @@ Das ist Ihr eigener Einmal-Link! Erzeuge Warteschlange server test step - - Create secret group - Geheime Gruppe erstellen + + Create your address + Ihre Adresse erstellen No comment provided by engineer. @@ -2402,6 +2484,11 @@ swipe action Chat-Profil löschen? No comment provided by engineer. + + Delete chat with member? + Chat mit dem Mitglied löschen? + alert title + Delete chat? Chat löschen? @@ -2597,11 +2684,21 @@ swipe action Empfangsbestätigungen! No comment provided by engineer. + + Deprecated options + Veraltete Optionen + No comment provided by engineer. + Description Beschreibung No comment provided by engineer. + + Description too large + Beschreibung zu lang + alert title + Desktop address Desktop-Adresse @@ -2825,7 +2922,7 @@ swipe action Don't show again Nicht nochmals anzeigen - No comment provided by engineer. + alert action Done @@ -2908,6 +3005,11 @@ chat item action Gruppenprofil bearbeiten No comment provided by engineer. + + Empty message! + Leere Nachricht! + No comment provided by engineer. + Enable Aktivieren @@ -2943,6 +3045,11 @@ chat item action Kamera-Zugriff aktivieren No comment provided by engineer. + + Enable disappearing messages by default. + Verschwindende Nachrichten sind per Voreinstellung aktiviert. + No comment provided by engineer. + Enable for all Für Alle aktivieren @@ -3143,6 +3250,11 @@ chat item action Fehler beim Annehmen der Kontaktanfrage No comment provided by engineer. + + Error accepting member + Fehler beim Annehmen des Mitglieds + alert title + Error adding member(s) Fehler beim Hinzufügen von Mitgliedern @@ -3153,11 +3265,21 @@ chat item action Fehler beim Hinzufügen des Servers alert title + + Error adding short link + Fehler beim Hinzufügen eines verkürzten Links + No comment provided by engineer. + Error changing address Fehler beim Wechseln der Empfängeradresse No comment provided by engineer. + + Error changing chat profile + Fehler beim Wechseln des Chat-Profils + alert title + Error changing connection profile Fehler beim Wechseln des Verbindungs-Profils @@ -3171,7 +3293,7 @@ chat item action Error changing setting Fehler beim Ändern der Einstellung - No comment provided by engineer. + alert title Error changing to incognito! @@ -3186,7 +3308,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut. - No comment provided by engineer. + alert message Error creating address @@ -3233,15 +3355,20 @@ chat item action Fehler beim Entschlüsseln der Datei No comment provided by engineer. + + Error deleting chat + Fehler beim Löschen des Chats mit dem Mitglied + alert title + Error deleting chat database Fehler beim Löschen der Chat-Datenbank - No comment provided by engineer. + alert title Error deleting chat! Fehler beim Löschen des Chats! - No comment provided by engineer. + alert title Error deleting connection @@ -3251,12 +3378,12 @@ chat item action Error deleting database Fehler beim Löschen der Datenbank - No comment provided by engineer. + alert title Error deleting old database Fehler beim Löschen der alten Datenbank - No comment provided by engineer. + alert title Error deleting token @@ -3291,7 +3418,7 @@ chat item action Error exporting chat database Fehler beim Exportieren der Chat-Datenbank - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3301,7 +3428,7 @@ chat item action Error importing chat database Fehler beim Importieren der Chat-Datenbank - No comment provided by engineer. + alert title Error joining group @@ -3320,7 +3447,12 @@ chat item action Error opening chat - Fehler beim Öffnen des Chats + Fehler beim Öffnen des Chat + No comment provided by engineer. + + + Error opening group + Fehler beim Vorbereiten der Gruppe No comment provided by engineer. @@ -3343,10 +3475,15 @@ chat item action Fehler beim Registrieren für Benachrichtigungen alert title + + Error rejecting contact request + Fehler bei der Ablehnung der Kontaktanfrage + alert title + Error removing member Fehler beim Entfernen des Mitglieds - No comment provided by engineer. + alert title Error reordering lists @@ -3418,6 +3555,11 @@ chat item action Fehler beim Senden der Nachricht No comment provided by engineer. + + Error setting auto-accept + Fehler bei der Einstellung des automatischen Akzeptierens + No comment provided by engineer. + Error setting delivery receipts! Fehler beim Setzen von Empfangsbestätigungen! @@ -3436,7 +3578,7 @@ chat item action Error switching profile Fehler beim Wechseln des Profils - No comment provided by engineer. + alert title Error switching profile! @@ -3500,6 +3642,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Fehler: URL ist ungültig @@ -3679,6 +3825,11 @@ snd error text Dateien und Medien chat feature + + Files and media are prohibited in this chat. + In diesem Chat sind Dateien und Medien nicht erlaubt. + No comment provided by engineer. + Files and media are prohibited. In dieser Gruppe sind Dateien und Medien nicht erlaubt. @@ -3719,6 +3870,23 @@ snd error text Chats schneller finden No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Reparieren @@ -3830,9 +3998,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Weiterleitungsserver %1$@ konnte sich nicht mit dem Zielserver %2$@ verbinden. Bitte versuchen Sie es später erneut. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3926,7 +4094,7 @@ Fehler: %2$@ Group already exists! Die Gruppe besteht bereits! - No comment provided by engineer. + new chat sheet title Group display name @@ -3993,6 +4161,11 @@ Fehler: %2$@ Das Gruppenprofil wird nur auf den Mitglieds-Systemen gespeichert und nicht auf den Servern. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Das Gruppenprofil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an die Gruppenmitglieder gesendet. + alert message + Group welcome message Gruppen-Begrüßungsmeldung @@ -4375,7 +4548,7 @@ Weitere Verbesserungen sind bald verfügbar! Invalid link Ungültiger Link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4488,37 +4661,32 @@ Weitere Verbesserungen sind bald verfügbar! Beitreten swipe action + + Join as %@ + Als %@ beitreten + No comment provided by engineer. + Join group Treten Sie der Gruppe bei - No comment provided by engineer. + new chat sheet title Join group conversations Gruppenunterhaltungen beitreten No comment provided by engineer. - - Join group? - Der Gruppe beitreten? - No comment provided by engineer. - Join incognito Inkognito beitreten No comment provided by engineer. - - Join with current profile - Mit dem aktuellen Profil beitreten - No comment provided by engineer. - Join your group? This is your link for group %@! Ihrer Gruppe beitreten? Das ist Ihr Link für die Gruppe %@! - No comment provided by engineer. + new chat action Joining group @@ -4545,6 +4713,11 @@ Das ist Ihr Link für die Gruppe %@! Nicht genutzte Einladung behalten? alert title + + Keep your chats clean + Ihre Chats übersichtlich halten + No comment provided by engineer. + Keep your connections Ihre Verbindungen beibehalten @@ -4600,6 +4773,11 @@ Das ist Ihr Link für die Gruppe %@! Die Gruppe verlassen? No comment provided by engineer. + + Less traffic on mobile networks. + Weniger Datenverkehr in mobilen Netzen. + No comment provided by engineer. + Let's talk in SimpleX Chat Lassen Sie uns in SimpleX Chat kommunizieren @@ -4655,6 +4833,11 @@ Das ist Ihr Link für die Gruppe %@! Live Nachrichten No comment provided by engineer. + + Loading profile… + Profil wird geladen… + in progress text + Local name Lokaler Name @@ -4730,11 +4913,26 @@ Das ist Ihr Link für die Gruppe %@! Mitglied No comment provided by engineer. + + Member %@ + Mitglied %@ + past/unknown group member + + + Member admission + Aufnahme von Mitgliedern + No comment provided by engineer. + Member inactive Mitglied inaktiv item status text + + Member is deleted - can't accept request + Mitglied ist gelöscht - Anfrage kann nicht angenommen werden + No comment provided by engineer. + Member reports Mitglieder-Meldungen @@ -4765,6 +4963,11 @@ Das ist Ihr Link für die Gruppe %@! Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. + + Member will join the group, accept member? + Mitglied wird der Gruppe beitreten. Annehmen? + alert message + Members can add message reactions. Gruppenmitglieder können eine Reaktion auf Nachrichten geben. @@ -4840,6 +5043,11 @@ Das ist Ihr Link für die Gruppe %@! Nachricht weitergeleitet item status text + + Message instantly once you tap Connect. + Sobald Sie auf Verbinden tippen, erhalten Sie sofort eine Nachricht. + No comment provided by engineer. + Message may be delivered later if member becomes active. Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird. @@ -4847,7 +5055,7 @@ Das ist Ihr Link für die Gruppe %@! Message queue info - Nachrichten-Warteschlangen-Information + Information Nachrichtenwarteschlange No comment provided by engineer. @@ -4915,6 +5123,11 @@ Das ist Ihr Link für die Gruppe %@! Nachrichten No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Nachrichten sind durch **Ende-zu-Ende-Verschlüsselung** geschützt. + No comment provided by engineer. + Messages from %@ will be shown! Die Nachrichten von %@ werden angezeigt! @@ -5170,6 +5383,11 @@ Das ist Ihr Link für die Gruppe %@! Neue Ereignisse notification + + New group role: Moderator + Neue Gruppen-Rolle: Moderator + No comment provided by engineer. + New in %@ Neu in %@ @@ -5185,6 +5403,11 @@ Das ist Ihr Link für die Gruppe %@! Neue Mitgliedsrolle No comment provided by engineer. + + New member wants to join the group. + Ein neues Mitglied will der Gruppe beitreten. + rcv group event chat item + New message Neue Nachricht @@ -5225,6 +5448,11 @@ Das ist Ihr Link für die Gruppe %@! Keine Chats in der Liste %@ No comment provided by engineer. + + No chats with members + Keine Chats mit Mitgliedern + No comment provided by engineer. + No contacts selected Keine Kontakte ausgewählt @@ -5305,6 +5533,11 @@ Das ist Ihr Link für die Gruppe %@! Keine Berechtigung für das Aufnehmen von Sprachnachrichten No comment provided by engineer. + + No private routing session + Keine private Routing-Sitzung + alert title + No push server Lokal @@ -5417,7 +5650,9 @@ Das ist Ihr Link für die Gruppe %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5508,6 +5743,11 @@ Dies erfordert die Aktivierung eines VPNs. Nur Sie können verschwindende Nachrichten senden. No comment provided by engineer. + + Only you can send files and media. + Nur Sie können Dateien und Medien senden. + No comment provided by engineer. + Only you can send voice messages. Nur Sie können Sprachnachrichten versenden. @@ -5533,6 +5773,11 @@ Dies erfordert die Aktivierung eines VPNs. Nur Ihr Kontakt kann verschwindende Nachrichten senden. No comment provided by engineer. + + Only your contact can send files and media. + Nur Ihr Kontakt kann Dateien und Medien senden. + No comment provided by engineer. + Only your contact can send voice messages. Nur Ihr Kontakt kann Sprachnachrichten versenden. @@ -5556,25 +5801,36 @@ Dies erfordert die Aktivierung eines VPNs. Open chat Chat öffnen - No comment provided by engineer. + new chat action Open chat console Chat-Konsole öffnen authentication reason + + Open clean link + Trackingfreien Link öffnen + alert action + Open conditions Nutzungsbedingungen öffnen No comment provided by engineer. + + Open full link + Vollständigen Link öffnen + alert action + Open group Gruppe öffnen - No comment provided by engineer. + new chat action Open link? + Link öffnen? alert title @@ -5582,6 +5838,36 @@ Dies erfordert die Aktivierung eines VPNs. Migration auf ein anderes Gerät öffnen authentication reason + + Open new chat + Neuen Chat öffnen + new chat action + + + Open new group + Neue Gruppe öffnen + new chat action + + + Open to accept + Zum Akzeptieren öffnen + No comment provided by engineer. + + + Open to connect + Zum Verbinden öffnen + No comment provided by engineer. + + + Open to join + Zum Beitreten öffnen + No comment provided by engineer. + + + Open to use bot + Bot für die Nutzung erlaubt + No comment provided by engineer. + Opening app… App wird geöffnet… @@ -5689,11 +5975,6 @@ Dies erfordert die Aktivierung eines VPNs. Passwort anzeigen No comment provided by engineer. - - Past member %@ - Ehemaliges Mitglied %@ - past/unknown group member - Paste desktop address Desktop-Adresse einfügen @@ -5764,7 +6045,7 @@ Bitte teilen Sie weitere mögliche Probleme den Entwicklern mit. Please check your network connection with %@ and try again. Bitte überprüfen Sie Ihre Netzwerkverbindung mit %@ und versuchen Sie es erneut. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5828,6 +6109,11 @@ Fehler: %@ Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren. token info + + Please wait for group moderators to review your request to join the group. + Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können. + snd group event chat item + Please wait for token activation to complete. Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist. @@ -5848,11 +6134,6 @@ Fehler: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig - server test error - Preserve the last message draft, with attachments. Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren. @@ -5936,7 +6217,12 @@ Fehler: %@ Private routing error Fehler beim privaten Routing - No comment provided by engineer. + alert title + + + Private routing timeout + Zeitüberschreitung der privaten Routing-Sitzung + alert title Profile and server connections @@ -6040,6 +6326,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Ihre Chat-Profile mit einem Passwort schützen! No comment provided by engineer. + + Protocol background timeout + Protokoll Hintergrund-Zeitüberschreitung + No comment provided by engineer. + Protocol timeout Protokollzeitüberschreitung @@ -6268,7 +6559,8 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reject Ablehnen - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6279,7 +6571,12 @@ swipe action Reject contact request Kontaktanfrage ablehnen - No comment provided by engineer. + alert title + + + Reject member? + Mitglied ablehnen? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6306,6 +6603,11 @@ swipe action Bild entfernen No comment provided by engineer. + + Remove link tracking + Link-Tracking entfernen + No comment provided by engineer. + Remove member Mitglied entfernen @@ -6321,6 +6623,11 @@ swipe action Passwort aus dem Schlüsselbund entfernen? No comment provided by engineer. + + Removes messages and blocks members. + Entfernt Nachrichten und blockiert Mitglieder. + No comment provided by engineer. + Renegotiate Neu aushandeln @@ -6336,11 +6643,6 @@ swipe action Verschlüsselung neu aushandeln? No comment provided by engineer. - - Repeat connection request? - Verbindungsanfrage wiederholen? - No comment provided by engineer. - Repeat download Herunterladen wiederholen @@ -6351,11 +6653,6 @@ swipe action Import wiederholen No comment provided by engineer. - - Repeat join request? - Verbindungsanfrage wiederholen? - No comment provided by engineer. - Repeat upload Hochladen wiederholen @@ -6391,6 +6688,11 @@ swipe action Grund der Meldung? No comment provided by engineer. + + Report sent to moderators + Meldung wurde an die Moderatoren gesendet + alert title + Report spam: only group moderators will see it. Spam melden: Nur Gruppenmoderatoren werden es sehen. @@ -6494,7 +6796,7 @@ swipe action Retry Wiederholen - No comment provided by engineer. + alert action Reveal @@ -6506,6 +6808,21 @@ swipe action Nutzungsbedingungen einsehen No comment provided by engineer. + + Review group members + Gruppenmitglieder überprüfen + No comment provided by engineer. + + + Review members + Überprüfung der Mitglieder + admission stage + + + Review members before admitting ("knocking"). + Überprüfung der Mitglieder vor der Aufnahme ("Anklopfen"). + admission stage description + Revoke Widerrufen @@ -6562,6 +6879,16 @@ chat item action Speichern (und Kontakte benachrichtigen) alert button + + Save (and notify members) + Speichern (und Mitglieder benachrichtigen) + alert button + + + Save admission settings? + Speichern der Aufnahme-Einstellungen? + alert title + Save and notify contact Speichern und Kontakt benachrichtigen @@ -6587,6 +6914,11 @@ chat item action Gruppenprofil speichern No comment provided by engineer. + + Save group profile? + Gruppenprofil speichern? + alert title + Save list Liste speichern @@ -6699,7 +7031,7 @@ chat item action Search bar accepts invitation links. - In der Suchleiste werden nun auch Einladungslinks akzeptiert. + In der Suchleiste werden nun auch Einladungslinks angenommen. No comment provided by engineer. @@ -6782,6 +7114,11 @@ chat item action Eine Live Nachricht senden - der/die Empfänger sieht/sehen Nachrichtenaktualisierungen, während Sie sie eingeben No comment provided by engineer. + + Send contact request? + Kontakt-Anfrage senden? + No comment provided by engineer. + Send delivery receipts to Empfangsbestätigungen senden an @@ -6847,6 +7184,16 @@ chat item action Bestätigungen senden No comment provided by engineer. + + Send request + Anfrage senden + No comment provided by engineer. + + + Send request without message + Anfrage ohne Nachricht senden + No comment provided by engineer. + Send them from gallery or custom keyboards. Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen. @@ -6857,6 +7204,11 @@ chat item action Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden. No comment provided by engineer. + + Send your private feedback to groups. + Senden Sie Ihr privates Feedback an Gruppen. + No comment provided by engineer. + Sender cancelled file transfer. Der Absender hat die Dateiübertragung abgebrochen. @@ -6997,13 +7349,13 @@ chat item action Das Server-Protokoll wurde geändert. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Bitte das Passwort überprüfen - für den Upload benötigt der Server eine Berechtigung server test error @@ -7077,6 +7429,11 @@ chat item action Anstelle der System-Authentifizierung festlegen. No comment provided by engineer. + + Set member admission + Aufnahme von Mitgliedern festlegen + No comment provided by engineer. + Set message expiration in chats. Verfallsdatum von Nachrichten in Chats festlegen. @@ -7097,6 +7454,11 @@ chat item action Passwort für den Export festlegen No comment provided by engineer. + + Set profile bio and welcome message. + Sie können eine Profil-Biografie und eine Begrüßungsmeldung eingeben. + No comment provided by engineer. + Set the message shown to new members! Definieren Sie eine Begrüßungsmeldung, die neuen Mitgliedern angezeigt wird! @@ -7168,6 +7530,16 @@ chat item action Link teilen No comment provided by engineer. + + Share old address + Alte Adresse teilen + alert button + + + Share old link + Vollständigen Link teilen + alert button + Share profile Profil teilen @@ -7188,6 +7560,21 @@ chat item action Mit Kontakten teilen No comment provided by engineer. + + Share your address + Ihre Adresse teilen + No comment provided by engineer. + + + Short SimpleX address + Verkürzte SimpleX-Adresse + No comment provided by engineer. + + + Short description + Kurze Beschreibung + No comment provided by engineer. + Short link Verkürzter Link @@ -7293,6 +7680,11 @@ chat item action SimpleX-Adresse oder Einmal-Link? No comment provided by engineer. + + SimpleX address settings + Einstellungen automatisch akzeptieren + alert title + SimpleX channel link SimpleX-Kanal-Link @@ -7338,6 +7730,11 @@ chat item action Die SimpleX-Protokolle wurden von Trail of Bits überprüft. No comment provided by engineer. + + SimpleX relay link + SimpleX Relais-Link + simplex link type + Simplified incognito mode Vereinfachter Inkognito-Modus @@ -7551,6 +7948,11 @@ report reason TCP-Verbindung No comment provided by engineer. + + TCP connection bg timeout + TCP-Verbindung Hintergrund-Zeitüberschreitung + No comment provided by engineer. + TCP connection timeout Timeout der TCP-Verbindung @@ -7586,11 +7988,31 @@ report reason Machen Sie ein Foto No comment provided by engineer. + + Tap Connect to chat + Verbinden tippen, um zu chatten + No comment provided by engineer. + + + Tap Connect to send request + Verbinden tippen, um die Anfrage zu senden + No comment provided by engineer. + + + Tap Connect to use bot + Verbinden tippen, um den Bot zu nutzen. + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen. No comment provided by engineer. + + Tap Join group + Tippen, um der Gruppe beizutreten + No comment provided by engineer. + Tap button Schaltfläche antippen @@ -7678,6 +8100,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + Die Adresse wird gekürzt sein, und Ihr Profil wird über die Adresse geteilt. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen. @@ -7738,6 +8165,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der Hash der vorherigen Nachricht unterscheidet sich. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + Der Link wird gekürzt sein, und das Gruppen-Profil wird über den Link geteilt. + alert message + The message will be deleted for all members. Diese Nachricht wird für alle Gruppenmitglieder gelöscht. @@ -7781,7 +8213,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The sender will NOT be notified Der Absender wird NICHT benachrichtigt - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7825,22 +8257,22 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! + Dieser Vorgang kann nicht rückgängig gemacht werden - alle empfangenen und gesendeten Dateien sowie Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. No comment provided by engineer. This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. - Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. Diese Aktion kann nicht rückgängig gemacht werden! + Dieser Vorgang kann nicht rückgängig gemacht werden - die früher als ausgewählt gesendeten und empfangenen Nachrichten werden gelöscht. Dies kann ein paar Minuten dauern. No comment provided by engineer. This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. - Die älteren als die ausgewählten gesendeten und empfangenen Nachrichten in diesem Chat werden gelöscht. Diese Aktion kann nicht rückgängig gemacht werden! + Dieser Vorgang kann nicht rückgängig gemacht werden - die in diesem Chat früher als ausgewählt gesendeten und empfangenen Nachrichten werden gelöscht. alert message This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. Diese Aktion kann nicht rückgängig gemacht werden! + Ihr Profil, Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. Diese Aktion kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -7873,16 +8305,6 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Diese Gruppe existiert nicht mehr. No comment provided by engineer. - - This is your own SimpleX address! - Das ist Ihre eigene SimpleX-Adresse! - No comment provided by engineer. - - - This is your own one-time link! - Das ist Ihr eigener Einmal-Link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. @@ -7903,6 +8325,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + Diese Einstellung gilt für Ihr aktuelles Profil **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Die Zeit bis zum Verschwinden wird nur für neue Kontakte eingestellt. + No comment provided by engineer. + Title Bezeichnung @@ -7985,11 +8417,21 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Für das Senden No comment provided by engineer. + + To send commands you must be connected. + Um Befehle senden zu können, müssen Sie verbunden sein. + alert message + To support instant push notifications the chat database has to be migrated. Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Wenn Sie nach dem Verbindungsversuch ein anderes Profil verwenden möchten, löschen Sie den Chat und verwenden Sie den Link erneut. + alert message + To use the servers of **%@**, accept conditions of use. Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren. @@ -8227,11 +8669,41 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden. No comment provided by engineer. + + Upgrade + Aktualisieren + alert button + + + Upgrade address + Adresse aktualisieren + No comment provided by engineer. + + + Upgrade address? + Adresse aktualisieren? + alert message + Upgrade and open chat Aktualisieren und den Chat öffnen No comment provided by engineer. + + Upgrade group link? + Gruppen-Link aktualisieren? + alert message + + + Upgrade link + Link hinzufügen + No comment provided by engineer. + + + Upgrade your address + Ihre Adresse aktualisieren + No comment provided by engineer. + Upload errors Fehler beim Hochladen @@ -8300,7 +8772,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use current profile Aktuelles Profil nutzen - No comment provided by engineer. + new chat action Use for files @@ -8327,10 +8799,15 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s iOS Anrufschnittstelle nutzen No comment provided by engineer. + + Use incognito profile + Inkognito-Profil nutzen + No comment provided by engineer. + Use new incognito profile Neues Inkognito-Profil nutzen - No comment provided by engineer. + new chat action Use only local notifications? @@ -8357,11 +8834,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verwende Server No comment provided by engineer. - - Use short links (BETA) - Kurze Links verwenden (BETA) - No comment provided by engineer. - Use the app while in the call. Die App kann während eines Anrufs genutzt werden. @@ -8567,6 +9039,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Die Begrüßungsmeldung ist zu lang No comment provided by engineer. + + Welcome your contacts 👋 + Begrüßen Sie Ihre Kontakte 👋 + No comment provided by engineer. + What's new Was ist neu @@ -8664,7 +9141,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s You accepted connection - Sie haben die Verbindung akzeptiert + Sie haben die Verbindung angenommen No comment provided by engineer. @@ -8690,12 +9167,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s You are already connecting to %@. Sie sind bereits mit %@ verbunden. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Sie sind bereits über diesen Einmal-Link verbunden! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8705,24 +9182,19 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s You are already joining the group %@. Sie sind bereits Mitglied der Gruppe %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Sie sind über diesen Link bereits Mitglied der Gruppe! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Sie sind über diesen Link bereits Mitglied der Gruppe. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Sie sind bereits Mitglied dieser Gruppe! Verbindungsanfrage wiederholen? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8839,10 +9311,15 @@ Verbindungsanfrage wiederholen? Den Einladungslink können Sie in den Details der Verbindung nochmals sehen. alert message + + You can view your reports in Chat with admins. + Sie können Ihre Meldungen im Chat mit den Administratoren sehen. + alert message + You can't send messages! Sie können keine Nachrichten versenden! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8854,17 +9331,12 @@ Verbindungsanfrage wiederholen? Sie entscheiden, wer sich mit Ihnen verbinden kann. No comment provided by engineer. - - You have already requested connection via this address! - Sie haben über diese Adresse bereits eine Verbindung beantragt! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Sie haben bereits ein Verbindungsanfrage beantragt! Verbindungsanfrage wiederholen? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8926,6 +9398,11 @@ Verbindungsanfrage wiederholen? Sie sollten Benachrichtigungen erhalten. token info + + You will be able to send messages **only after your request is accepted**. + Sie können erst dann Nachrichten versenden, **sobald Ihre Anfrage angenommen wurde**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach! @@ -8938,7 +9415,7 @@ Verbindungsanfrage wiederholen? You will be connected when your connection request is accepted, please wait or check later! - Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach! + Sie werden verbunden, sobald Ihre Verbindungsanfrage angenommen wird. Bitte warten oder schauen Sie später nochmal nach! No comment provided by engineer. @@ -8951,11 +9428,6 @@ Verbindungsanfrage wiederholen? Sie müssen sich authentifizieren, wenn Sie die im Hintergrund befindliche App nach 30 Sekunden starten oder fortsetzen. No comment provided by engineer. - - You will connect to all group members. - Sie werden mit allen Gruppenmitgliedern verbunden. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind. @@ -8996,6 +9468,11 @@ Verbindungsanfrage wiederholen? Ihre SimpleX-Adresse No comment provided by engineer. + + Your business contact + Ihr geschäftlicher Kontakt + No comment provided by engineer. + Your calls Anrufe @@ -9021,11 +9498,21 @@ Verbindungsanfrage wiederholen? Ihre Chat-Profile No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ihr Chat wurde nach %@ verschoben, aber es ist ein unerwarteter Fehler aufgetreten, als Sie zum Profil weitergeleitet wurden. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf. No comment provided by engineer. + + Your contact + Ihr Kontakt + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@). @@ -9056,6 +9543,11 @@ Verbindungsanfrage wiederholen? Mein aktuelles Chat-Profil No comment provided by engineer. + + Your group + Ihre Gruppe + No comment provided by engineer. + Your preferences Ihre Präferenzen @@ -9078,7 +9570,7 @@ Verbindungsanfrage wiederholen? Your profile is stored on your device and only shared with your contacts. - Das Profil wird nur mit Ihren Kontakten geteilt. + Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. No comment provided by engineer. @@ -9141,6 +9633,11 @@ Verbindungsanfrage wiederholen? Danach die gewünschte Aktion auswählen: No comment provided by engineer. + + accepted %@ + %@ angenommen + rcv group event chat item + accepted call Anruf angenommen @@ -9151,6 +9648,11 @@ Verbindungsanfrage wiederholen? Einladung angenommen chat list item title + + accepted you + hat Sie angenommen + rcv group event chat item + admin Admin @@ -9171,6 +9673,11 @@ Verbindungsanfrage wiederholen? Verschlüsselung zustimmen… chat item text + + all + alle + member criteria value + all members Alle Mitglieder @@ -9257,6 +9764,11 @@ marked deleted chat item preview text Anrufen… call status + + can't send messages + Es können keine Nachrichten gesendet werden + No comment provided by engineer. + cancelled %@ abgebrochen %@ @@ -9307,11 +9819,6 @@ marked deleted chat item preview text Verbunden No comment provided by engineer. - - connected directly - Direkt miteinander verbunden - rcv group event chat item - connecting verbinde @@ -9362,6 +9869,16 @@ marked deleted chat item preview text Der Kontaktname wurde von %1$@ auf %2$@ geändert profile update event chat item + + contact deleted + Kontakt gelöscht + No comment provided by engineer. + + + contact disabled + Kontakt deaktiviert + No comment provided by engineer. + contact has e2e encryption Kontakt nutzt E2E-Verschlüsselung @@ -9372,6 +9889,16 @@ marked deleted chat item preview text Kontakt nutzt keine E2E-Verschlüsselung No comment provided by engineer. + + contact not ready + Kontakt nicht bereit + No comment provided by engineer. + + + contact should accept… + Kontakt sollte annehmen… + No comment provided by engineer. + creator Ersteller @@ -9538,11 +10065,21 @@ pref value weitergeleitet No comment provided by engineer. + + group + Gruppe + shown on group welcome message + group deleted Gruppe gelöscht No comment provided by engineer. + + group is deleted + Gruppe wurde gelöscht + No comment provided by engineer. + group profile updated Gruppenprofil aktualisiert @@ -9638,11 +10175,6 @@ pref value kursiv No comment provided by engineer. - - join as %@ - beitreten als %@ - No comment provided by engineer. - left hat die Gruppe verlassen @@ -9668,6 +10200,11 @@ pref value ist der Gruppe beigetreten rcv group event chat item + + member has old version + Das Mitglied hat eine alte App-Version + No comment provided by engineer. + message Nachricht @@ -9733,6 +10270,11 @@ pref value Kein Text copied message info in history + + not synchronized + Nicht synchronisiert + No comment provided by engineer. + observer Beobachter @@ -9743,6 +10285,7 @@ pref value Aus enabled status group pref value +member criteria value time to disappear @@ -9795,6 +10338,11 @@ time to disappear ausstehende Genehmigung No comment provided by engineer. + + pending review + Ausstehende Überprüfung + No comment provided by engineer. + quantum resistant e2e encryption Quantum-resistente E2E-Verschlüsselung @@ -9835,6 +10383,11 @@ time to disappear Die Kontaktadresse wurde entfernt profile update event chat item + + removed from group + Aus der Gruppe entfernt + No comment provided by engineer. + removed profile picture Das Profil-Bild wurde entfernt @@ -9845,11 +10398,41 @@ time to disappear hat Sie aus der Gruppe entfernt rcv group event chat item + + request is sent + Anfrage wurde gesendet + No comment provided by engineer. + + + request to join rejected + Beitrittsanfrage abgelehnt + No comment provided by engineer. + + + requested connection + Angefragte Verbindung + rcv group event chat item + + + requested connection from group %@ + Angefragte Verbindung von Gruppe %@ + rcv direct event chat item + requested to connect Zur Verbindung aufgefordert chat list item title + + review + Überprüfung + No comment provided by engineer. + + + reviewed by admins + Von Administratoren überprüft + No comment provided by engineer. + saved abgespeichert @@ -9885,11 +10468,6 @@ time to disappear Sicherheitscode wurde geändert chat item text - - send direct message - Direktnachricht senden - No comment provided by engineer. - server queue info: %1$@ @@ -10039,10 +10617,10 @@ Zuletzt empfangene Nachricht: %2$@ Profil No comment provided by engineer. - - you are invited to group - Sie sind zu der Gruppe eingeladen - No comment provided by engineer. + + you accepted this member + Sie haben dieses Mitglied angenommen + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index fc1846942c..7a560bb41b 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -2350,8 +2350,8 @@ Available in v5.1 Polish interface No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -2726,12 +2726,12 @@ Available in v5.1 Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. server test error @@ -3940,7 +3940,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -4117,8 +4117,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index fd71e0dee6..6761ff6fce 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -563,8 +563,19 @@ time interval Accept accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Accept as member + alert action + + + Accept as observer + Accept as observer + alert action + Accept conditions Accept conditions @@ -575,6 +586,11 @@ swipe action Accept connection request? No comment provided by engineer. + + Accept contact request + Accept contact request + alert title + Accept contact request from %@? Accept contact request from %@? @@ -583,9 +599,14 @@ swipe action Accept incognito Accept incognito - accept contact request via notification + alert action swipe action + + Accept member + Accept member + alert title + Accepted conditions Accepted conditions @@ -626,6 +647,11 @@ swipe action Add list No comment provided by engineer. + + Add message + Add message + placeholder for sending contact request + Add profile Add profile @@ -841,6 +867,11 @@ swipe action Allow downgrade No comment provided by engineer. + + Allow files and media only if your contact allows them. + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Allow irreversible message deletion only if your contact allows it to you. (24 hours) @@ -926,6 +957,11 @@ swipe action Allow your contacts to send disappearing messages. No comment provided by engineer. + + Allow your contacts to send files and media. + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. Allow your contacts to send voice messages. @@ -939,12 +975,12 @@ swipe action Already connecting! Already connecting! - No comment provided by engineer. + new chat sheet title Already joining the group! Already joining the group! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1151,11 +1187,6 @@ swipe action Auto-accept images No comment provided by engineer. - - Auto-accept settings - Auto-accept settings - alert title - Back Back @@ -1231,6 +1262,16 @@ swipe action Better user experience No comment provided by engineer. + + Bio + Bio + No comment provided by engineer. + + + Bio too large + Bio too large + alert title + Black Black @@ -1281,6 +1322,11 @@ swipe action Blur media No comment provided by engineer. + + Bot + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Both you and your contact can add message reactions. @@ -1301,6 +1347,11 @@ swipe action Both you and your contact can send disappearing messages. No comment provided by engineer. + + Both you and your contact can send files and media. + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. Both you and your contact can send voice messages. @@ -1321,6 +1372,11 @@ swipe action Business chats No comment provided by engineer. + + Business connection + Business connection + No comment provided by engineer. + Businesses Businesses @@ -1370,6 +1426,11 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + Can't change profile + alert title + Can't invite contact! Can't invite contact! @@ -1389,7 +1450,8 @@ swipe action Cancel Cancel alert action -alert button +alert button +new chat action Cancel migration @@ -1495,7 +1557,7 @@ set passcode view Chat already exists! Chat already exists! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1582,11 +1644,31 @@ set passcode view Chat will be deleted for you - this cannot be undone! No comment provided by engineer. + + Chat with admins + Chat with admins + chat toolbar + + + Chat with member + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + Chat with members before they join. + No comment provided by engineer. + Chats Chats No comment provided by engineer. + + Chats with members + Chats with members + No comment provided by engineer. + Check messages every 20 min. Check messages every 20 min. @@ -1812,9 +1894,9 @@ set passcode view Connect automatically No comment provided by engineer. - - Connect incognito - Connect incognito + + Connect faster! 🚀 + Connect faster! 🚀 No comment provided by engineer. @@ -1827,44 +1909,39 @@ set passcode view Connect to your friends faster. No comment provided by engineer. - - Connect to yourself? - Connect to yourself? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Connect to yourself? This is your own SimpleX address! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Connect to yourself? This is your own one-time link! - No comment provided by engineer. + new chat sheet title Connect via contact address Connect via contact address - No comment provided by engineer. + new chat sheet title Connect via link Connect via link - No comment provided by engineer. + new chat sheet title Connect via one-time link Connect via one-time link - No comment provided by engineer. + new chat sheet title Connect with %@ Connect with %@ - No comment provided by engineer. + new chat action Connected @@ -1929,7 +2006,7 @@ This is your own one-time link! Connection error Connection error - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1976,7 +2053,7 @@ This is your own one-time link! Connection timeout Connection timeout - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2028,6 +2105,11 @@ This is your own one-time link! Contact preferences No comment provided by engineer. + + Contact requests from groups + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Contact will be deleted - this cannot be undone! @@ -2143,9 +2225,9 @@ This is your own one-time link! Create queue server test step - - Create secret group - Create secret group + + Create your address + Create your address No comment provided by engineer. @@ -2402,6 +2484,11 @@ swipe action Delete chat profile? No comment provided by engineer. + + Delete chat with member? + Delete chat with member? + alert title + Delete chat? Delete chat? @@ -2597,11 +2684,21 @@ swipe action Delivery receipts! No comment provided by engineer. + + Deprecated options + Deprecated options + No comment provided by engineer. + Description Description No comment provided by engineer. + + Description too large + Description too large + alert title + Desktop address Desktop address @@ -2825,7 +2922,7 @@ swipe action Don't show again Don't show again - No comment provided by engineer. + alert action Done @@ -2908,6 +3005,11 @@ chat item action Edit group profile No comment provided by engineer. + + Empty message! + Empty message! + No comment provided by engineer. + Enable Enable @@ -2943,6 +3045,11 @@ chat item action Enable camera access No comment provided by engineer. + + Enable disappearing messages by default. + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Enable for all @@ -3143,6 +3250,11 @@ chat item action Error accepting contact request No comment provided by engineer. + + Error accepting member + Error accepting member + alert title + Error adding member(s) Error adding member(s) @@ -3153,11 +3265,21 @@ chat item action Error adding server alert title + + Error adding short link + Error adding short link + No comment provided by engineer. + Error changing address Error changing address No comment provided by engineer. + + Error changing chat profile + Error changing chat profile + alert title + Error changing connection profile Error changing connection profile @@ -3171,7 +3293,7 @@ chat item action Error changing setting Error changing setting - No comment provided by engineer. + alert title Error changing to incognito! @@ -3186,7 +3308,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Error connecting to forwarding server %@. Please try later. - No comment provided by engineer. + alert message Error creating address @@ -3233,15 +3355,20 @@ chat item action Error decrypting file No comment provided by engineer. + + Error deleting chat + Error deleting chat + alert title + Error deleting chat database Error deleting chat database - No comment provided by engineer. + alert title Error deleting chat! Error deleting chat! - No comment provided by engineer. + alert title Error deleting connection @@ -3251,12 +3378,12 @@ chat item action Error deleting database Error deleting database - No comment provided by engineer. + alert title Error deleting old database Error deleting old database - No comment provided by engineer. + alert title Error deleting token @@ -3291,7 +3418,7 @@ chat item action Error exporting chat database Error exporting chat database - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3301,7 +3428,7 @@ chat item action Error importing chat database Error importing chat database - No comment provided by engineer. + alert title Error joining group @@ -3323,6 +3450,11 @@ chat item action Error opening chat No comment provided by engineer. + + Error opening group + Error opening group + No comment provided by engineer. + Error receiving file Error receiving file @@ -3343,10 +3475,15 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + Error rejecting contact request + alert title + Error removing member Error removing member - No comment provided by engineer. + alert title Error reordering lists @@ -3418,6 +3555,11 @@ chat item action Error sending message No comment provided by engineer. + + Error setting auto-accept + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Error setting delivery receipts! @@ -3436,7 +3578,7 @@ chat item action Error switching profile Error switching profile - No comment provided by engineer. + alert title Error switching profile! @@ -3500,6 +3642,11 @@ chat item action file error text snd error text + + Error: %@. + Error: %@. + server test error + Error: URL is invalid Error: URL is invalid @@ -3679,6 +3826,11 @@ snd error text Files and media chat feature + + Files and media are prohibited in this chat. + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Files and media are prohibited. @@ -3719,6 +3871,26 @@ snd error text Find chats faster No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Fingerprint in server address does not match certificate. + server test error + + + Fingerprint in server address does not match certificate: %@. + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Fix @@ -3830,9 +4002,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Forwarding server %@ failed to connect to destination server %@. Please try later. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3926,7 +4098,7 @@ Error: %2$@ Group already exists! Group already exists! - No comment provided by engineer. + new chat sheet title Group display name @@ -3993,6 +4165,11 @@ Error: %2$@ Group profile is stored on members' devices, not on the servers. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Group welcome message @@ -4375,7 +4552,7 @@ More improvements are coming soon! Invalid link Invalid link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4488,37 +4665,32 @@ More improvements are coming soon! Join swipe action + + Join as %@ + Join as %@ + No comment provided by engineer. + Join group Join group - No comment provided by engineer. + new chat sheet title Join group conversations Join group conversations No comment provided by engineer. - - Join group? - Join group? - No comment provided by engineer. - Join incognito Join incognito No comment provided by engineer. - - Join with current profile - Join with current profile - No comment provided by engineer. - Join your group? This is your link for group %@! Join your group? This is your link for group %@! - No comment provided by engineer. + new chat action Joining group @@ -4545,6 +4717,11 @@ This is your link for group %@! Keep unused invitation? alert title + + Keep your chats clean + Keep your chats clean + No comment provided by engineer. + Keep your connections Keep your connections @@ -4600,6 +4777,11 @@ This is your link for group %@! Leave group? No comment provided by engineer. + + Less traffic on mobile networks. + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Let's talk in SimpleX Chat @@ -4655,6 +4837,11 @@ This is your link for group %@! Live messages No comment provided by engineer. + + Loading profile… + Loading profile… + in progress text + Local name Local name @@ -4730,11 +4917,26 @@ This is your link for group %@! Member No comment provided by engineer. + + Member %@ + Member %@ + past/unknown group member + + + Member admission + Member admission + No comment provided by engineer. + Member inactive Member inactive item status text + + Member is deleted - can't accept request + Member is deleted - can't accept request + No comment provided by engineer. + Member reports Member reports @@ -4765,6 +4967,11 @@ This is your link for group %@! Member will be removed from group - this cannot be undone! No comment provided by engineer. + + Member will join the group, accept member? + Member will join the group, accept member? + alert message + Members can add message reactions. Members can add message reactions. @@ -4840,6 +5047,11 @@ This is your link for group %@! Message forwarded item status text + + Message instantly once you tap Connect. + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. Message may be delivered later if member becomes active. @@ -4915,6 +5127,11 @@ This is your link for group %@! Messages & files No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! Messages from %@ will be shown! @@ -5170,6 +5387,11 @@ This is your link for group %@! New events notification + + New group role: Moderator + New group role: Moderator + No comment provided by engineer. + New in %@ New in %@ @@ -5185,6 +5407,11 @@ This is your link for group %@! New member role No comment provided by engineer. + + New member wants to join the group. + New member wants to join the group. + rcv group event chat item + New message New message @@ -5225,6 +5452,11 @@ This is your link for group %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No chats with members + No comment provided by engineer. + No contacts selected No contacts selected @@ -5305,6 +5537,11 @@ This is your link for group %@! No permission to record voice message No comment provided by engineer. + + No private routing session + No private routing session + alert title + No push server No push server @@ -5417,7 +5654,9 @@ This is your link for group %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5508,6 +5747,11 @@ Requires compatible VPN. Only you can send disappearing messages. No comment provided by engineer. + + Only you can send files and media. + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Only you can send voice messages. @@ -5533,6 +5777,11 @@ Requires compatible VPN. Only your contact can send disappearing messages. No comment provided by engineer. + + Only your contact can send files and media. + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Only your contact can send voice messages. @@ -5556,22 +5805,32 @@ Requires compatible VPN. Open chat Open chat - No comment provided by engineer. + new chat action Open chat console Open chat console authentication reason + + Open clean link + Open clean link + alert action + Open conditions Open conditions No comment provided by engineer. + + Open full link + Open full link + alert action + Open group Open group - No comment provided by engineer. + new chat action Open link? @@ -5583,6 +5842,36 @@ Requires compatible VPN. Open migration to another device authentication reason + + Open new chat + Open new chat + new chat action + + + Open new group + Open new group + new chat action + + + Open to accept + Open to accept + No comment provided by engineer. + + + Open to connect + Open to connect + No comment provided by engineer. + + + Open to join + Open to join + No comment provided by engineer. + + + Open to use bot + Open to use bot + No comment provided by engineer. + Opening app… Opening app… @@ -5690,11 +5979,6 @@ Requires compatible VPN. Password to show No comment provided by engineer. - - Past member %@ - Past member %@ - past/unknown group member - Paste desktop address Paste desktop address @@ -5765,7 +6049,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. Please check your network connection with %@ and try again. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5829,6 +6113,11 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. Please wait for token activation to complete. @@ -5849,11 +6138,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Possibly, certificate fingerprint in server address is incorrect - server test error - Preserve the last message draft, with attachments. Preserve the last message draft, with attachments. @@ -5937,7 +6221,12 @@ Error: %@ Private routing error Private routing error - No comment provided by engineer. + alert title + + + Private routing timeout + Private routing timeout + alert title Profile and server connections @@ -6041,6 +6330,11 @@ Enable in *Network & servers* settings. Protect your chat profiles with a password! No comment provided by engineer. + + Protocol background timeout + Protocol background timeout + No comment provided by engineer. + Protocol timeout Protocol timeout @@ -6269,7 +6563,8 @@ Enable in *Network & servers* settings. Reject Reject - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6280,7 +6575,12 @@ swipe action Reject contact request Reject contact request - No comment provided by engineer. + alert title + + + Reject member? + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6307,6 +6607,11 @@ swipe action Remove image No comment provided by engineer. + + Remove link tracking + Remove link tracking + No comment provided by engineer. + Remove member Remove member @@ -6322,6 +6627,11 @@ swipe action Remove passphrase from keychain? No comment provided by engineer. + + Removes messages and blocks members. + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Renegotiate @@ -6337,11 +6647,6 @@ swipe action Renegotiate encryption? No comment provided by engineer. - - Repeat connection request? - Repeat connection request? - No comment provided by engineer. - Repeat download Repeat download @@ -6352,11 +6657,6 @@ swipe action Repeat import No comment provided by engineer. - - Repeat join request? - Repeat join request? - No comment provided by engineer. - Repeat upload Repeat upload @@ -6392,6 +6692,11 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + Report sent to moderators + alert title + Report spam: only group moderators will see it. Report spam: only group moderators will see it. @@ -6495,7 +6800,7 @@ swipe action Retry Retry - No comment provided by engineer. + alert action Reveal @@ -6507,6 +6812,21 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + Review group members + No comment provided by engineer. + + + Review members + Review members + admission stage + + + Review members before admitting ("knocking"). + Review members before admitting ("knocking"). + admission stage description + Revoke Revoke @@ -6563,6 +6883,16 @@ chat item action Save (and notify contacts) alert button + + Save (and notify members) + Save (and notify members) + alert button + + + Save admission settings? + Save admission settings? + alert title + Save and notify contact Save and notify contact @@ -6588,6 +6918,11 @@ chat item action Save group profile No comment provided by engineer. + + Save group profile? + Save group profile? + alert title + Save list Save list @@ -6783,6 +7118,11 @@ chat item action Send a live message - it will update for the recipient(s) as you type it No comment provided by engineer. + + Send contact request? + Send contact request? + No comment provided by engineer. + Send delivery receipts to Send delivery receipts to @@ -6848,6 +7188,16 @@ chat item action Send receipts No comment provided by engineer. + + Send request + Send request + No comment provided by engineer. + + + Send request without message + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Send them from gallery or custom keyboards. @@ -6858,6 +7208,11 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send your private feedback to groups. + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. Sender cancelled file transfer. @@ -6998,14 +7353,14 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. + Server requires authorization to create queues, check password. server test error - - Server requires authorization to upload, check password - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. + Server requires authorization to upload, check password. server test error @@ -7078,6 +7433,11 @@ chat item action Set it instead of system authentication. No comment provided by engineer. + + Set member admission + Set member admission + No comment provided by engineer. + Set message expiration in chats. Set message expiration in chats. @@ -7098,6 +7458,11 @@ chat item action Set passphrase to export No comment provided by engineer. + + Set profile bio and welcome message. + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Set the message shown to new members! @@ -7169,6 +7534,16 @@ chat item action Share link No comment provided by engineer. + + Share old address + Share old address + alert button + + + Share old link + Share old link + alert button + Share profile Share profile @@ -7189,6 +7564,21 @@ chat item action Share with contacts No comment provided by engineer. + + Share your address + Share your address + No comment provided by engineer. + + + Short SimpleX address + Short SimpleX address + No comment provided by engineer. + + + Short description + Short description + No comment provided by engineer. + Short link Short link @@ -7294,6 +7684,11 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + SimpleX address settings + alert title + SimpleX channel link SimpleX channel link @@ -7339,6 +7734,11 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + SimpleX relay link + simplex link type + Simplified incognito mode Simplified incognito mode @@ -7552,6 +7952,11 @@ report reason TCP connection No comment provided by engineer. + + TCP connection bg timeout + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout TCP connection timeout @@ -7587,11 +7992,31 @@ report reason Take picture No comment provided by engineer. + + Tap Connect to chat + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + Tap Join group + No comment provided by engineer. + Tap button Tap button @@ -7679,6 +8104,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. The app can notify you when you receive messages or contact requests - please open settings to enable. @@ -7739,6 +8169,11 @@ It can happen because of some bug or when the connection is compromised.The hash of the previous message is different. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. The message will be deleted for all members. @@ -7782,7 +8217,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified The sender will NOT be notified - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7874,16 +8309,6 @@ It can happen because of some bug or when the connection is compromised.This group no longer exists. No comment provided by engineer. - - This is your own SimpleX address! - This is your own SimpleX address! - No comment provided by engineer. - - - This is your own one-time link! - This is your own one-time link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. @@ -7904,6 +8329,16 @@ It can happen because of some bug or when the connection is compromised.This setting applies to messages in your current chat profile **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title Title @@ -7986,11 +8421,21 @@ You will be prompted to complete authentication before this feature is enabled.< To send No comment provided by engineer. + + To send commands you must be connected. + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. To support instant push notifications the chat database has to be migrated. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. To use the servers of **%@**, accept conditions of use. @@ -8228,11 +8673,41 @@ To connect, please ask your contact to create another connection link and check Updating settings will re-connect the client to all servers. No comment provided by engineer. + + Upgrade + Upgrade + alert button + + + Upgrade address + Upgrade address + No comment provided by engineer. + + + Upgrade address? + Upgrade address? + alert message + Upgrade and open chat Upgrade and open chat No comment provided by engineer. + + Upgrade group link? + Upgrade group link? + alert message + + + Upgrade link + Upgrade link + No comment provided by engineer. + + + Upgrade your address + Upgrade your address + No comment provided by engineer. + Upload errors Upload errors @@ -8301,7 +8776,7 @@ To connect, please ask your contact to create another connection link and check Use current profile Use current profile - No comment provided by engineer. + new chat action Use for files @@ -8328,10 +8803,15 @@ To connect, please ask your contact to create another connection link and check Use iOS call interface No comment provided by engineer. + + Use incognito profile + Use incognito profile + No comment provided by engineer. + Use new incognito profile Use new incognito profile - No comment provided by engineer. + new chat action Use only local notifications? @@ -8358,11 +8838,6 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. - - Use short links (BETA) - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Use the app while in the call. @@ -8568,6 +9043,11 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long No comment provided by engineer. + + Welcome your contacts 👋 + Welcome your contacts 👋 + No comment provided by engineer. + What's new What's new @@ -8691,12 +9171,12 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. You are already connecting to %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! You are already connecting via this one-time link! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8706,24 +9186,19 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. You are already joining the group %@. - No comment provided by engineer. - - - You are already joining the group via this link! - You are already joining the group via this link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. You are already joining the group via this link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? You are already joining the group! Repeat join request? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8840,10 +9315,15 @@ Repeat join request? You can view invitation link again in connection details. alert message + + You can view your reports in Chat with admins. + You can view your reports in Chat with admins. + alert message + You can't send messages! You can't send messages! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8855,17 +9335,12 @@ Repeat join request? You decide who can connect. No comment provided by engineer. - - You have already requested connection via this address! - You have already requested connection via this address! - No comment provided by engineer. - You have already requested connection! Repeat connection request? You have already requested connection! Repeat connection request? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8927,6 +9402,11 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! You will be connected to group when the group host's device is online, please wait or check later! @@ -8952,11 +9432,6 @@ Repeat connection request? You will be required to authenticate when you start or resume the app after 30 seconds in background. No comment provided by engineer. - - You will connect to all group members. - You will connect to all group members. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. You will still receive calls and notifications from muted profiles when they are active. @@ -8997,6 +9472,11 @@ Repeat connection request? Your SimpleX address No comment provided by engineer. + + Your business contact + Your business contact + No comment provided by engineer. + Your calls Your calls @@ -9022,9 +9502,19 @@ Repeat connection request? Your chat profiles No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact + Your contact No comment provided by engineer. @@ -9057,6 +9547,11 @@ Repeat connection request? Your current profile No comment provided by engineer. + + Your group + Your group + No comment provided by engineer. + Your preferences Your preferences @@ -9142,6 +9637,11 @@ Repeat connection request? above, then choose: No comment provided by engineer. + + accepted %@ + accepted %@ + rcv group event chat item + accepted call accepted call @@ -9152,6 +9652,11 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + accepted you + rcv group event chat item + admin admin @@ -9172,6 +9677,11 @@ Repeat connection request? agreeing encryption… chat item text + + all + all + member criteria value + all members all members @@ -9258,6 +9768,11 @@ marked deleted chat item preview text calling… call status + + can't send messages + can't send messages + No comment provided by engineer. + cancelled %@ cancelled %@ @@ -9308,11 +9823,6 @@ marked deleted chat item preview text connected No comment provided by engineer. - - connected directly - connected directly - rcv group event chat item - connecting connecting @@ -9363,6 +9873,16 @@ marked deleted chat item preview text contact %1$@ changed to %2$@ profile update event chat item + + contact deleted + contact deleted + No comment provided by engineer. + + + contact disabled + contact disabled + No comment provided by engineer. + contact has e2e encryption contact has e2e encryption @@ -9373,6 +9893,16 @@ marked deleted chat item preview text contact has no e2e encryption No comment provided by engineer. + + contact not ready + contact not ready + No comment provided by engineer. + + + contact should accept… + contact should accept… + No comment provided by engineer. + creator creator @@ -9539,11 +10069,21 @@ pref value forwarded No comment provided by engineer. + + group + group + shown on group welcome message + group deleted group deleted No comment provided by engineer. + + group is deleted + group is deleted + No comment provided by engineer. + group profile updated group profile updated @@ -9639,11 +10179,6 @@ pref value italic No comment provided by engineer. - - join as %@ - join as %@ - No comment provided by engineer. - left left @@ -9669,6 +10204,11 @@ pref value connected rcv group event chat item + + member has old version + member has old version + No comment provided by engineer. + message message @@ -9734,6 +10274,11 @@ pref value no text copied message info in history + + not synchronized + not synchronized + No comment provided by engineer. + observer observer @@ -9744,6 +10289,7 @@ pref value off enabled status group pref value +member criteria value time to disappear @@ -9796,6 +10342,11 @@ time to disappear pending approval No comment provided by engineer. + + pending review + pending review + No comment provided by engineer. + quantum resistant e2e encryption quantum resistant e2e encryption @@ -9836,6 +10387,11 @@ time to disappear removed contact address profile update event chat item + + removed from group + removed from group + No comment provided by engineer. + removed profile picture removed profile picture @@ -9846,11 +10402,41 @@ time to disappear removed you rcv group event chat item + + request is sent + request is sent + No comment provided by engineer. + + + request to join rejected + request to join rejected + No comment provided by engineer. + + + requested connection + requested connection + rcv group event chat item + + + requested connection from group %@ + requested connection from group %@ + rcv direct event chat item + requested to connect requested to connect chat list item title + + review + review + No comment provided by engineer. + + + reviewed by admins + reviewed by admins + No comment provided by engineer. + saved saved @@ -9886,11 +10472,6 @@ time to disappear security code changed chat item text - - send direct message - send direct message - No comment provided by engineer. - server queue info: %1$@ @@ -10040,10 +10621,10 @@ last received msg: %2$@ you No comment provided by engineer. - - you are invited to group - you are invited to group - No comment provided by engineer. + + you accepted this member + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index d39fb61249..f11ad704ac 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -563,8 +563,19 @@ time interval Aceptar accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Aceptar como miembro + alert action + + + Accept as observer + Aceptar como observador + alert action + Accept conditions Aceptar condiciones @@ -575,6 +586,11 @@ swipe action ¿Aceptar solicitud de conexión? No comment provided by engineer. + + Accept contact request + Aceptar petición de contacto + alert title + Accept contact request from %@? ¿Aceptar solicitud de contacto de %@? @@ -583,9 +599,14 @@ swipe action Accept incognito Aceptar incógnito - accept contact request via notification + alert action swipe action + + Accept member + Aceptar miembro + alert title + Accepted conditions Condiciones aceptadas @@ -626,6 +647,11 @@ swipe action Añadir lista No comment provided by engineer. + + Add message + Añadir mensaje + placeholder for sending contact request + Add profile Añadir perfil @@ -833,7 +859,7 @@ swipe action Allow disappearing messages only if your contact allows it to you. - Se permiten los mensajes temporales pero sólo si tu contacto también los permite para tí. + Se permiten los mensajes temporales pero sólo si tu contacto también los permite. No comment provided by engineer. @@ -841,9 +867,14 @@ swipe action Permitir versión anterior No comment provided by engineer. + + Allow files and media only if your contact allows them. + Se permiten archivos y multimedia pero sólo si tu contacto también los permite. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) - Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también la permite para tí. (24 horas) + Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite. (24 horas) No comment provided by engineer. @@ -908,27 +939,32 @@ swipe action Allow your contacts adding message reactions. - Permitir que tus contactos añadan reacciones a los mensajes. + Permites que tus contactos añadan reacciones a los mensajes. No comment provided by engineer. Allow your contacts to call you. - Permites que tus contactos puedan llamarte. + Permites que tus contactos te llamen. No comment provided by engineer. Allow your contacts to irreversibly delete sent messages. (24 hours) - Permites a tus contactos eliminar irreversiblemente los mensajes enviados. (24 horas) + Permites que tus contactos eliminan irreversiblemente los mensajes enviados. (24 horas) No comment provided by engineer. Allow your contacts to send disappearing messages. - Permites a tus contactos enviar mensajes temporales. + Permites que tus contactos envien mensajes temporales. + No comment provided by engineer. + + + Allow your contacts to send files and media. + Permes que tus contactos envíen archivos y multimedia. No comment provided by engineer. Allow your contacts to send voice messages. - Permites a tus contactos enviar mensajes de voz. + Permites que tus contactos envien mensajes de voz. No comment provided by engineer. @@ -939,12 +975,12 @@ swipe action Already connecting! ¡Ya en proceso de conexión! - No comment provided by engineer. + new chat sheet title Already joining the group! ¡Ya en proceso de unirte al grupo! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1098,7 +1134,7 @@ swipe action Audio & video calls - Llamadas y videollamadas + Llamadas y Videollamadas No comment provided by engineer. @@ -1151,11 +1187,6 @@ swipe action Aceptar imágenes automáticamente No comment provided by engineer. - - Auto-accept settings - Auto aceptar configuración - alert title - Back Volver @@ -1231,6 +1262,16 @@ swipe action Experiencia de usuario mejorada No comment provided by engineer. + + Bio + Biografía + No comment provided by engineer. + + + Bio too large + Biografía demasiado larga + alert title + Black Negro @@ -1281,6 +1322,11 @@ swipe action Difuminar multimedia No comment provided by engineer. + + Bot + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Tanto tú como tu contacto podéis añadir reacciones a los mensajes. @@ -1301,6 +1347,11 @@ swipe action Tanto tú como tu contacto podéis enviar mensajes temporales. No comment provided by engineer. + + Both you and your contact can send files and media. + Tanto tú como tu contacto podéis enviar archivos y multimedia. + No comment provided by engineer. + Both you and your contact can send voice messages. Tanto tú como tu contacto podéis enviar mensajes de voz. @@ -1321,6 +1372,11 @@ swipe action Chats empresariales No comment provided by engineer. + + Business connection + Conexión empresarial + No comment provided by engineer. + Businesses Empresas @@ -1370,6 +1426,11 @@ swipe action No se puede llamar al miembro No comment provided by engineer. + + Can't change profile + No se puede cambiar el perfil + alert title + Can't invite contact! ¡No se puede invitar el contacto! @@ -1389,7 +1450,8 @@ swipe action Cancel Cancelar alert action -alert button +alert button +new chat action Cancel migration @@ -1495,7 +1557,7 @@ set passcode view Chat already exists! ¡El chat ya existe! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1582,11 +1644,31 @@ set passcode view El chat será eliminado para tí. ¡No puede deshacerse! No comment provided by engineer. + + Chat with admins + Chatea con administradores + chat toolbar + + + Chat with member + Chat con miembro + No comment provided by engineer. + + + Chat with members before they join. + Chatea con el miembro antes de unirse. + No comment provided by engineer. + Chats Chats No comment provided by engineer. + + Chats with members + Chat con miembros + No comment provided by engineer. + Check messages every 20 min. Comprobar mensajes cada 20 min. @@ -1614,7 +1696,7 @@ set passcode view Choose file - Elije archivo + Elegir archivo No comment provided by engineer. @@ -1812,9 +1894,9 @@ set passcode view Conectar automáticamente No comment provided by engineer. - - Connect incognito - Conectar incognito + + Connect faster! 🚀 + ¡Conéctate más rápido! 🚀 No comment provided by engineer. @@ -1824,12 +1906,7 @@ set passcode view Connect to your friends faster. - Conecta más rápido con tus amigos. - No comment provided by engineer. - - - Connect to yourself? - ¿Conectarte a tí mismo? + Conéctate más rápido con tus amigos. No comment provided by engineer. @@ -1837,34 +1914,34 @@ set passcode view This is your own SimpleX address! ¿Conectarte a tí mismo? ¡Esta es tu propia dirección SimpleX! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! ¿Conectarte a tí mismo? ¡Este es tu propio enlace de un solo uso! - No comment provided by engineer. + new chat sheet title Connect via contact address Conectar mediante dirección de contacto - No comment provided by engineer. + new chat sheet title Connect via link Conectar mediante enlace - No comment provided by engineer. + new chat sheet title Connect via one-time link Conectar mediante enlace de un sólo uso - No comment provided by engineer. + new chat sheet title Connect with %@ Conectar con %@ - No comment provided by engineer. + new chat action Connected @@ -1929,7 +2006,7 @@ This is your own one-time link! Connection error Error conexión - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1976,7 +2053,7 @@ This is your own one-time link! Connection timeout Tiempo de conexión agotado - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2028,6 +2105,11 @@ This is your own one-time link! Preferencias de contacto No comment provided by engineer. + + Contact requests from groups + Solicitudes de contacto en grupo + No comment provided by engineer. + Contact will be deleted - this cannot be undone! El contacto será eliminado. ¡No puede deshacerse! @@ -2090,7 +2172,7 @@ This is your own one-time link! Create 1-time link - Crear enlace de un uso + Crear enlace de un solo uso No comment provided by engineer. @@ -2143,9 +2225,9 @@ This is your own one-time link! Crear cola server test step - - Create secret group - Crea grupo secreto + + Create your address + Crea tu dirección No comment provided by engineer. @@ -2402,6 +2484,11 @@ swipe action ¿Eliminar perfil? No comment provided by engineer. + + Delete chat with member? + ¿Eliminar chat con miembro? + alert title + Delete chat? ¿Eliminar chat? @@ -2597,11 +2684,21 @@ swipe action ¡Confirmación de entrega! No comment provided by engineer. + + Deprecated options + Opciones obsoletas + No comment provided by engineer. + Description Descripción No comment provided by engineer. + + Description too large + Descripción demasiado larga + alert title + Desktop address Dirección ordenador @@ -2799,7 +2896,7 @@ swipe action Do not use credentials with proxy. - No uses credenciales con proxy. + No se usan credenciales con proxy. No comment provided by engineer. @@ -2825,7 +2922,7 @@ swipe action Don't show again No volver a mostrar - No comment provided by engineer. + alert action Done @@ -2908,6 +3005,11 @@ chat item action Editar perfil de grupo No comment provided by engineer. + + Empty message! + ¡Mensaje vacío! + No comment provided by engineer. + Enable Activar @@ -2943,6 +3045,11 @@ chat item action Permitir acceso a la cámara No comment provided by engineer. + + Enable disappearing messages by default. + Activa por defecto los mensajes temporales. + No comment provided by engineer. + Enable for all Activar para todos @@ -2955,7 +3062,7 @@ chat item action Enable instant notifications? - ¿Activar notificación instantánea? + ¿Activar notificaciones instantáneas? No comment provided by engineer. @@ -3110,12 +3217,12 @@ chat item action Enter welcome message… - Introduce mensaje de bienvenida… + Deja un mensaje de bienvenida… placeholder Enter welcome message… (optional) - Introduce mensaje de bienvenida… (opcional) + Deja un mensaje de bienvenida… (opcional) placeholder @@ -3143,6 +3250,11 @@ chat item action Error al aceptar solicitud del contacto No comment provided by engineer. + + Error accepting member + Error al aceptar el miembro + alert title + Error adding member(s) Error al añadir miembro(s) @@ -3153,11 +3265,21 @@ chat item action Error al añadir servidor alert title + + Error adding short link + Error al añadir enlace corto + No comment provided by engineer. + Error changing address Error al cambiar servidor No comment provided by engineer. + + Error changing chat profile + Error al cambiar perfil de chat + alert title + Error changing connection profile Error al cambiar el perfil de conexión @@ -3171,7 +3293,7 @@ chat item action Error changing setting Error cambiando configuración - No comment provided by engineer. + alert title Error changing to incognito! @@ -3186,7 +3308,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Error al conectar con el servidor de reenvío %@. Por favor, inténtalo más tarde. - No comment provided by engineer. + alert message Error creating address @@ -3233,15 +3355,20 @@ chat item action Error al descifrar el archivo No comment provided by engineer. + + Error deleting chat + Error al eliminar el chat con el miembro + alert title + Error deleting chat database Error al eliminar base de datos - No comment provided by engineer. + alert title Error deleting chat! ¡Error al eliminar chat! - No comment provided by engineer. + alert title Error deleting connection @@ -3251,12 +3378,12 @@ chat item action Error deleting database Error al eliminar base de datos - No comment provided by engineer. + alert title Error deleting old database Error al eliminar base de datos antigua - No comment provided by engineer. + alert title Error deleting token @@ -3291,7 +3418,7 @@ chat item action Error exporting chat database Error al exportar base de datos - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3301,7 +3428,7 @@ chat item action Error importing chat database Error al importar base de datos - No comment provided by engineer. + alert title Error joining group @@ -3323,6 +3450,11 @@ chat item action Error al abrir chat No comment provided by engineer. + + Error opening group + Error al abrir el grupo + No comment provided by engineer. + Error receiving file Error al recibir archivo @@ -3343,10 +3475,15 @@ chat item action Error al registrarse para notificaciones alert title + + Error rejecting contact request + Error al rechazar la solicitud del contacto + alert title + Error removing member Error al expulsar miembro - No comment provided by engineer. + alert title Error reordering lists @@ -3418,6 +3555,11 @@ chat item action Error al enviar mensaje No comment provided by engineer. + + Error setting auto-accept + Error al configurar auto aceptar + No comment provided by engineer. + Error setting delivery receipts! ¡Error al configurar confirmaciones de entrega! @@ -3436,7 +3578,7 @@ chat item action Error switching profile Error al cambiar perfil - No comment provided by engineer. + alert title Error switching profile! @@ -3500,6 +3642,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Error: la URL no es válida @@ -3679,6 +3825,11 @@ snd error text Archivos y multimedia chat feature + + Files and media are prohibited in this chat. + Los archivos y multimedia no están permitidos en este chat. + No comment provided by engineer. + Files and media are prohibited. Los archivos y multimedia no están permitidos en este grupo. @@ -3719,6 +3870,23 @@ snd error text Encuentra chats mas rápido No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Posiblemente la huella del certificado en la dirección del servidor es incorrecta + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Reparar @@ -3830,9 +3998,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - El servidor de reenvío %@ no ha podido conectarse al servidor de destino %@. Por favor, intentalo más tarde. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + El servidor de reenvío %1$@ no ha podido conectarse al servidor de destino %2$@. Por favor, intentalo más tarde. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3926,7 +4094,7 @@ Error: %2$@ Group already exists! ¡El grupo ya existe! - No comment provided by engineer. + new chat sheet title Group display name @@ -3993,6 +4161,11 @@ Error: %2$@ El perfil de grupo se almacena en los dispositivos, no en los servidores. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + El perfil del grupo ha cambiado. Si lo guardas, el perfil actualizado se enviará a los miembros del grupo. + alert message + Group welcome message Mensaje de bienvenida en grupos @@ -4055,7 +4228,7 @@ Error: %2$@ Hide: - Ocultar: + Oculta: No comment provided by engineer. @@ -4375,7 +4548,7 @@ More improvements are coming soon! Invalid link Enlace no válido - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4485,32 +4658,27 @@ More improvements are coming soon! Join - Unirte + Unirme swipe action + + Join as %@ + Unirme como %@ + No comment provided by engineer. + Join group - Unirte al grupo - No comment provided by engineer. + Unirme al grupo + new chat sheet title Join group conversations Unirse a la conversación del grupo No comment provided by engineer. - - Join group? - ¿Unirte al grupo? - No comment provided by engineer. - Join incognito - Unirte en modo incógnito - No comment provided by engineer. - - - Join with current profile - Unirte con el perfil actual + Unirme en modo incógnito No comment provided by engineer. @@ -4518,7 +4686,7 @@ More improvements are coming soon! This is your link for group %@! ¿Unirse a tu grupo? ¡Este es tu enlace para el grupo %@! - No comment provided by engineer. + new chat action Joining group @@ -4542,9 +4710,14 @@ This is your link for group %@! Keep unused invitation? - ¿Guardar invitación no usada? + ¿Guardar enlace no usado? alert title + + Keep your chats clean + Mantén los chats limpios + No comment provided by engineer. + Keep your connections Conserva tus conexiones @@ -4600,6 +4773,11 @@ This is your link for group %@! ¿Salir del grupo? No comment provided by engineer. + + Less traffic on mobile networks. + Menos tráfico en redes móviles. + No comment provided by engineer. + Let's talk in SimpleX Chat Hablemos en SimpleX Chat @@ -4655,6 +4833,11 @@ This is your link for group %@! Mensajes en vivo No comment provided by engineer. + + Loading profile… + Cargando perfil. . . + in progress text + Local name Nombre local @@ -4730,11 +4913,26 @@ This is your link for group %@! Miembro No comment provided by engineer. + + Member %@ + Miembro %@ + past/unknown group member + + + Member admission + Admisión de miembros + No comment provided by engineer. + Member inactive Miembro inactivo item status text + + Member is deleted - can't accept request + Miembro eliminado, no puede aceptar solicitudes + No comment provided by engineer. + Member reports Informes de miembros @@ -4742,7 +4940,7 @@ This is your link for group %@! Member role will be changed to "%@". All chat members will be notified. - El rol del miembro cambiará a "%@" y todos serán notificados. + El rol del miembro cambiará a "%@". Se notificará en el chat. No comment provided by engineer. @@ -4765,6 +4963,11 @@ This is your link for group %@! El miembro será expulsado del grupo. ¡No puede deshacerse! No comment provided by engineer. + + Member will join the group, accept member? + El miembro se unirá al grupo, ¿aceptas al miembro? + alert message + Members can add message reactions. Los miembros pueden añadir reacciones a los mensajes. @@ -4840,6 +5043,11 @@ This is your link for group %@! Mensaje reenviado item status text + + Message instantly once you tap Connect. + Tras pulsar Contactar, mensajea ya. + No comment provided by engineer. + Message may be delivered later if member becomes active. El mensaje podría ser entregado más tarde si el miembro vuelve a estar activo. @@ -4915,9 +5123,14 @@ This is your link for group %@! Mensajes No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Los mensajes están protegidos mediante **cifrado de extremo a extremo**. + No comment provided by engineer. + Messages from %@ will be shown! - ¡Los mensajes de %@ serán mostrados! + ¡Los mensajes nuevos de %@ serán mostrados! No comment provided by engineer. @@ -5170,6 +5383,11 @@ This is your link for group %@! Eventos nuevos notification + + New group role: Moderator + Nuevo rol de grupo: Moderador + No comment provided by engineer. + New in %@ Nuevo en %@ @@ -5185,6 +5403,11 @@ This is your link for group %@! Nuevo rol de miembro No comment provided by engineer. + + New member wants to join the group. + Un miembro nuevo desea unirse al grupo. + rcv group event chat item + New message Mensaje nuevo @@ -5225,6 +5448,11 @@ This is your link for group %@! Sin chats en la lista %@ No comment provided by engineer. + + No chats with members + Sin chats + No comment provided by engineer. + No contacts selected Ningún contacto seleccionado @@ -5305,6 +5533,11 @@ This is your link for group %@! Sin permiso para grabar mensajes de voz No comment provided by engineer. + + No private routing session + Ninguna sesión con enrutamiento privado + alert title + No push server Sin servidores push @@ -5417,7 +5650,9 @@ This is your link for group %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5508,6 +5743,11 @@ Requiere activación de la VPN. Sólo tú puedes enviar mensajes temporales. No comment provided by engineer. + + Only you can send files and media. + Sólo tú puedes enviar archivos y multimedia. + No comment provided by engineer. + Only you can send voice messages. Sólo tú puedes enviar mensajes de voz. @@ -5533,6 +5773,11 @@ Requiere activación de la VPN. Sólo tu contacto puede enviar mensajes temporales. No comment provided by engineer. + + Only your contact can send files and media. + Sólo tu contacto puede enviar archivos y multimedia. + No comment provided by engineer. + Only your contact can send voice messages. Sólo tu contacto puede enviar mensajes de voz. @@ -5556,25 +5801,36 @@ Requiere activación de la VPN. Open chat Abrir chat - No comment provided by engineer. + new chat action Open chat console Abrir consola de Chat authentication reason + + Open clean link + Abrir enlace limpio + alert action + Open conditions Abrir condiciones No comment provided by engineer. + + Open full link + Abrir enlace completo + alert action + Open group Grupo abierto - No comment provided by engineer. + new chat action Open link? + ¿Abrir enlace? alert title @@ -5582,6 +5838,36 @@ Requiere activación de la VPN. Abrir menú migración a otro dispositivo authentication reason + + Open new chat + Abrir chat nuevo + new chat action + + + Open new group + Abrir grupo nuevo + new chat action + + + Open to accept + Abrir para aceptar + No comment provided by engineer. + + + Open to connect + Abrir para conectar + No comment provided by engineer. + + + Open to join + Abre para unirte + No comment provided by engineer. + + + Open to use bot + Abre para usar el bot + No comment provided by engineer. + Opening app… Iniciando aplicación… @@ -5689,11 +5975,6 @@ Requiere activación de la VPN. Contraseña para hacerlo visible No comment provided by engineer. - - Past member %@ - Miembro pasado %@ - past/unknown group member - Paste desktop address Pegar dirección de ordenador @@ -5764,7 +6045,7 @@ Por favor, comparte cualquier otro problema con los desarrolladores. Please check your network connection with %@ and try again. Comprueba tu conexión de red con %@ e inténtalo de nuevo. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5800,7 +6081,7 @@ Error: %@ Please remember or store it securely - there is no way to recover a lost passcode! - Por favor, recuerda y guarda el código de acceso en un lugar seguro. ¡No hay forma de recuperar un código perdido! + Por favor, recuerda y guarda el código de acceso en un lugar seguro. ¡No hay manera de recuperar un código perdido! No comment provided by engineer. @@ -5815,12 +6096,12 @@ Error: %@ Please store passphrase securely, you will NOT be able to access chat if you lose it. - Guarda la contraseña de forma segura, NO podrás acceder al chat si la pierdes. + Guarda la contraseña de forma segura, NO podrás acceder al chat si se pierde. No comment provided by engineer. Please store passphrase securely, you will NOT be able to change it if you lose it. - Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes. + Guarda la contraseña de forma segura, NO podrás cambiarla si se pierde. No comment provided by engineer. @@ -5828,6 +6109,11 @@ Error: %@ Por favor, intenta desactivar y reactivar las notificaciones. token info + + Please wait for group moderators to review your request to join the group. + Por favor, espera a que tu solicitud sea revisada por los moderadores del grupo. + snd group event chat item + Please wait for token activation to complete. Por favor, espera a que el token de activación se complete. @@ -5848,11 +6134,6 @@ Error: %@ Puerto No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Posiblemente la huella del certificado en la dirección del servidor es incorrecta - server test error - Preserve the last message draft, with attachments. Conserva el último borrador del mensaje con los datos adjuntos. @@ -5936,7 +6217,12 @@ Error: %@ Private routing error Error de enrutamiento privado - No comment provided by engineer. + alert title + + + Private routing timeout + Timeout enrutamiento privado + alert title Profile and server connections @@ -6040,6 +6326,11 @@ Actívalo en ajustes de *Servidores y Redes*. ¡Protege tus perfiles con contraseña! No comment provided by engineer. + + Protocol background timeout + Timeout protocolo en segundo plano + No comment provided by engineer. + Protocol timeout Timeout protocolo @@ -6268,7 +6559,8 @@ Actívalo en ajustes de *Servidores y Redes*. Reject Rechazar - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6279,7 +6571,12 @@ swipe action Reject contact request Rechazar solicitud de contacto - No comment provided by engineer. + alert title + + + Reject member? + ¿Rechazar al miembro? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6306,6 +6603,11 @@ swipe action Eliminar imagen No comment provided by engineer. + + Remove link tracking + Limpiar enlaces de seguimiento + No comment provided by engineer. + Remove member Expulsar miembro @@ -6321,6 +6623,11 @@ swipe action ¿Eliminar contraseña de Keychain? No comment provided by engineer. + + Removes messages and blocks members. + Elimina mensajes y bloquea miembros. + No comment provided by engineer. + Renegotiate Renegociar @@ -6336,11 +6643,6 @@ swipe action ¿Renegociar cifrado? No comment provided by engineer. - - Repeat connection request? - ¿Repetir solicitud de conexión? - No comment provided by engineer. - Repeat download Repetir descarga @@ -6351,11 +6653,6 @@ swipe action Repetir importación No comment provided by engineer. - - Repeat join request? - ¿Repetir solicitud de admisión? - No comment provided by engineer. - Repeat upload Repetir subida @@ -6391,6 +6688,11 @@ swipe action ¿Motivo del informe? No comment provided by engineer. + + Report sent to moderators + Informe enviado a los moderadores + alert title + Report spam: only group moderators will see it. Informar de spam: sólo los moderadores del grupo lo verán. @@ -6494,7 +6796,7 @@ swipe action Retry Reintentar - No comment provided by engineer. + alert action Reveal @@ -6506,6 +6808,21 @@ swipe action Revisar condiciones No comment provided by engineer. + + Review group members + Revisa los miembros del grupo + No comment provided by engineer. + + + Review members + Revisar miembros + admission stage + + + Review members before admitting ("knocking"). + Revisa a los miembros antes de admitirles en el grupo. + admission stage description + Revoke Revocar @@ -6562,6 +6879,16 @@ chat item action Guardar (y notificar contactos) alert button + + Save (and notify members) + Guardar (y notificar miembros) + alert button + + + Save admission settings? + ¿Guardar configuración? + alert title + Save and notify contact Guardar y notificar contacto @@ -6587,6 +6914,11 @@ chat item action Guardar perfil de grupo No comment provided by engineer. + + Save group profile? + ¿Guardar perfil del grupo? + alert title + Save list Guardar lista @@ -6782,6 +7114,11 @@ chat item action Envía un mensaje en vivo: se actualizará para el (los) destinatario(s) a medida que se escribe No comment provided by engineer. + + Send contact request? + ¿Enviar solicitud de contacto? + No comment provided by engineer. + Send delivery receipts to Enviar confirmaciones de entrega a @@ -6847,6 +7184,16 @@ chat item action Enviar confirmaciones No comment provided by engineer. + + Send request + Enviar solicitud + No comment provided by engineer. + + + Send request without message + Enviar solicitud sin mensaje + No comment provided by engineer. + Send them from gallery or custom keyboards. Envíalos desde la galería o desde teclados personalizados. @@ -6857,6 +7204,11 @@ chat item action Se envían hasta 100 mensajes más recientes a los miembros nuevos. No comment provided by engineer. + + Send your private feedback to groups. + Envía tu comentario privado a los grupos. + No comment provided by engineer. + Sender cancelled file transfer. El remitente ha cancelado la transferencia de archivos. @@ -6997,13 +7349,13 @@ chat item action El protocolo del servidor ha cambiado. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. El servidor requiere autorización para crear colas, comprueba la contraseña server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. El servidor requiere autorización para subir, comprueba la contraseña server test error @@ -7077,6 +7429,11 @@ chat item action Úsalo en lugar de la autenticación del sistema. No comment provided by engineer. + + Set member admission + Admisión miembro + No comment provided by engineer. + Set message expiration in chats. Establece el vencimiento para los mensajes en los chats. @@ -7097,6 +7454,11 @@ chat item action Escribe la contraseña para exportar No comment provided by engineer. + + Set profile bio and welcome message. + Añade mensaje de bienvenida y biografía del perfil. + No comment provided by engineer. + Set the message shown to new members! ¡Guarda un mensaje para ser mostrado a los miembros nuevos! @@ -7168,6 +7530,16 @@ chat item action Compartir enlace No comment provided by engineer. + + Share old address + Compartir dirección antigua + alert button + + + Share old link + Comparte enlace completo + alert button + Share profile Perfil a compartir @@ -7188,6 +7560,21 @@ chat item action Compartir con contactos No comment provided by engineer. + + Share your address + Comparte tu dirección + No comment provided by engineer. + + + Short SimpleX address + Direcciones SimpleX cortas + No comment provided by engineer. + + + Short description + Descripción corta + No comment provided by engineer. + Short link Enlace corto @@ -7235,7 +7622,7 @@ chat item action Show: - Mostrar: + Muestra: No comment provided by engineer. @@ -7290,9 +7677,14 @@ chat item action SimpleX address or 1-time link? - ¿Dirección SimpleX o enlace de un uso? + ¿Dirección SimpleX o enlace de un solo uso? No comment provided by engineer. + + SimpleX address settings + Auto aceptar configuración + alert title + SimpleX channel link Enlace de canal SimpleX @@ -7338,6 +7730,11 @@ chat item action Protocolos de SimpleX auditados por Trail of Bits. No comment provided by engineer. + + SimpleX relay link + Enlace de servidor SimpleX + simplex link type + Simplified incognito mode Modo incógnito simplificado @@ -7551,6 +7948,11 @@ report reason Conexión TCP No comment provided by engineer. + + TCP connection bg timeout + Timeout conexión TCP sp + No comment provided by engineer. + TCP connection timeout Timeout de la conexión TCP @@ -7583,7 +7985,22 @@ report reason Take picture - Tomar foto + Hacer foto + No comment provided by engineer. + + + Tap Connect to chat + Pulsa Conectar para chatear + No comment provided by engineer. + + + Tap Connect to send request + Pulsa Conectar para enviar solicitud + No comment provided by engineer. + + + Tap Connect to use bot + Pulsa Conectar para usar el bot No comment provided by engineer. @@ -7591,6 +8008,11 @@ report reason Pulsa Crear dirección SimpleX en el menú para crearla más tarde. No comment provided by engineer. + + Tap Join group + Pulsa Unirme al grupo + No comment provided by engineer. + Tap button Pulsa el botón @@ -7618,7 +8040,7 @@ report reason Tap to paste link - Pulsa para pegar el enlacePulsa para pegar enlace + Pulsa aquí para pegar el enlace No comment provided by engineer. @@ -7668,7 +8090,7 @@ report reason Thanks to the users – contribute via Weblate! - ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate + ¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate No comment provided by engineer. @@ -7678,6 +8100,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + La dirección pasará a ser corta y tu perfil será compartido mediante la dirección. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo. @@ -7738,6 +8165,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El hash del mensaje anterior es diferente. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + El enlace será corto y el perfil del grupo se compartirá mediante el enlace. + alert message + The message will be deleted for all members. El mensaje se eliminará para todos los miembros. @@ -7781,7 +8213,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The sender will NOT be notified El remitente NO será notificado - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7860,7 +8292,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This display name is invalid. Please choose another name. - Éste nombre mostrado no es válido. Por favor, elije otro nombre. + Éste nombre mostrado no es válido. Por favor, elige otro nombre. No comment provided by engineer. @@ -7873,16 +8305,6 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Este grupo ya no existe. No comment provided by engineer. - - This is your own SimpleX address! - ¡Esta es tu propia dirección SimpleX! - No comment provided by engineer. - - - This is your own one-time link! - ¡Este es tu propio enlace de un solo uso! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible. @@ -7903,6 +8325,16 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Esta configuración se aplica a los mensajes del perfil actual **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + Esta configuración se aplica al perfil actual **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Mensajes temporales activados sólo para los contactos nuevos. + No comment provided by engineer. + Title Título @@ -7985,11 +8417,21 @@ Se te pedirá que completes la autenticación antes de activar esta función.Para enviar No comment provided by engineer. + + To send commands you must be connected. + Para enviar comandos debes estar conectado. + alert message + To support instant push notifications the chat database has to be migrated. Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Para usar otro perfil tras el intento de conexión, elimina el chat y usa el enlace de nuevo. + alert message + To use the servers of **%@**, accept conditions of use. Para usar los servidores de **%@**, debes aceptar las condiciones de uso. @@ -8082,7 +8524,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unblock member for all? - ¿Desbloquear el miembro para todos? + ¿Desbloquear al miembro para todos? No comment provided by engineer. @@ -8227,11 +8669,41 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Para actualizar la configuración el cliente se reconectará a todos los servidores. No comment provided by engineer. + + Upgrade + Actualizar + alert button + + + Upgrade address + Actualizar dirección + No comment provided by engineer. + + + Upgrade address? + ¿Actualizar la dirección? + alert message + Upgrade and open chat Actualizar y abrir Chat No comment provided by engineer. + + Upgrade group link? + ¿Actualizar enlace de grupo? + alert message + + + Upgrade link + Añadir enlace + No comment provided by engineer. + + + Upgrade your address + Actualiza tu dirección + No comment provided by engineer. + Upload errors Errores en subida @@ -8300,7 +8772,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use current profile Usar perfil actual - No comment provided by engineer. + new chat action Use for files @@ -8327,10 +8799,15 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar interfaz de llamada de iOS No comment provided by engineer. + + Use incognito profile + Usar perfil incógnito + No comment provided by engineer. + Use new incognito profile Usar nuevo perfil incógnito - No comment provided by engineer. + new chat action Use only local notifications? @@ -8357,11 +8834,6 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar servidores No comment provided by engineer. - - Use short links (BETA) - Usar enlaces cortos (BETA) - No comment provided by engineer. - Use the app while in the call. Usar la aplicación durante la llamada. @@ -8567,6 +9039,11 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Mensaje de bienvenida demasiado largo No comment provided by engineer. + + Welcome your contacts 👋 + Da la bienvenida a tus contactos 👋 + No comment provided by engineer. + What's new Novedades @@ -8690,12 +9167,12 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión You are already connecting to %@. Ya estás conectando con %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! ¡Ya estás conectando mediante este enlace de un solo uso! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8705,24 +9182,19 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión You are already joining the group %@. Ya estás uniéndote al grupo %@. - No comment provided by engineer. - - - You are already joining the group via this link! - ¡Ya estás uniéndote al grupo mediante este enlace! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Ya estás uniéndote al grupo mediante este enlace. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? ¡En proceso de unirte al grupo! ¿Repetir solicitud de admisión? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8806,7 +9278,7 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Puedes compartir un enlace o código QR para que cualquiera pueda unirse al grupo. Si decides eliminarlo más tarde, los miembros del grupo se mantendrán. + Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. No comment provided by engineer. @@ -8836,13 +9308,18 @@ Repeat join request? You can view invitation link again in connection details. - Podrás ver el enlace de invitación en detalles de conexión. + Puedes ver el enlace de invitación de nuevo en los detalles de la conexión. + alert message + + + You can view your reports in Chat with admins. + Puedes ver tus informes en Chat con administradores. alert message You can't send messages! ¡No puedes enviar mensajes! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8854,17 +9331,12 @@ Repeat join request? Tu decides quién se conecta. No comment provided by engineer. - - You have already requested connection via this address! - ¡Ya has solicitado la conexión mediante esta dirección! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Ya has solicitado la conexión ¿Repetir solicitud? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8926,6 +9398,11 @@ Repeat connection request? Deberías recibir notificaciones. token info + + You will be able to send messages **only after your request is accepted**. + Podrás enviar mensajes **después de que tu solicitud sea aceptada**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o revisa más tarde. @@ -8951,11 +9428,6 @@ Repeat connection request? Se te pedirá autenticarte cuando inicies la aplicación o sigas usándola tras 30 segundos en segundo plano. No comment provided by engineer. - - You will connect to all group members. - Te conectarás con todos los miembros del grupo. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos. @@ -8963,12 +9435,12 @@ Repeat connection request? You will stop receiving messages from this chat. Chat history will be preserved. - Dejarás de recibir mensajes de este chat. El historial del chat se conserva. + Dejarás de recibir mensajes del chat. El historial del chat se conserva. No comment provided by engineer. You will stop receiving messages from this group. Chat history will be preserved. - Dejarás de recibir mensajes de este grupo. El historial del chat se conservará. + Dejarás de recibir mensajes del grupo. El historial del chat se conservará. No comment provided by engineer. @@ -8996,6 +9468,11 @@ Repeat connection request? Mi dirección SimpleX No comment provided by engineer. + + Your business contact + Mi contacto empresarial + No comment provided by engineer. + Your calls Llamadas @@ -9021,11 +9498,21 @@ Repeat connection request? Mis perfiles No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Tu chat ha sido movido a %@ pero ha ocurrido un error inesperado mientras se te redirigía al perfil. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Tu conexión ha sido trasladada a %@ pero ha ocurrido un error inesperado al redirigirte al perfil. No comment provided by engineer. + + Your contact + Mi contacto + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). El contacto ha enviado un archivo mayor al máximo admitido (%@). @@ -9056,6 +9543,11 @@ Repeat connection request? Tu perfil actual No comment provided by engineer. + + Your group + Mi grupo + No comment provided by engineer. + Your preferences Mis preferencias @@ -9083,7 +9575,7 @@ Repeat connection request? Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. - Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. + Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. No comment provided by engineer. @@ -9141,6 +9633,11 @@ Repeat connection request? y después elige: No comment provided by engineer. + + accepted %@ + %@ aceptado + rcv group event chat item + accepted call llamada aceptada @@ -9151,6 +9648,11 @@ Repeat connection request? invitación aceptada chat list item title + + accepted you + te ha aceptado + rcv group event chat item + admin administrador @@ -9171,6 +9673,11 @@ Repeat connection request? acordando cifrado… chat item text + + all + todos + member criteria value + all members todos los miembros @@ -9257,6 +9764,11 @@ marked deleted chat item preview text llamando… call status + + can't send messages + no se pueden enviar mensajes + No comment provided by engineer. + cancelled %@ cancelado %@ @@ -9307,11 +9819,6 @@ marked deleted chat item preview text conectado No comment provided by engineer. - - connected directly - conectado directamente - rcv group event chat item - connecting conectando... @@ -9362,6 +9869,16 @@ marked deleted chat item preview text el contacto %1$@ ha cambiado a %2$@ profile update event chat item + + contact deleted + contacto eliminado + No comment provided by engineer. + + + contact disabled + contacto desactivado + No comment provided by engineer. + contact has e2e encryption el contacto dispone de cifrado de extremo a extremo @@ -9372,6 +9889,16 @@ marked deleted chat item preview text el contacto no dispone de cifrado de extremo a extremo No comment provided by engineer. + + contact not ready + en espera de ser aceptado + No comment provided by engineer. + + + contact should accept… + el contacto debe aceptarte… + No comment provided by engineer. + creator creador @@ -9538,11 +10065,21 @@ pref value reenviado No comment provided by engineer. + + group + grupo + shown on group welcome message + group deleted grupo eliminado No comment provided by engineer. + + group is deleted + el grupo ha sido eliminado + No comment provided by engineer. + group profile updated perfil de grupo actualizado @@ -9638,11 +10175,6 @@ pref value cursiva No comment provided by engineer. - - join as %@ - unirte como %@ - No comment provided by engineer. - left ha salido @@ -9668,6 +10200,11 @@ pref value conectado rcv group event chat item + + member has old version + el miembro usa una versión antigua + No comment provided by engineer. + message mensaje @@ -9733,6 +10270,11 @@ pref value sin texto copied message info in history + + not synchronized + no sincronizado + No comment provided by engineer. + observer observador @@ -9743,6 +10285,7 @@ pref value desactivado enabled status group pref value +member criteria value time to disappear @@ -9795,6 +10338,11 @@ time to disappear pendiente de aprobación No comment provided by engineer. + + pending review + pendiente de revisión + No comment provided by engineer. + quantum resistant e2e encryption cifrado e2e resistente a tecnología cuántica @@ -9835,6 +10383,11 @@ time to disappear dirección de contacto eliminada profile update event chat item + + removed from group + expulsado del grupo + No comment provided by engineer. + removed profile picture ha eliminado la imagen del perfil @@ -9845,11 +10398,41 @@ time to disappear te ha expulsado rcv group event chat item + + request is sent + petición enviada + No comment provided by engineer. + + + request to join rejected + petición para unirse rechazada + No comment provided by engineer. + + + requested connection + conexión solicitada + rcv group event chat item + + + requested connection from group %@ + conexión solicitada desde el grupo %@ + rcv direct event chat item + requested to connect solicitado para conectar chat list item title + + review + por revisar + No comment provided by engineer. + + + reviewed by admins + en revisión por los administradores + No comment provided by engineer. + saved guardado @@ -9885,11 +10468,6 @@ time to disappear código de seguridad cambiado chat item text - - send direct message - Enviar mensaje directo - No comment provided by engineer. - server queue info: %1$@ @@ -10039,10 +10617,10 @@ last received msg: %2$@ tu No comment provided by engineer. - - you are invited to group - has sido invitado a un grupo - No comment provided by engineer. + + you accepted this member + has aceptado al miembro + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index a54666bb10..6e400a6078 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -525,8 +525,17 @@ time interval Hyväksy accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + alert action + + + Accept as observer + alert action + Accept conditions No comment provided by engineer. @@ -536,6 +545,10 @@ swipe action Hyväksy yhteyspyyntö? No comment provided by engineer. + + Accept contact request + alert title + Accept contact request from %@? Hyväksy kontaktipyyntö %@:ltä? @@ -544,9 +557,13 @@ swipe action Accept incognito Hyväksy tuntematon - accept contact request via notification + alert action swipe action + + Accept member + alert title + Accepted conditions No comment provided by engineer. @@ -580,6 +597,10 @@ swipe action Add list No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile Lisää profiili @@ -771,6 +792,10 @@ swipe action Allow downgrade No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle. (24 tuntia) @@ -853,6 +878,10 @@ swipe action Salli kontaktiesi lähettää katoavia viestejä. No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. Salli kontaktiesi lähettää ääniviestejä. @@ -865,11 +894,11 @@ swipe action Already connecting! - No comment provided by engineer. + new chat sheet title Already joining the group! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1058,10 +1087,6 @@ swipe action Hyväksy kuvat automaattisesti No comment provided by engineer. - - Auto-accept settings - alert title - Back Takaisin @@ -1126,6 +1151,14 @@ swipe action Better user experience No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black No comment provided by engineer. @@ -1166,6 +1199,10 @@ swipe action Blur media No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Sekä sinä että kontaktisi voivat käyttää viestireaktioita. @@ -1186,6 +1223,10 @@ swipe action Sekä sinä että kontaktisi voitte lähettää katoavia viestejä. No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. Sekä sinä että kontaktisi voitte lähettää ääniviestejä. @@ -1203,6 +1244,10 @@ swipe action Business chats No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses No comment provided by engineer. @@ -1244,6 +1289,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Kontaktia ei voi kutsua! @@ -1262,7 +1311,8 @@ swipe action Cancel Peruuta alert action -alert button +alert button +new chat action Cancel migration @@ -1359,7 +1409,7 @@ set passcode view Chat already exists! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1437,11 +1487,27 @@ set passcode view Chat will be deleted for you - this cannot be undone! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats Keskustelut No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1639,9 +1705,8 @@ set passcode view Connect automatically No comment provided by engineer. - - Connect incognito - Yhdistä Incognito + + Connect faster! 🚀 No comment provided by engineer. @@ -1652,37 +1717,33 @@ set passcode view Connect to your friends faster. No comment provided by engineer. - - Connect to yourself? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! - No comment provided by engineer. + new chat sheet title Connect via contact address - No comment provided by engineer. + new chat sheet title Connect via link Yhdistä linkin kautta - No comment provided by engineer. + new chat sheet title Connect via one-time link Yhdistä kertalinkillä - No comment provided by engineer. + new chat sheet title Connect with %@ - No comment provided by engineer. + new chat action Connected @@ -1738,7 +1799,7 @@ This is your own one-time link! Connection error Yhteysvirhe - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1778,7 +1839,7 @@ This is your own one-time link! Connection timeout Yhteyden aikakatkaisu - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1826,6 +1887,10 @@ This is your own one-time link! Kontaktin asetukset No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! No comment provided by engineer. @@ -1931,9 +1996,8 @@ This is your own one-time link! Luo jono server test step - - Create secret group - Luo salainen ryhmä + + Create your address No comment provided by engineer. @@ -2174,6 +2238,10 @@ swipe action Poista keskusteluprofiili? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? No comment provided by engineer. @@ -2358,11 +2426,19 @@ swipe action Toimituskuittaukset! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Kuvaus No comment provided by engineer. + + Description too large + alert title + Desktop address No comment provided by engineer. @@ -2565,7 +2641,7 @@ swipe action Don't show again Älä näytä uudelleen - No comment provided by engineer. + alert action Done @@ -2638,6 +2714,10 @@ chat item action Muokkaa ryhmäprofiilia No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable Salli @@ -2671,6 +2751,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Salli kaikille @@ -2858,6 +2942,10 @@ chat item action Virhe kontaktipyynnön hyväksymisessä No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) Virhe lisättäessä jäseniä @@ -2867,11 +2955,19 @@ chat item action Error adding server alert title + + Error adding short link + No comment provided by engineer. + Error changing address Virhe osoitteenvaihdossa No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile No comment provided by engineer. @@ -2884,7 +2980,7 @@ chat item action Error changing setting Virhe asetuksen muuttamisessa - No comment provided by engineer. + alert title Error changing to incognito! @@ -2896,7 +2992,7 @@ chat item action Error connecting to forwarding server %@. Please try later. - No comment provided by engineer. + alert message Error creating address @@ -2939,15 +3035,19 @@ chat item action Virhe tiedoston salauksen purussa No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database Virhe keskustelujen tietokannan poistamisessa - No comment provided by engineer. + alert title Error deleting chat! Virhe keskutelun poistamisessa! - No comment provided by engineer. + alert title Error deleting connection @@ -2957,12 +3057,12 @@ chat item action Error deleting database Virhe tietokannan poistamisessa - No comment provided by engineer. + alert title Error deleting old database Virhe vanhan tietokannan poistamisessa - No comment provided by engineer. + alert title Error deleting token @@ -2996,7 +3096,7 @@ chat item action Error exporting chat database Virhe vietäessä keskustelujen tietokantaa - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3005,7 +3105,7 @@ chat item action Error importing chat database Virhe keskustelujen tietokannan tuonnissa - No comment provided by engineer. + alert title Error joining group @@ -3024,6 +3124,10 @@ chat item action Error opening chat No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file Virhe tiedoston vastaanottamisessa @@ -3041,10 +3145,14 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + alert title + Error removing member Virhe poistettaessa jäsentä - No comment provided by engineer. + alert title Error reordering lists @@ -3109,6 +3217,10 @@ chat item action Virhe viestin lähettämisessä No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Virhe toimituskuittauksien asettamisessa! @@ -3126,7 +3238,7 @@ chat item action Error switching profile - No comment provided by engineer. + alert title Error switching profile! @@ -3186,6 +3298,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Virhe: URL on virheellinen @@ -3345,6 +3461,10 @@ snd error text Tiedostot ja media chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Tiedostot ja media ovat tässä ryhmässä kiellettyjä. @@ -3382,6 +3502,23 @@ snd error text Löydä keskustelut nopeammin No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Korjaa @@ -3478,8 +3615,8 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3560,7 +3697,7 @@ Error: %2$@ Group already exists! - No comment provided by engineer. + new chat sheet title Group display name @@ -3627,6 +3764,10 @@ Error: %2$@ Ryhmäprofiili tallennetaan jäsenten laitteille, ei palvelimille. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Ryhmän tervetuloviesti @@ -3980,7 +4121,7 @@ More improvements are coming soon! Invalid link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4088,32 +4229,29 @@ More improvements are coming soon! Liity swipe action + + Join as %@ + Liity %@:nä + No comment provided by engineer. + Join group Liity ryhmään - No comment provided by engineer. + new chat sheet title Join group conversations No comment provided by engineer. - - Join group? - No comment provided by engineer. - Join incognito Liity incognito-tilassa No comment provided by engineer. - - Join with current profile - No comment provided by engineer. - Join your group? This is your link for group %@! - No comment provided by engineer. + new chat action Joining group @@ -4136,6 +4274,10 @@ This is your link for group %@! Keep unused invitation? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections Pidä kontaktisi @@ -4189,6 +4331,10 @@ This is your link for group %@! Poistu ryhmästä? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Jutellaan SimpleX Chatissa @@ -4238,6 +4384,10 @@ This is your link for group %@! Live-viestit No comment provided by engineer. + + Loading profile… + in progress text + Local name Paikallinen nimi @@ -4311,10 +4461,22 @@ This is your link for group %@! Jäsen No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4342,6 +4504,10 @@ This is your link for group %@! Jäsen poistetaan ryhmästä - tätä ei voi perua! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. Ryhmän jäsenet voivat lisätä viestireaktioita. @@ -4411,6 +4577,10 @@ This is your link for group %@! Message forwarded item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. item status description @@ -4477,6 +4647,10 @@ This is your link for group %@! Viestit ja tiedostot No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! No comment provided by engineer. @@ -4702,6 +4876,10 @@ This is your link for group %@! New events notification + + New group role: Moderator + No comment provided by engineer. + New in %@ Uutta %@ @@ -4716,6 +4894,10 @@ This is your link for group %@! Uusi jäsenrooli No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message Uusi viesti @@ -4752,6 +4934,10 @@ This is your link for group %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected Kontakteja ei ole valittu @@ -4824,6 +5010,10 @@ This is your link for group %@! Ei lupaa ääniviestin tallentamiseen No comment provided by engineer. + + No private routing session + alert title + No push server Paikallinen @@ -4922,7 +5112,9 @@ This is your link for group %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5009,6 +5201,10 @@ Edellyttää VPN:n sallimista. Vain sinä voit lähettää katoavia viestejä. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Vain sinä voit lähettää ääniviestejä. @@ -5034,6 +5230,10 @@ Edellyttää VPN:n sallimista. Vain kontaktisi voi lähettää katoavia viestejä. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Vain kontaktisi voi lähettää ääniviestejä. @@ -5055,20 +5255,28 @@ Edellyttää VPN:n sallimista. Open chat Avaa keskustelu - No comment provided by engineer. + new chat action Open chat console Avaa keskustelukonsoli authentication reason + + Open clean link + alert action + Open conditions No comment provided by engineer. + + Open full link + alert action + Open group - No comment provided by engineer. + new chat action Open link? @@ -5078,6 +5286,30 @@ Edellyttää VPN:n sallimista. Open migration to another device authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… No comment provided by engineer. @@ -5171,10 +5403,6 @@ Edellyttää VPN:n sallimista. Salasana näytettäväksi No comment provided by engineer. - - Past member %@ - past/unknown group member - Paste desktop address No comment provided by engineer. @@ -5236,7 +5464,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. Tarkista verkkoyhteytesi %@:lla ja yritä uudelleen. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5296,6 +5524,10 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5313,11 +5545,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen - server test error - Preserve the last message draft, with attachments. Säilytä viimeinen viestiluonnos liitteineen. @@ -5390,7 +5617,11 @@ Error: %@ Private routing error - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5487,6 +5718,10 @@ Enable in *Network & servers* settings. Suojaa keskusteluprofiilisi salasanalla! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout Protokollan aikakatkaisu @@ -5694,7 +5929,8 @@ Enable in *Network & servers* settings. Reject Hylkää - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -5705,7 +5941,11 @@ swipe action Reject contact request Hylkää yhteyspyyntö - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -5730,6 +5970,10 @@ swipe action Remove image No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Poista jäsen @@ -5745,6 +5989,10 @@ swipe action Poista tunnuslause avainnipusta? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Neuvottele uudelleen @@ -5760,10 +6008,6 @@ swipe action Uudelleenneuvottele salaus? No comment provided by engineer. - - Repeat connection request? - No comment provided by engineer. - Repeat download No comment provided by engineer. @@ -5772,10 +6016,6 @@ swipe action Repeat import No comment provided by engineer. - - Repeat join request? - No comment provided by engineer. - Repeat upload No comment provided by engineer. @@ -5805,6 +6045,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -5897,7 +6141,7 @@ swipe action Retry - No comment provided by engineer. + alert action Reveal @@ -5908,6 +6152,18 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke Peruuta @@ -5960,6 +6216,14 @@ chat item action Tallenna (ja ilmoita kontakteille) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact Tallenna ja ilmoita kontaktille @@ -5984,6 +6248,10 @@ chat item action Tallenna ryhmäprofiili No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6163,6 +6431,10 @@ chat item action Lähetä live-viesti - se päivittyy vastaanottajille, kun kirjoitat sitä No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to Lähetä toimituskuittaukset vastaanottajalle @@ -6222,6 +6494,14 @@ chat item action Lähetä kuittaukset No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Lähetä ne galleriasta tai mukautetuista näppäimistöistä. @@ -6231,6 +6511,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. Lähettäjä peruutti tiedoston siirron. @@ -6358,13 +6642,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Palvelin vaatii valtuutuksen tiedoston lataamiseksi, tarkista salasana server test error @@ -6430,6 +6714,10 @@ chat item action Aseta se järjestelmän todennuksen sijaan. No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -6448,6 +6736,10 @@ chat item action Aseta tunnuslause vientiä varten No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Aseta uusille jäsenille näytettävä viesti! @@ -6513,6 +6805,14 @@ chat item action Jaa linkki No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile No comment provided by engineer. @@ -6530,6 +6830,18 @@ chat item action Jaa kontaktien kanssa No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -6626,6 +6938,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + alert title + SimpleX channel link simplex link type @@ -6667,6 +6983,10 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode No comment provided by engineer. @@ -6857,6 +7177,10 @@ report reason TCP connection No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout TCP-yhteyden aikakatkaisu @@ -6890,10 +7214,26 @@ report reason Ota kuva No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button Napauta painiketta @@ -6976,6 +7316,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön. @@ -7032,6 +7376,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Edellisen viestin tarkiste on erilainen. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. Viesti poistetaan kaikilta jäseniltä. @@ -7071,7 +7419,7 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut. The sender will NOT be notified Lähettäjälle EI ilmoiteta - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7153,14 +7501,6 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Tätä ryhmää ei enää ole olemassa. No comment provided by engineer. - - This is your own SimpleX address! - No comment provided by engineer. - - - This is your own one-time link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7178,6 +7518,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Tämä asetus koskee nykyisen keskusteluprofiilisi viestejä *%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title No comment provided by engineer. @@ -7252,11 +7600,19 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. Keskustelujen-tietokanta on siirrettävä välittömien push-ilmoitusten tukemiseksi. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -7473,11 +7829,35 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Asetusten päivittäminen yhdistää asiakkaan uudelleen kaikkiin palvelimiin. No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat Päivitä ja avaa keskustelu No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors No comment provided by engineer. @@ -7537,7 +7917,7 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use current profile Käytä nykyistä profiilia - No comment provided by engineer. + new chat action Use for files @@ -7561,10 +7941,14 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä iOS:n puhelujen käyttöliittymää No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile Käytä uutta incognito-profiilia - No comment provided by engineer. + new chat action Use only local notifications? @@ -7587,10 +7971,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. No comment provided by engineer. @@ -7776,6 +8156,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Welcome message is too long No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new Uusimmat @@ -7884,11 +8268,11 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja You are already connecting to %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -7896,20 +8280,16 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja You are already joining the group %@. - No comment provided by engineer. - - - You are already joining the group via this link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8017,10 +8397,14 @@ Repeat join request? You can view invitation link again in connection details. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! Et voi lähettää viestejä! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8032,14 +8416,10 @@ Repeat join request? Kimin bağlanabileceğine siz karar verirsiniz. No comment provided by engineer. - - You have already requested connection via this address! - No comment provided by engineer. - You have already requested connection! Repeat connection request? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8097,6 +8477,10 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Sinut yhdistetään ryhmään, kun ryhmän isännän laite on online-tilassa, odota tai tarkista myöhemmin! @@ -8121,10 +8505,6 @@ Repeat connection request? Sinun on tunnistauduttava, kun käynnistät sovelluksen tai jatkat sen käyttöä 30 sekunnin tauon jälkeen. No comment provided by engineer. - - You will connect to all group members. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Saat edelleen puheluita ja ilmoituksia mykistetyiltä profiileilta, kun ne ovat aktiivisia. @@ -8164,6 +8544,10 @@ Repeat connection request? SimpleX-osoitteesi No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls Puhelusi @@ -8188,8 +8572,16 @@ Repeat connection request? Keskusteluprofiilisi No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact No comment provided by engineer. @@ -8221,6 +8613,10 @@ Repeat connection request? Nykyinen profiilisi No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences Asetuksesi @@ -8303,6 +8699,10 @@ Repeat connection request? edellä, valitse sitten: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call hyväksytty puhelu @@ -8312,6 +8712,10 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin ylläpitäjä @@ -8331,6 +8735,10 @@ Repeat connection request? hyväksyy salausta… chat item text + + all + member criteria value + all members feature role @@ -8408,6 +8816,10 @@ marked deleted chat item preview text soittaa… call status + + can't send messages + No comment provided by engineer. + cancelled %@ peruutettu %@ @@ -8458,10 +8870,6 @@ marked deleted chat item preview text yhdistetty No comment provided by engineer. - - connected directly - rcv group event chat item - connecting yhdistää @@ -8511,6 +8919,14 @@ marked deleted chat item preview text contact %1$@ changed to %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption kontaktilla on e2e-salaus @@ -8521,6 +8937,14 @@ marked deleted chat item preview text kontaktilla ei ole e2e-salausta No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator luoja @@ -8682,11 +9106,19 @@ pref value forwarded No comment provided by engineer. + + group + shown on group welcome message + group deleted ryhmä poistettu No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated ryhmäprofiili päivitetty @@ -8780,11 +9212,6 @@ pref value kursivoitu No comment provided by engineer. - - join as %@ - Liity %@:nä - No comment provided by engineer. - left poistunut @@ -8809,6 +9236,10 @@ pref value yhdistetty rcv group event chat item + + member has old version + No comment provided by engineer. + message No comment provided by engineer. @@ -8872,6 +9303,10 @@ pref value ei tekstiä copied message info in history + + not synchronized + No comment provided by engineer. + observer tarkkailija @@ -8882,6 +9317,7 @@ pref value pois enabled status group pref value +member criteria value time to disappear @@ -8929,6 +9365,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -8966,6 +9406,10 @@ time to disappear removed contact address profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture profile update event chat item @@ -8975,10 +9419,34 @@ time to disappear poisti sinut rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved No comment provided by engineer. @@ -9011,10 +9479,6 @@ time to disappear turvakoodi on muuttunut chat item text - - send direct message - No comment provided by engineer. - server queue info: %1$@ @@ -9149,10 +9613,9 @@ last received msg: %2$@ you No comment provided by engineer. - - you are invited to group - sinut on kutsuttu ryhmään - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 59bde0650e..d008696a14 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -563,8 +563,19 @@ time interval Accepter accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Accepter en tant que membre + alert action + + + Accept as observer + Accepter en tant qu'observateur + alert action + Accept conditions Accepter les conditions @@ -575,6 +586,11 @@ swipe action Accepter la demande de connexion ? No comment provided by engineer. + + Accept contact request + Accepter la demande de contact + alert title + Accept contact request from %@? Accepter la demande de contact de %@ ? @@ -583,9 +599,14 @@ swipe action Accept incognito Accepter en incognito - accept contact request via notification + alert action swipe action + + Accept member + Accepter le membre + alert title + Accepted conditions Conditions acceptées @@ -626,6 +647,11 @@ swipe action Ajouter une liste No comment provided by engineer. + + Add message + Ajouter un message + placeholder for sending contact request + Add profile Ajouter un profil @@ -798,6 +824,7 @@ swipe action All servers + Tous les serveurs No comment provided by engineer. @@ -840,6 +867,11 @@ swipe action Autoriser la rétrogradation No comment provided by engineer. + + Allow files and media only if your contact allows them. + Permettre des fichiers et des médias seulement si votre contact les permet. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures) @@ -925,6 +957,11 @@ swipe action Autorise votre contact à envoyer des messages éphémères. No comment provided by engineer. + + Allow your contacts to send files and media. + Permettre à vos contacts d'envoyer des fichiers et des médias. + No comment provided by engineer. + Allow your contacts to send voice messages. Autorise vos contacts à envoyer des messages vocaux. @@ -938,12 +975,12 @@ swipe action Already connecting! Déjà en connexion ! - No comment provided by engineer. + new chat sheet title Already joining the group! Groupe déjà rejoint ! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1149,11 +1186,6 @@ swipe action Images auto-acceptées No comment provided by engineer. - - Auto-accept settings - Paramètres de réception automatique - alert title - Back Retour @@ -1229,6 +1261,14 @@ swipe action Une meilleure expérience pour l'utilisateur No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black Noir @@ -1279,6 +1319,10 @@ swipe action Flouter les médias No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Vous et votre contact pouvez ajouter des réactions aux messages. @@ -1299,6 +1343,10 @@ swipe action Vous et votre contact êtes tous deux en mesure d'envoyer des messages éphémères. No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. Vous et votre contact êtes tous deux en mesure d'envoyer des messages vocaux. @@ -1319,6 +1367,10 @@ swipe action Discussions professionnelles No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses Entreprises @@ -1368,6 +1420,10 @@ swipe action Impossible d'appeler le membre No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Impossible d'inviter le contact ! @@ -1387,7 +1443,8 @@ swipe action Cancel Annuler alert action -alert button +alert button +new chat action Cancel migration @@ -1493,7 +1550,7 @@ set passcode view Chat already exists! La discussion existe déjà ! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1580,11 +1637,27 @@ set passcode view Le discussion sera supprimé pour vous - il n'est pas possible de revenir en arrière ! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats Discussions No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. Consulter les messages toutes les 20 minutes. @@ -1810,9 +1883,8 @@ set passcode view Connexion automatique No comment provided by engineer. - - Connect incognito - Se connecter incognito + + Connect faster! 🚀 No comment provided by engineer. @@ -1825,44 +1897,39 @@ set passcode view Connectez-vous à vos amis plus rapidement. No comment provided by engineer. - - Connect to yourself? - Se connecter à soi-même ? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Se connecter à soi-même ? C'est votre propre adresse SimpleX ! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Se connecter à soi-même ? Il s'agit de votre propre lien unique ! - No comment provided by engineer. + new chat sheet title Connect via contact address Se connecter via l'adresse de contact - No comment provided by engineer. + new chat sheet title Connect via link Se connecter via un lien - No comment provided by engineer. + new chat sheet title Connect via one-time link Se connecter via un lien unique - No comment provided by engineer. + new chat sheet title Connect with %@ Se connecter avec %@ - No comment provided by engineer. + new chat action Connected @@ -1927,7 +1994,7 @@ Il s'agit de votre propre lien unique ! Connection error Erreur de connexion - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1974,7 +2041,7 @@ Il s'agit de votre propre lien unique ! Connection timeout Délai de connexion - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2026,6 +2093,10 @@ Il s'agit de votre propre lien unique ! Préférences de contact No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Le contact sera supprimé - il n'est pas possible de revenir en arrière ! @@ -2141,9 +2212,8 @@ Il s'agit de votre propre lien unique ! Créer une file d'attente server test step - - Create secret group - Créer un groupe secret + + Create your address No comment provided by engineer. @@ -2400,6 +2470,10 @@ swipe action Supprimer le profil du chat ? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? Supprimer la discussion ? @@ -2595,11 +2669,19 @@ swipe action Justificatifs de réception ! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Description No comment provided by engineer. + + Description too large + alert title + Desktop address Adresse de bureau @@ -2823,7 +2905,7 @@ swipe action Don't show again Ne plus afficher - No comment provided by engineer. + alert action Done @@ -2906,6 +2988,10 @@ chat item action Modifier le profil du groupe No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable Activer @@ -2941,6 +3027,10 @@ chat item action Autoriser l'accès à la caméra No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Activer pour tous @@ -3141,6 +3231,10 @@ chat item action Erreur de validation de la demande de contact No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) Erreur lors de l'ajout de membre·s @@ -3151,11 +3245,19 @@ chat item action Erreur lors de l'ajout du serveur alert title + + Error adding short link + No comment provided by engineer. + Error changing address Erreur de changement d'adresse No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile Erreur lors du changement de profil de connexion @@ -3169,7 +3271,7 @@ chat item action Error changing setting Erreur de changement de paramètre - No comment provided by engineer. + alert title Error changing to incognito! @@ -3184,7 +3286,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard. - No comment provided by engineer. + alert message Error creating address @@ -3231,15 +3333,19 @@ chat item action Erreur lors du déchiffrement du fichier No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database Erreur lors de la suppression de la base de données du chat - No comment provided by engineer. + alert title Error deleting chat! Erreur lors de la suppression du chat ! - No comment provided by engineer. + alert title Error deleting connection @@ -3249,12 +3355,12 @@ chat item action Error deleting database Erreur lors de la suppression de la base de données - No comment provided by engineer. + alert title Error deleting old database Erreur lors de la suppression de l'ancienne base de données - No comment provided by engineer. + alert title Error deleting token @@ -3289,7 +3395,7 @@ chat item action Error exporting chat database Erreur lors de l'exportation de la base de données du chat - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3299,7 +3405,7 @@ chat item action Error importing chat database Erreur lors de l'importation de la base de données du chat - No comment provided by engineer. + alert title Error joining group @@ -3321,6 +3427,10 @@ chat item action Erreur lors de l'ouverture du chat No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file Erreur lors de la réception du fichier @@ -3341,10 +3451,14 @@ chat item action Erreur lors de l'inscription aux notifications alert title + + Error rejecting contact request + alert title + Error removing member Erreur lors de la suppression d'un membre - No comment provided by engineer. + alert title Error reordering lists @@ -3416,6 +3530,10 @@ chat item action Erreur lors de l'envoi du message No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Erreur lors de la configuration des accusés de réception ! @@ -3434,7 +3552,7 @@ chat item action Error switching profile Erreur lors du changement de profil - No comment provided by engineer. + alert title Error switching profile! @@ -3498,6 +3616,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Erreur : URL invalide @@ -3677,6 +3799,10 @@ snd error text Fichiers et médias chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Les fichiers et les médias sont interdits dans ce groupe. @@ -3717,6 +3843,23 @@ snd error text Recherche de message plus rapide No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Réparer @@ -3826,9 +3969,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Le serveur de redirection %1$@ n'a pas réussi à se connecter au serveur de destination %2$@. Veuillez réessayer plus tard. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3921,7 +4064,7 @@ Erreur : %2$@ Group already exists! Ce groupe existe déjà ! - No comment provided by engineer. + new chat sheet title Group display name @@ -3988,6 +4131,10 @@ Erreur : %2$@ Le profil du groupe est stocké sur les appareils des membres, pas sur les serveurs. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Message d'accueil du groupe @@ -4360,7 +4507,7 @@ D'autres améliorations sont à venir ! Invalid link Lien invalide - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4473,37 +4620,32 @@ D'autres améliorations sont à venir ! Rejoindre swipe action + + Join as %@ + rejoindre entant que %@ + No comment provided by engineer. + Join group Rejoindre le groupe - No comment provided by engineer. + new chat sheet title Join group conversations Participez aux conversations de groupe No comment provided by engineer. - - Join group? - Rejoindre le groupe ? - No comment provided by engineer. - Join incognito Rejoindre en incognito No comment provided by engineer. - - Join with current profile - Rejoindre avec le profil actuel - No comment provided by engineer. - Join your group? This is your link for group %@! Rejoindre votre groupe ? Voici votre lien pour le groupe %@ ! - No comment provided by engineer. + new chat action Joining group @@ -4530,6 +4672,10 @@ Voici votre lien pour le groupe %@ ! Conserver l'invitation inutilisée ? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections Conserver vos connexions @@ -4585,6 +4731,10 @@ Voici votre lien pour le groupe %@ ! Quitter le groupe ? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Discutons sur SimpleX Chat @@ -4637,6 +4787,10 @@ Voici votre lien pour le groupe %@ ! Messages dynamiques No comment provided by engineer. + + Loading profile… + in progress text + Local name Nom local @@ -4712,11 +4866,23 @@ Voici votre lien pour le groupe %@ ! Membre No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive Membre inactif item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4746,6 +4912,10 @@ Voici votre lien pour le groupe %@ ! Ce membre sera retiré du groupe - impossible de revenir en arrière ! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. Les membres du groupe peuvent ajouter des réactions aux messages. @@ -4819,6 +4989,10 @@ Voici votre lien pour le groupe %@ ! Message transféré item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. Le message peut être transmis plus tard si le membre devient actif. @@ -4894,6 +5068,10 @@ Voici votre lien pour le groupe %@ ! Messages No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! Les messages de %@ seront affichés ! @@ -5145,6 +5323,10 @@ Voici votre lien pour le groupe %@ ! Nouveaux événements notification + + New group role: Moderator + No comment provided by engineer. + New in %@ Nouveautés de la %@ @@ -5160,6 +5342,10 @@ Voici votre lien pour le groupe %@ ! Nouveau rôle No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message Nouveau message @@ -5197,6 +5383,10 @@ Voici votre lien pour le groupe %@ ! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected Aucun contact sélectionné @@ -5276,6 +5466,10 @@ Voici votre lien pour le groupe %@ ! Pas l'autorisation d'enregistrer un message vocal No comment provided by engineer. + + No private routing session + alert title + No push server No push server @@ -5383,7 +5577,9 @@ Voici votre lien pour le groupe %@ ! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5472,6 +5668,10 @@ Nécessite l'activation d'un VPN. Seulement vous pouvez envoyer des messages éphémères. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Vous seul pouvez envoyer des messages vocaux. @@ -5497,6 +5697,10 @@ Nécessite l'activation d'un VPN. Seulement votre contact peut envoyer des messages éphémères. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Seul votre contact peut envoyer des messages vocaux. @@ -5520,22 +5724,30 @@ Nécessite l'activation d'un VPN. Open chat Ouvrir le chat - No comment provided by engineer. + new chat action Open chat console Ouvrir la console du chat authentication reason + + Open clean link + alert action + Open conditions Ouvrir les conditions No comment provided by engineer. + + Open full link + alert action + Open group Ouvrir le groupe - No comment provided by engineer. + new chat action Open link? @@ -5546,6 +5758,30 @@ Nécessite l'activation d'un VPN. Ouvrir le transfert vers un autre appareil authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… Ouverture de l'app… @@ -5652,11 +5888,6 @@ Nécessite l'activation d'un VPN. Mot de passe à entrer No comment provided by engineer. - - Past member %@ - Ancien membre %@ - past/unknown group member - Paste desktop address Coller l'adresse du bureau @@ -5727,7 +5958,7 @@ Veuillez faire part de tout autre problème aux développeurs. Please check your network connection with %@ and try again. Veuillez vérifier votre connexion réseau avec %@ et réessayer. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5790,6 +6021,10 @@ Erreur : %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5808,11 +6043,6 @@ Erreur : %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte - server test error - Preserve the last message draft, with attachments. Conserver le brouillon du dernier message, avec les pièces jointes. @@ -5893,7 +6123,11 @@ Erreur : %@ Private routing error Erreur de routage privé - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5996,6 +6230,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Protégez vos profils de chat par un mot de passe ! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout Délai du protocole @@ -6221,7 +6459,8 @@ Activez-le dans les paramètres *Réseau et serveurs*. Reject Rejeter - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6232,7 +6471,11 @@ swipe action Reject contact request Rejeter la demande de contact - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6259,6 +6502,10 @@ swipe action Enlever l'image No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Retirer le membre @@ -6274,6 +6521,10 @@ swipe action Supprimer la phrase secrète de la keychain ? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Renégocier @@ -6289,11 +6540,6 @@ swipe action Renégocier le chiffrement ? No comment provided by engineer. - - Repeat connection request? - Répéter la demande de connexion ? - No comment provided by engineer. - Repeat download Répéter le téléchargement @@ -6304,11 +6550,6 @@ swipe action Répéter l'importation No comment provided by engineer. - - Repeat join request? - Répéter la requête d'adhésion ? - No comment provided by engineer. - Repeat upload Répéter l'envoi @@ -6339,6 +6580,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -6437,7 +6682,7 @@ swipe action Retry Réessayer - No comment provided by engineer. + alert action Reveal @@ -6449,6 +6694,18 @@ swipe action Vérifier les conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke Révoquer @@ -6505,6 +6762,14 @@ chat item action Enregistrer (et en informer les contacts) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact Enregistrer et en informer le contact @@ -6530,6 +6795,10 @@ chat item action Enregistrer le profil du groupe No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6724,6 +6993,10 @@ chat item action Envoyez un message dynamique - il sera mis à jour pour le⸱s destinataire⸱s au fur et à mesure que vous le tapez No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to Envoyer les accusés de réception à @@ -6788,6 +7061,14 @@ chat item action Envoi de justificatifs No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Envoyez-les depuis la phototèque ou des claviers personnalisés. @@ -6798,6 +7079,10 @@ chat item action Envoi des 100 derniers messages aux nouveaux membres. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. L'expéditeur a annulé le transfert de fichiers. @@ -6938,13 +7223,13 @@ chat item action Le protocole du serveur a été modifié. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Le serveur requiert une autorisation pour téléverser, vérifiez le mot de passe server test error @@ -7017,6 +7302,10 @@ chat item action Il permet de remplacer l'authentification du système. No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -7036,6 +7325,10 @@ chat item action Définir la phrase secrète pour l'export No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Choisissez un message à l'attention des nouveaux membres ! @@ -7107,6 +7400,14 @@ chat item action Partager le lien No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile Partager le profil @@ -7127,6 +7428,18 @@ chat item action Partager avec vos contacts No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -7231,6 +7544,11 @@ chat item action Adresse SimpleX ou lien unique ? No comment provided by engineer. + + SimpleX address settings + Paramètres de réception automatique + alert title + SimpleX channel link simplex link type @@ -7275,6 +7593,10 @@ chat item action Protocoles SimpleX audité par Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode Mode incognito simplifié @@ -7486,6 +7808,10 @@ report reason Connexion TCP No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout Délai de connexion TCP @@ -7520,11 +7846,27 @@ report reason Prendre une photo No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Appuyez sur Créer une adresse SimpleX dans le menu pour la créer ultérieurement. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button Appuyez sur le bouton @@ -7611,6 +7953,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer. @@ -7671,6 +8017,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le hash du message précédent est différent. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. Le message sera supprimé pour tous les membres. @@ -7714,7 +8064,7 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. The sender will NOT be notified L'expéditeur N'en sera PAS informé - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7805,16 +8155,6 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Ce groupe n'existe plus. No comment provided by engineer. - - This is your own SimpleX address! - Voici votre propre adresse SimpleX ! - No comment provided by engineer. - - - This is your own one-time link! - Voici votre propre lien unique ! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7833,6 +8173,14 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Ce paramètre s'applique aux messages de votre profil de chat actuel **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title Titre @@ -7915,11 +8263,19 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Pour envoyer No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. Pour prendre en charge les notifications push instantanées, la base de données du chat doit être migrée. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Pour utiliser les serveurs de **%@**, acceptez les conditions d'utilisation. @@ -8154,11 +8510,35 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien La mise à jour des ces paramètres reconnectera le client à tous les serveurs. No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat Mettre à niveau et ouvrir le chat No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors Erreurs de téléversement @@ -8225,7 +8605,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Use current profile Utiliser le profil actuel - No comment provided by engineer. + new chat action Use for files @@ -8252,10 +8632,14 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser l'interface d'appel d'iOS No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile Utiliser un nouveau profil incognito - No comment provided by engineer. + new chat action Use only local notifications? @@ -8282,10 +8666,6 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser les serveurs No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Utiliser l'application pendant l'appel. @@ -8490,6 +8870,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Le message de bienvenue est trop long No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new Quoi de neuf ? @@ -8613,12 +8997,12 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien You are already connecting to %@. Vous êtes déjà en train de vous connecter à %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Vous êtes déjà connecté(e) via ce lien unique ! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8628,24 +9012,19 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien You are already joining the group %@. Vous êtes déjà en train de rejoindre le groupe %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Vous êtes déjà en train de rejoindre le groupe via ce lien ! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Vous êtes déjà en train de rejoindre le groupe via ce lien. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Vous êtes déjà membre de ce groupe ! Répéter la demande d'adhésion ? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8762,10 +9141,14 @@ Répéter la demande d'adhésion ? Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! Vous ne pouvez pas envoyer de messages ! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8777,17 +9160,12 @@ Répéter la demande d'adhésion ? Vous choisissez qui peut se connecter. No comment provided by engineer. - - You have already requested connection via this address! - Vous avez déjà demandé une connexion via cette adresse ! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Vous avez déjà demandé une connexion ! Répéter la demande de connexion ? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8848,6 +9226,10 @@ Répéter la demande de connexion ? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Vous serez connecté·e au groupe lorsque l'appareil de l'hôte sera en ligne, veuillez attendre ou vérifier plus tard ! @@ -8873,11 +9255,6 @@ Répéter la demande de connexion ? Il vous sera demandé de vous authentifier lorsque vous démarrez ou reprenez l'application après 30 secondes en arrière-plan. No comment provided by engineer. - - You will connect to all group members. - Vous vous connecterez à tous les membres du groupe. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Vous continuerez à recevoir des appels et des notifications des profils mis en sourdine lorsqu'ils sont actifs. @@ -8918,6 +9295,10 @@ Répéter la demande de connexion ? Votre adresse SimpleX No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls Vos appels @@ -8943,11 +9324,19 @@ Répéter la demande de connexion ? Vos profils de chat No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil. No comment provided by engineer. + + Your contact + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@). @@ -8978,6 +9367,10 @@ Répéter la demande de connexion ? Votre profil actuel No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences Vos préférences @@ -9063,6 +9456,10 @@ Répéter la demande de connexion ? ci-dessus, puis choisissez : No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call appel accepté @@ -9073,6 +9470,10 @@ Répéter la demande de connexion ? invitation acceptée chat list item title + + accepted you + rcv group event chat item + admin admin @@ -9093,6 +9494,10 @@ Répéter la demande de connexion ? négociation du chiffrement… chat item text + + all + member criteria value + all members tous les membres @@ -9178,6 +9583,10 @@ marked deleted chat item preview text appel… call status + + can't send messages + No comment provided by engineer. + cancelled %@ annulé %@ @@ -9228,11 +9637,6 @@ marked deleted chat item preview text connecté No comment provided by engineer. - - connected directly - s'est connecté.e de manière directe - rcv group event chat item - connecting connexion @@ -9283,6 +9687,14 @@ marked deleted chat item preview text le contact %1$@ est devenu %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption Ce contact a le chiffrement de bout en bout @@ -9293,6 +9705,14 @@ marked deleted chat item preview text Ce contact n'a pas le chiffrement de bout en bout No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator créateur @@ -9459,11 +9879,19 @@ pref value transféré No comment provided by engineer. + + group + shown on group welcome message + group deleted groupe supprimé No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated mise à jour du profil de groupe @@ -9559,11 +9987,6 @@ pref value italique No comment provided by engineer. - - join as %@ - rejoindre entant que %@ - No comment provided by engineer. - left a quitté @@ -9589,6 +10012,10 @@ pref value est connecté·e rcv group event chat item + + member has old version + No comment provided by engineer. + message message @@ -9653,6 +10080,10 @@ pref value aucun texte copied message info in history + + not synchronized + No comment provided by engineer. + observer observateur @@ -9663,6 +10094,7 @@ pref value off enabled status group pref value +member criteria value time to disappear @@ -9713,6 +10145,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption chiffrement e2e résistant post-quantique @@ -9752,6 +10188,10 @@ time to disappear suppression de l'adresse de contact profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture suppression de la photo de profil @@ -9762,11 +10202,35 @@ time to disappear vous a retiré rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect demande à se connecter chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved enregistré @@ -9802,11 +10266,6 @@ time to disappear code de sécurité modifié chat item text - - send direct message - envoyer un message direct - No comment provided by engineer. - server queue info: %1$@ @@ -9956,10 +10415,9 @@ dernier message reçu : %2$@ vous No comment provided by engineer. - - you are invited to group - vous êtes invité·e au groupe - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index f76d7eba1e..f94d6cefd8 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -2876,8 +2876,8 @@ Available in v5.1 Polish interface No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -3252,12 +3252,12 @@ Available in v5.1 Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. server test error @@ -4466,7 +4466,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -4643,8 +4643,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. @@ -5405,7 +5405,7 @@ SimpleX servers cannot see your profile. Apply to - החל ל + החל על Background @@ -5579,6 +5579,112 @@ This is your own one-time link! File error שגיאה בקובץ + + Corner + קצוות + + + Use for messages + שימוש בהודעות + + + Pending + ממתין + + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + הגן על כתובת ה-IP שלך מפני ממסרי הודעות שנבחרו על ידי אנשי הקשר שלך. +הפעל בהגדרות *רשת ושרתים*. + + + To send + לשליחה + + + Do NOT send messages directly, even if your or destination server does not support private routing. + אל תשלח הודעות ישירות, גם אם שרת היעד שלך או שרת היעד אינו תומך בניתוב פרטי. + + + Servers statistics will be reset - this cannot be undone! + סטטיסטיקות השרתים יאופסו - לא ניתן לבטל פעולה זו! + + + Detailed statistics + סטטיסטיקות מפורטות + + + Reachable chat toolbar + סרגל כלים נגיש לצ'אט + + + Configure server operators + הגדרת מפעילי שרת + + + Private chats, groups and your contacts are not accessible to server operators. + צ'אטים פרטיים, קבוצות ואנשי הקשר שלך אינם נגישים למפעילי השרת. + + + pending + ממתין + + + Save and reconnect + שמור וחבר מחדש + + + Customizable message shape. + צורת הודעה הניתנת להתאמה אישית. + + + All data is kept private on your device. + כל הנתונים נשמרים פרטיים במכשיר שלך. + + + Downloading link details + הורדת פרטי קישור + + + Message shape + צורת ההודעה + + + No chats in list %@ + אין צ'אטים ברשימה %@ + + + Privacy policy and conditions of use. + מדיניות פרטיות ותנאי שימוש. + + + pending review + ממתין לסקירה + + + Save list + שמור רשימה + + + pending approval + ממתין לאישור + + + %@ downloaded + %@ יורד + + + %@ server + %@ שרת + + + %@ connected + %@ מחובר + + + %@ uploaded + %@ הועלה + @@ -5627,4 +5733,12 @@ This is your own one-time link! + + + + New messages + הודעות חדשות + + + diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index 6ad4d159c7..2aa945f603 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -2043,8 +2043,8 @@ We will be adding server redundancy to prevent lost messages. Please store passphrase securely, you will NOT be able to change it if you lose it. No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -2371,8 +2371,8 @@ We will be adding server redundancy to prevent lost messages. Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error @@ -3412,7 +3412,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -3577,8 +3577,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 78bee138e4..b542c6eabf 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -337,7 +337,7 @@ **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. - **Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat-kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van. + **Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van. No comment provided by engineer. @@ -422,7 +422,7 @@ - custom time to disappear. - editing history. - legfeljebb 5 perc hosszúságú hangüzenetek. -- egyéni üzenet-eltűnési időkorlát. +- egyéni időkorlát beállítása az üzenetek eltűnéséhez. - előzmények szerkesztése. No comment provided by engineer. @@ -518,14 +518,14 @@ time interval A separate TCP connection will be used **for each chat profile you have in the app**. - **Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. + **Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat lesz használva. No comment provided by engineer. A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. - **Az összes partneréhez és csoporttaghoz** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. -**Megjegyzés:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. + **Az összes partneréhez és csoporttaghoz** külön TCP-kapcsolat lesz használva. +**Megjegyzés:** ha sok kapcsolata van, akkor az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. No comment provided by engineer. @@ -563,8 +563,19 @@ time interval Elfogadás accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Befogadás tagként + alert action + + + Accept as observer + Befogadás megfigyelőként + alert action + Accept conditions Feltételek elfogadása @@ -572,20 +583,30 @@ swipe action Accept connection request? - Elfogadja a meghívási kérést? + Elfogadja a kapcsolódási kérést? No comment provided by engineer. + + Accept contact request + Partneri kapcsolatkérés elfogadása + alert title + Accept contact request from %@? - Elfogadja %@ meghívási kérését? + Elfogadja %@ partneri kapcsolatkérését? notification body Accept incognito Elfogadás inkognitóban - accept contact request via notification + alert action swipe action + + Accept member + Tag befogadása + alert title + Accepted conditions Elfogadott feltételek @@ -626,6 +647,11 @@ swipe action Lista hozzáadása No comment provided by engineer. + + Add message + Üzenet hozzáadása + placeholder for sending contact request + Add profile Profil hozzáadása @@ -668,7 +694,7 @@ swipe action Added media & file servers - Hozzáadott média- és fájlkiszolgálók + Hozzáadott fájl- és médiakiszolgálók No comment provided by engineer. @@ -698,7 +724,7 @@ swipe action Address change will be aborted. Old receiving address will be used. - A cím módosítása meg fog szakadni. A régi fogadási cím lesz használva. + A kliens megszakítja a cím módosítását, és a régi fogadási címet fogja használni. No comment provided by engineer. @@ -841,6 +867,11 @@ swipe action Visszafejlesztés engedélyezése No comment provided by engineer. + + Allow files and media only if your contact allows them. + A fájlok és a médiatartalmak küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra) @@ -888,7 +919,7 @@ swipe action Allow to send files and media. - A fájlok- és a médiatartalmak küldése engedélyezve van. + A fájlok és a médiatartalmak küldése engedélyezve van. No comment provided by engineer. @@ -926,6 +957,11 @@ swipe action Az eltűnő üzenetek küldésének engedélyezése a partnerei számára. No comment provided by engineer. + + Allow your contacts to send files and media. + A fájlok és a médiatartalmak küldése engedélyezve van a partnerei számára. + No comment provided by engineer. + Allow your contacts to send voice messages. A hangüzenetek küldése engedélyezve van a partnerei számára. @@ -939,12 +975,12 @@ swipe action Already connecting! Kapcsolódás folyamatban! - No comment provided by engineer. + new chat sheet title Already joining the group! A csatlakozás folyamatban van a csoporthoz! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -978,7 +1014,7 @@ swipe action App build: %@ - Az alkalmazás összeállítási száma: %@ + Alkalmazás összeállítási száma: %@ No comment provided by engineer. @@ -1003,12 +1039,12 @@ swipe action App passcode - Alkalmazás jelkód + Alkalmazásjelkód No comment provided by engineer. App passcode is replaced with self-destruct passcode. - Az alkalmazás-jelkód helyettesítve lesz egy önmegsemmisítő-jelkóddal. + Az alkalmazásjelkód helyettesítve lesz egy önmegsemmisítő jelkóddal. No comment provided by engineer. @@ -1018,12 +1054,12 @@ swipe action App version - Az alkalmazás verziója + Alkalmazás verziója No comment provided by engineer. App version: v%@ - Az alkalmazás verziója: v%@ + Alkalmazás verziója: v%@ No comment provided by engineer. @@ -1038,7 +1074,7 @@ swipe action Apply to - Alkalmazás erre + Használat ehhez No comment provided by engineer. @@ -1113,7 +1149,7 @@ swipe action Audio/video calls are prohibited. - A hívások kezdeményezése le van tiltva ebben a csevegésben. + A hívások kezdeményezése le van tiltva. No comment provided by engineer. @@ -1143,7 +1179,7 @@ swipe action Auto-accept contact requests - Meghívási kérések automatikus elfogadása + Partneri kapcsolatkérések automatikus elfogadása No comment provided by engineer. @@ -1151,11 +1187,6 @@ swipe action Képek automatikus elfogadása No comment provided by engineer. - - Auto-accept settings - Beállítások automatikus elfogadása - alert title - Back Vissza @@ -1178,7 +1209,7 @@ swipe action Bad message hash - Érvénytelen az üzenet hasítóértéke + Érvénytelen az üzenet kivonata No comment provided by engineer. @@ -1231,6 +1262,16 @@ swipe action Továbbfejlesztett felhasználói élmény No comment provided by engineer. + + Bio + Névjegy + No comment provided by engineer. + + + Bio too large + A névjegy túl hosszú + alert title + Black Fekete @@ -1281,6 +1322,11 @@ swipe action Médiatartalom elhomályosítása No comment provided by engineer. + + Bot + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Mindkét fél hozzáadhat az üzenetekhez reakciókat. @@ -1301,6 +1347,11 @@ swipe action Mindkét fél küldhet eltűnő üzeneteket. No comment provided by engineer. + + Both you and your contact can send files and media. + Mindkét fél küldhet fájlokat és médiatartalmakat. + No comment provided by engineer. + Both you and your contact can send voice messages. Mindkét fél küldhet hangüzeneteket. @@ -1321,6 +1372,11 @@ swipe action Üzleti csevegések No comment provided by engineer. + + Business connection + Üzleti kapcsolat + No comment provided by engineer. + Businesses Üzleti @@ -1370,6 +1426,11 @@ swipe action Nem lehet felhívni a tagot No comment provided by engineer. + + Can't change profile + Nem lehet módosítani a profilt + alert title + Can't invite contact! Nem lehet meghívni a partnert! @@ -1389,7 +1450,8 @@ swipe action Cancel Mégse alert action -alert button +alert button +new chat action Cancel migration @@ -1478,7 +1540,7 @@ alert button Change self-destruct passcode - Önmegsemmisítő-jelkód módosítása + Önmegsemmisítő jelkód módosítása authentication reason set passcode view @@ -1495,7 +1557,7 @@ set passcode view Chat already exists! A csevegés már létezik! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1539,7 +1601,7 @@ set passcode view Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. - A csevegés megállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés elindítása előtt. + A csevegés megállt. Ha ezt az adatbázist már használta egy másik eszközön, akkor a csevegés elindítása előtt vissza kell állítania azt. No comment provided by engineer. @@ -1582,11 +1644,31 @@ set passcode view A csevegés törölve lesz az Ön számára – ez a művelet nem vonható vissza! No comment provided by engineer. + + Chat with admins + Csevegés az adminisztrátorokkal + chat toolbar + + + Chat with member + Csevegés a taggal + No comment provided by engineer. + + + Chat with members before they join. + Csevegés a tagokkal mielőtt csatlakoznának. + No comment provided by engineer. + Chats Csevegések No comment provided by engineer. + + Chats with members + Csevegés a tagokkal + No comment provided by engineer. + Check messages every 20 min. Üzenetek ellenőrzése 20 percenként. @@ -1812,9 +1894,9 @@ set passcode view Kapcsolódás automatikusan No comment provided by engineer. - - Connect incognito - Kapcsolódás inkognitóban + + Connect faster! 🚀 + Gyorsabb kapcsolódás! 🚀 No comment provided by engineer. @@ -1827,44 +1909,39 @@ set passcode view Kapcsolódjon gyorsabban a partnereihez. No comment provided by engineer. - - Connect to yourself? - Kapcsolódik saját magához? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Kapcsolódik saját magához? Ez a saját SimpleX-címe! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Kapcsolódik saját magához? Ez a saját egyszer használható meghívója! - No comment provided by engineer. + new chat sheet title Connect via contact address Kapcsolódás a kapcsolattartási címen keresztül - No comment provided by engineer. + new chat sheet title Connect via link Kapcsolódás egy hivatkozáson keresztül - No comment provided by engineer. + new chat sheet title Connect via one-time link Kapcsolódás egyszer használható meghívón keresztül - No comment provided by engineer. + new chat sheet title Connect with %@ Kapcsolódás a következővel: %@ - No comment provided by engineer. + new chat action Connected @@ -1929,7 +2006,7 @@ Ez a saját egyszer használható meghívója! Connection error Kapcsolódási hiba - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1955,7 +2032,7 @@ Ez a saját egyszer használható meghívója! Connection request sent! - Meghívási kérés elküldve! + Kapcsolódási kérés elküldve! No comment provided by engineer. @@ -1976,7 +2053,7 @@ Ez a saját egyszer használható meghívója! Connection timeout Időtúllépés kapcsolódáskor - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2028,6 +2105,11 @@ Ez a saját egyszer használható meghívója! Partnerbeállítások No comment provided by engineer. + + Contact requests from groups + Partneri kapcsolatkérések a csoportokból + No comment provided by engineer. + Contact will be deleted - this cannot be undone! A partner törölve lesz – ez a művelet nem vonható vissza! @@ -2140,17 +2222,17 @@ Ez a saját egyszer használható meghívója! Create queue - Sorba állítás létrehozása + Várólista létrehozása server test step - - Create secret group - Titkos csoport létrehozása + + Create your address + Saját cím létrehozása No comment provided by engineer. Create your profile - Saját profil létrehozása + Profil létrehozása No comment provided by engineer. @@ -2240,7 +2322,7 @@ Ez a saját egyszer használható meghívója! Database IDs and Transport isolation option. - Adatbázis-azonosítók és átvitel-izolációs beállítások. + Adatbázis-azonosítók és átvitelelkülönítési beállítások. No comment provided by engineer. @@ -2338,7 +2420,7 @@ Ez a saját egyszer használható meghívója! Decryption error - Titkosítás visszafejtési hiba + Titkosítás-visszafejtési hiba message decrypt error item @@ -2389,7 +2471,7 @@ swipe action Delete chat messages from your device. - Csevegési üzenetek törlése a saját eszközéről. + Csevegési üzenetek törlése az eszközről. No comment provided by engineer. @@ -2402,6 +2484,11 @@ swipe action Törli a csevegési profilt? No comment provided by engineer. + + Delete chat with member? + Törli a taggal való csevegést? + alert title + Delete chat? Törli a csevegést? @@ -2439,7 +2526,7 @@ swipe action Delete files and media? - Törli a fájl- és a médiatartalmakat? + Törli a fájlokat és a médiatartalmakat? No comment provided by engineer. @@ -2524,7 +2611,7 @@ swipe action Delete pending connection? - Törli a függőben lévő meghívót? + Törli a függőben lévő kapcsolatot? No comment provided by engineer. @@ -2534,7 +2621,7 @@ swipe action Delete queue - Sorba állítás törlése + Várólista törlése server test step @@ -2597,11 +2684,21 @@ swipe action Kézbesítési jelentések! No comment provided by engineer. + + Deprecated options + Elavult beállítások + No comment provided by engineer. + Description Leírás No comment provided by engineer. + + Description too large + A leírás túl hosszú + alert title + Desktop address Számítógép címe @@ -2674,7 +2771,7 @@ swipe action Different names, avatars and transport isolation. - Különböző nevek, profilképek és átvitel-izoláció. + Különböző nevek, profilképek és átvitelizoláció. No comment provided by engineer. @@ -2694,7 +2791,7 @@ swipe action Disable (keep overrides) - Letiltás (felülírások megtartásával) + Letiltás (egyéni beállítások megtartása) No comment provided by engineer. @@ -2799,7 +2896,7 @@ swipe action Do not use credentials with proxy. - Ne használja a hitelesítőadatokat proxyval. + Ne használja a hitelesítési adatokat proxyval. No comment provided by engineer. @@ -2825,7 +2922,7 @@ swipe action Don't show again Ne mutasd újra - No comment provided by engineer. + alert action Done @@ -2908,6 +3005,11 @@ chat item action Csoportprofil szerkesztése No comment provided by engineer. + + Empty message! + Üres üzenet! + No comment provided by engineer. + Enable Engedélyezés @@ -2915,7 +3017,7 @@ chat item action Enable (keep overrides) - Engedélyezés (felülírások megtartásával) + Engedélyezés (egyéni beállítások megtartása) No comment provided by engineer. @@ -2940,7 +3042,12 @@ chat item action Enable camera access - Kamera hozzáférés engedélyezése + Kamera-hozzáférés engedélyezése + No comment provided by engineer. + + + Enable disappearing messages by default. + Eltűnő üzenetek engedélyezése alapértelmezetten. No comment provided by engineer. @@ -2980,7 +3087,7 @@ chat item action Enable self-destruct passcode - Önmegsemmisítő-jelkód engedélyezése + Önmegsemmisítő jelkód engedélyezése set passcode view @@ -3010,7 +3117,7 @@ chat item action Encrypt stored files & media - A tárolt fájlok- és a médiatartalmak titkosítása + Tárolt fájlok és médiatartalmak titkosítása No comment provided by engineer. @@ -3100,7 +3207,7 @@ chat item action Enter server manually - Adja meg a kiszolgálót kézzel + Kiszolgáló megadása kézzel No comment provided by engineer. @@ -3140,9 +3247,14 @@ chat item action Error accepting contact request - Hiba történt a meghívási kérés elfogadásakor + Hiba történt a partneri kapcsolatkérés elfogadásakor No comment provided by engineer. + + Error accepting member + Hiba a tag befogadásakor + alert title + Error adding member(s) Hiba történt a tag(ok) hozzáadásakor @@ -3153,11 +3265,21 @@ chat item action Hiba történt a kiszolgáló hozzáadásakor alert title + + Error adding short link + Hiba történt a rövid hivatkozás hozzáadásakor + No comment provided by engineer. + Error changing address Hiba történt a cím módosításakor No comment provided by engineer. + + Error changing chat profile + Hiba a csevegési profil módosításakor + alert title + Error changing connection profile Hiba történt a kapcsolati profilra való váltáskor @@ -3171,7 +3293,7 @@ chat item action Error changing setting Hiba történt a beállítás módosításakor - No comment provided by engineer. + alert title Error changing to incognito! @@ -3186,7 +3308,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Hiba történt a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később. - No comment provided by engineer. + alert message Error creating address @@ -3233,15 +3355,20 @@ chat item action Hiba történt a fájl visszafejtésekor No comment provided by engineer. + + Error deleting chat + Hiba a taggal való csevegés törlésekor + alert title + Error deleting chat database Hiba történt a csevegési adatbázis törlésekor - No comment provided by engineer. + alert title Error deleting chat! Hiba történt a csevegés törlésekor! - No comment provided by engineer. + alert title Error deleting connection @@ -3251,12 +3378,12 @@ chat item action Error deleting database Hiba történt az adatbázis törlésekor - No comment provided by engineer. + alert title Error deleting old database Hiba történt a régi adatbázis törlésekor - No comment provided by engineer. + alert title Error deleting token @@ -3265,7 +3392,7 @@ chat item action Error deleting user profile - Hiba történt a felhasználó-profil törlésekor + Hiba történt a felhasználói profil törlésekor No comment provided by engineer. @@ -3291,7 +3418,7 @@ chat item action Error exporting chat database Hiba történt a csevegési adatbázis exportálásakor - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3301,7 +3428,7 @@ chat item action Error importing chat database Hiba történt a csevegési adatbázis importálásakor - No comment provided by engineer. + alert title Error joining group @@ -3323,6 +3450,11 @@ chat item action Hiba történt a csevegés megnyitásakor No comment provided by engineer. + + Error opening group + Hiba a csoport előkészítésekor + No comment provided by engineer. + Error receiving file Hiba történt a fájl fogadásakor @@ -3343,10 +3475,15 @@ chat item action Hiba történt az értesítések regisztrálásakor alert title + + Error rejecting contact request + Hiba történt a partneri kapcsolatkérés elutasításakor + alert title + Error removing member Hiba történt a tag eltávolításakor - No comment provided by engineer. + alert title Error reordering lists @@ -3418,6 +3555,11 @@ chat item action Hiba történt az üzenet elküldésekor No comment provided by engineer. + + Error setting auto-accept + Hiba az automatikus elfogadás beállításakor + No comment provided by engineer. + Error setting delivery receipts! Hiba történt a kézbesítési jelentések beállításakor! @@ -3436,7 +3578,7 @@ chat item action Error switching profile Hiba történt a profilváltáskor - No comment provided by engineer. + alert title Error switching profile! @@ -3500,6 +3642,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Hiba: a webcím érvénytelen @@ -3577,7 +3723,7 @@ snd error text Fast and no wait until the sender is online! - Gyors és nem kell várni, amíg a feladó online lesz! + Gyors és nem kell várni, amíg az üzenetküldő online lesz! No comment provided by engineer. @@ -3679,19 +3825,24 @@ snd error text Fájlok és médiatartalmak chat feature + + Files and media are prohibited in this chat. + A fájlok és a médiatartalmak küldése le van tiltva ebben a csevegésben. + No comment provided by engineer. + Files and media are prohibited. - A fájlok- és a médiatartalmak küldése le van tiltva. + A fájlok és a médiatartalmak küldése le van tiltva. No comment provided by engineer. Files and media not allowed - A fájlok- és médiatartalmak nincsenek engedélyezve + A fájlok és a médiatartalmak nincsenek engedélyezve No comment provided by engineer. Files and media prohibited! - A fájlok- és a médiatartalmak küldése le van tiltva! + A fájlok és a médiatartalmak küldése le van tiltva! No comment provided by engineer. @@ -3719,6 +3870,23 @@ snd error text Csevegési üzenetek gyorsabb megtalálása No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Javítás @@ -3766,7 +3934,7 @@ snd error text For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. - Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. + Például, ha a partnere egy SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux kiszolgálón keresztül fogja azokat kézbesíteni. No comment provided by engineer. @@ -3830,9 +3998,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - A(z) %@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %@ célkiszolgálóhoz. Próbálja meg később. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + A(z) %1$@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %2$@ célkiszolgálóhoz. Próbálja meg később. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3926,7 +4094,7 @@ Hiba: %2$@ Group already exists! A csoport már létezik! - No comment provided by engineer. + new chat sheet title Group display name @@ -3990,9 +4158,14 @@ Hiba: %2$@ Group profile is stored on members' devices, not on the servers. - A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon. + A csoportprofil a tagok eszközein tárolódik, nem a kiszolgálókon. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Csoportprofil módosítva. Ha menti, akkor a frissített profil el lesz küldve a csoporttagoknak. + alert message + Group welcome message A csoport üdvözlőüzenete @@ -4130,7 +4303,7 @@ Hiba: %2$@ If you enter your self-destruct passcode while opening the app: - Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő-jelkódot: + Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: No comment provided by engineer. @@ -4375,7 +4548,7 @@ További fejlesztések hamarosan! Invalid link Érvénytelen hivatkozás - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4449,7 +4622,7 @@ További fejlesztések hamarosan! It can happen when you or your connection used the old database backup. - Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt. + Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta. No comment provided by engineer. @@ -4459,7 +4632,7 @@ További fejlesztések hamarosan! 3. The connection was compromised. Ez akkor fordulhat elő, ha: 1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak. -2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt. +2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta. 3. A kapcsolat sérült. No comment provided by engineer. @@ -4488,19 +4661,19 @@ További fejlesztések hamarosan! Csatlakozás swipe action + + Join as %@ + csatlakozás mint %@ + No comment provided by engineer. + Join group - Csatlakozás csoporthoz - No comment provided by engineer. + Csatlakozás a csoporthoz + new chat sheet title Join group conversations - Csatlakozás csoportos beszélgetésekhez - No comment provided by engineer. - - - Join group? - Csatlakozik a csoporthoz? + Csatlakozás a csoportbeszélgetésekhez No comment provided by engineer. @@ -4508,17 +4681,12 @@ További fejlesztések hamarosan! Csatlakozás inkognitóban No comment provided by engineer. - - Join with current profile - Csatlakozás a jelenlegi profillal - No comment provided by engineer. - Join your group? This is your link for group %@! Csatlakozik a csoportjához? Ez a saját hivatkozása a(z) %@ nevű csoporthoz! - No comment provided by engineer. + new chat action Joining group @@ -4545,6 +4713,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Megtartja a fel nem használt meghívót? alert title + + Keep your chats clean + Tartsa tisztán a csevegéseit + No comment provided by engineer. + Keep your connections Kapcsolatok megtartása @@ -4600,6 +4773,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Elhagyja a csoportot? No comment provided by engineer. + + Less traffic on mobile networks. + Kevesebb adatforgalom a mobilhálózatokon. + No comment provided by engineer. + Let's talk in SimpleX Chat Beszélgessünk a SimpleX Chatben @@ -4655,6 +4833,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Élő üzenetek No comment provided by engineer. + + Loading profile… + Profil betöltése… + in progress text + Local name Helyi név @@ -4717,7 +4900,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Media & file servers - Média- és fájlkiszolgálók + Fájl- és médiakiszolgálók No comment provided by engineer. @@ -4730,11 +4913,26 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Tag No comment provided by engineer. + + Member %@ + %@ ismeretlen vagy már nem tag + past/unknown group member + + + Member admission + Tagbefogadás + No comment provided by engineer. + Member inactive Inaktív tag item status text + + Member is deleted - can't accept request + A tag törölve lett – nem lehet elfogadni a kérést + No comment provided by engineer. + Member reports Tagok jelentései @@ -4765,6 +4963,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza! No comment provided by engineer. + + Member will join the group, accept member? + A tag csatlakozni akar a csoporthoz, befogadja a tagot? + alert message + Members can add message reactions. A tagok reakciókat adhatnak hozzá az üzenetekhez. @@ -4840,6 +5043,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Továbbított üzenet item status text + + Message instantly once you tap Connect. + Az üzenet azonnal megjelenik, amint a kapcsolódás gombra koppint. + No comment provided by engineer. + Message may be delivered later if member becomes active. Az üzenet később is kézbesíthető, ha a tag aktívvá válik. @@ -4847,7 +5055,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Message queue info - Üzenetsorbaállítási információ + Üzenet várólista információi No comment provided by engineer. @@ -4867,7 +5075,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Message reception - Üzenetjelentés + Üzenetfogadás No comment provided by engineer. @@ -4915,6 +5123,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Üzenetek és fájlok No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Az üzenetek **végpontok közötti titkosítással** vannak védve. + No comment provided by engineer. + Messages from %@ will be shown! %@ összes üzenete meg fog jelenni! @@ -4942,12 +5155,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve. + Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve. + Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve. No comment provided by engineer. @@ -5127,12 +5340,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! New SOCKS credentials will be used every time you start the app. - Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni. + Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítési adatok lesznek használva. No comment provided by engineer. New SOCKS credentials will be used for each server. - Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva. + Az összes kiszolgálóhoz új, SOCKS-hitelesítési adatok lesznek használva. No comment provided by engineer. @@ -5147,7 +5360,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! New contact request - Új meghívási kérés + Új partneri kapcsolatkérés notification @@ -5170,6 +5383,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Új események notification + + New group role: Moderator + Új szerepkör: Moderátor + No comment provided by engineer. + New in %@ Újdonságok a(z) %@ verzióban @@ -5185,6 +5403,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Új tag szerepköre No comment provided by engineer. + + New member wants to join the group. + Új tag szeretne csatlakozni a csoporthoz. + rcv group event chat item + New message Új üzenet @@ -5225,6 +5448,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nincsenek csevegések a(z) %@ nevű listában No comment provided by engineer. + + No chats with members + Nincsenek csevegések a tagokkal + No comment provided by engineer. + No contacts selected Nincs partner kijelölve @@ -5272,7 +5500,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No media & file servers. - Nincsenek média- és fájlkiszolgálók. + Nincsenek fájl- és médiakiszolgálók. servers error @@ -5282,7 +5510,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No message servers. - Nincsenek üzenet-kiszolgálók. + Nincsenek üzenetkiszolgálók. servers error @@ -5305,6 +5533,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nincs engedély a hangüzenet rögzítésére No comment provided by engineer. + + No private routing session + Nincs privát útválasztási munkamenet + alert title + No push server Helyi @@ -5347,7 +5580,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No user identifiers. - Nincsenek felhasználó-azonosítók. + Nincsenek felhasználói azonosítók. No comment provided by engineer. @@ -5417,7 +5650,9 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Ok Rendben - alert button + alert action +alert button +new chat action Old database @@ -5432,20 +5667,20 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Onion hosts will be **required** for connection. Requires compatible VPN. - Onion-kiszolgálók **szükségesek** a kapcsolódáshoz. + Onion kiszolgálók **szükségesek** a kapcsolódáshoz. Kompatibilis VPN szükséges. No comment provided by engineer. Onion hosts will be used when available. Requires compatible VPN. - Onion-kiszolgálók használata, ha azok rendelkezésre állnak. + Onion kiszolgálók használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges. No comment provided by engineer. Onion hosts will not be used. - Az onion-kiszolgálók nem lesznek használva. + Az onion kiszolgálók nem lesznek használva. No comment provided by engineer. @@ -5470,7 +5705,7 @@ VPN engedélyezése szükséges. Only group owners can enable files and media. - Csak a csoport tulajdonosai engedélyezhetik a fájlok- és a médiatartalmak küldését. + Csak a csoport tulajdonosai engedélyezhetik a fájlok és a médiatartalmak küldését. No comment provided by engineer. @@ -5480,7 +5715,7 @@ VPN engedélyezése szükséges. Only sender and moderators see it - Csak a küldő és a moderátorok látják + Csak az üzenet küldője és a moderátorok látják No comment provided by engineer. @@ -5508,6 +5743,11 @@ VPN engedélyezése szükséges. Csak Ön tud eltűnő üzeneteket küldeni. No comment provided by engineer. + + Only you can send files and media. + Csak Ön küldhet fájlokat és médiatartalmakat. + No comment provided by engineer. + Only you can send voice messages. Csak Ön tud hangüzeneteket küldeni. @@ -5533,6 +5773,11 @@ VPN engedélyezése szükséges. Csak a partnere tud eltűnő üzeneteket küldeni. No comment provided by engineer. + + Only your contact can send files and media. + Csak a partnere küldhet fájlokat és a médiatartalmakat. + No comment provided by engineer. + Only your contact can send voice messages. Csak a partnere tud hangüzeneteket küldeni. @@ -5556,25 +5801,36 @@ VPN engedélyezése szükséges. Open chat Csevegés megnyitása - No comment provided by engineer. + new chat action Open chat console Csevegési konzol megnyitása authentication reason + + Open clean link + Tiszta hivatkozás megnyitása + alert action + Open conditions Feltételek megnyitása No comment provided by engineer. + + Open full link + Teljes hivatkozás megnyitása + alert action + Open group Csoport megnyitása - No comment provided by engineer. + new chat action Open link? + Megnyitja a hivatkozást? alert title @@ -5582,6 +5838,36 @@ VPN engedélyezése szükséges. Átköltöztetés indítása egy másik eszközre authentication reason + + Open new chat + Új csevegés megnyitása + new chat action + + + Open new group + Új csoport megnyitása + new chat action + + + Open to accept + Megnyitás az elfogadáshoz + No comment provided by engineer. + + + Open to connect + Megnyitás a kapcsolódáshoz + No comment provided by engineer. + + + Open to join + Megnyitás a csatlakozáshoz + No comment provided by engineer. + + + Open to use bot + Megnyitás a bot használatához + No comment provided by engineer. + Opening app… Az alkalmazás megnyitása… @@ -5689,11 +5975,6 @@ VPN engedélyezése szükséges. Jelszó a megjelenítéshez No comment provided by engineer. - - Past member %@ - (Már nem tag) %@ - past/unknown group member - Paste desktop address Számítógép címének beillesztése @@ -5764,7 +6045,7 @@ Minden további problémát osszon meg a fejlesztőkkel. Please check your network connection with %@ and try again. Ellenőrizze a hálózati kapcsolatát a vele: %@, és próbálja újra. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5828,6 +6109,11 @@ Hiba: %@ Próbálja meg letiltani és újra engedélyezni az értesítéseket. token info + + Please wait for group moderators to review your request to join the group. + Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérését. + snd group event chat item + Please wait for token activation to complete. Várjon, amíg a token aktiválása befejeződik. @@ -5848,11 +6134,6 @@ Hiba: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen - server test error - Preserve the last message draft, with attachments. Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. @@ -5900,7 +6181,7 @@ Hiba: %@ Private chats, groups and your contacts are not accessible to server operators. - A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára. + A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. No comment provided by engineer. @@ -5936,7 +6217,12 @@ Hiba: %@ Private routing error Privát útválasztási hiba - No comment provided by engineer. + alert title + + + Private routing timeout + Privát útválasztás időtúllépése + alert title Profile and server connections @@ -6010,7 +6296,7 @@ Hiba: %@ Prohibit sending files and media. - A fájlok- és a médiatartalmak küldése le van tiltva. + A fájlok és a médiatartalmak küldése le van tiltva. No comment provided by engineer. @@ -6040,6 +6326,11 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Védje meg a csevegési profiljait egy jelszóval! No comment provided by engineer. + + Protocol background timeout + Protokoll időtúllépése a háttérben + No comment provided by engineer. + Protocol timeout Protokoll időtúllépése @@ -6087,7 +6378,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Reachable chat toolbar - Könnyen elérhető eszköztár + Könnyen elérhető csevegési eszköztár No comment provided by engineer. @@ -6122,7 +6413,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - További információ a [GitHub tárolóban](https://github.com/simplex-chat/simplex-chat#readme). + További információ a [GitHub-tárolónkban](https://github.com/simplex-chat/simplex-chat#readme). No comment provided by engineer. @@ -6172,7 +6463,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Receiving address will be changed to a different server. Address change will complete after sender comes online. - A fogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása a feladó online állapotba kerülése után fejeződik be. + Az üzenetfogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása akkor fejeződik be, amikor az üzenetküldési kiszolgáló online lesz. No comment provided by engineer. @@ -6268,27 +6559,33 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Reject Elutasítás - reject incoming call via notification + alert action +reject incoming call via notification swipe action Reject (sender NOT notified) - Elutasítás (a feladó NEM kap értesítést) + Elutasítás (a kérés küldője NEM fog értesítést kapni) No comment provided by engineer. Reject contact request - Meghívási kérés elutasítása - No comment provided by engineer. + Partneri kapcsolatkérés elutasítása + alert title + + + Reject member? + Elutasítja a tagot? + alert title Relay server is only used if necessary. Another party can observe your IP address. - A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet. + A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét. No comment provided by engineer. Relay server protects your IP address, but it can observe the duration of the call. - A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát. + A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát. No comment provided by engineer. @@ -6306,6 +6603,11 @@ swipe action Kép eltávolítása No comment provided by engineer. + + Remove link tracking + Nyomonkövetési paraméterek eltávolítása a hivatkozásokból + No comment provided by engineer. + Remove member Eltávolítás @@ -6321,6 +6623,11 @@ swipe action Eltávolítja a jelmondatot a kulcstartóból? No comment provided by engineer. + + Removes messages and blocks members. + Üzenetek eltávolítása és a tagok tiltása. + No comment provided by engineer. + Renegotiate Újraegyeztetés @@ -6336,11 +6643,6 @@ swipe action Újraegyezteti a titkosítást? No comment provided by engineer. - - Repeat connection request? - Megismétli a meghívási kérést? - No comment provided by engineer. - Repeat download Letöltés ismét @@ -6351,11 +6653,6 @@ swipe action Importálás ismét No comment provided by engineer. - - Repeat join request? - Megismétli a meghívási kérést? - No comment provided by engineer. - Repeat upload Feltöltés ismét @@ -6391,6 +6688,11 @@ swipe action Jelentés indoklása? No comment provided by engineer. + + Report sent to moderators + A jelentés el lett küldve a moderátoroknak + alert title + Report spam: only group moderators will see it. Kéretlen tartalom jelentése: csak a csoport moderátorai látják. @@ -6494,7 +6796,7 @@ swipe action Retry Újrapróbálkozás - No comment provided by engineer. + alert action Reveal @@ -6506,6 +6808,21 @@ swipe action Feltételek felülvizsgálata No comment provided by engineer. + + Review group members + Csoporttagok áttekintése + No comment provided by engineer. + + + Review members + Tagok áttekintése + admission stage + + + Review members before admitting ("knocking"). + Tagok áttekintése a befogadás előtt (kopogtatás). + admission stage description + Revoke Visszavonás @@ -6538,7 +6855,7 @@ swipe action SOCKS proxy - SOCKS-proxy + SOCKS proxy No comment provided by engineer. @@ -6562,6 +6879,16 @@ chat item action Mentés (és a partnerek értesítése) alert button + + Save (and notify members) + Mentés (és a tagok értesítése) + alert button + + + Save admission settings? + Menti a befogadási beállításokat? + alert title + Save and notify contact Mentés és a partner értesítése @@ -6587,6 +6914,11 @@ chat item action Csoportprofil mentése No comment provided by engineer. + + Save group profile? + Menti a csoportprofilt? + alert title + Save list Lista mentése @@ -6644,7 +6976,7 @@ chat item action Saved from - Elmentve innen + Mentve innen No comment provided by engineer. @@ -6689,7 +7021,7 @@ chat item action Scan server QR code - A kiszolgáló QR-kódjának beolvasása + Kiszolgáló QR-kódjának beolvasása No comment provided by engineer. @@ -6714,7 +7046,7 @@ chat item action Secure queue - Biztonságos sorba állítás + Biztonságos várólista server test step @@ -6759,17 +7091,17 @@ chat item action Self-destruct passcode - Önmegsemmisítő-jelkód + Önmegsemmisítő jelkód No comment provided by engineer. Self-destruct passcode changed! - Az önmegsemmisítő-jelkód módosult! + Az önmegsemmisítő jelkód módosult! No comment provided by engineer. Self-destruct passcode enabled! - Az önmegsemmisítő-jelkód engedélyezve! + Az önmegsemmisítő jelkód engedélyezve! No comment provided by engineer. @@ -6782,6 +7114,11 @@ chat item action Élő üzenet küldése – az üzenet a címzett(ek) számára valós időben frissül, ahogy Ön beírja az üzenetet No comment provided by engineer. + + Send contact request? + Elküldi a partneri kapcsolatkérést? + No comment provided by engineer. + Send delivery receipts to A kézbesítési jelentéseket a következő címre kell küldeni @@ -6804,7 +7141,7 @@ chat item action Send link previews - Hivatkozás előnézete + Hivatkozások előnézetének megjelenítése No comment provided by engineer. @@ -6847,6 +7184,16 @@ chat item action Kézbesítési jelentések küldése No comment provided by engineer. + + Send request + Kérés küldése + No comment provided by engineer. + + + Send request without message + Kérés küldése üzenet nélkül + No comment provided by engineer. + Send them from gallery or custom keyboards. Küldje el őket a galériából vagy az egyéni billentyűzetekről. @@ -6857,6 +7204,11 @@ chat item action Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára. No comment provided by engineer. + + Send your private feedback to groups. + Küldjön privát visszajelzést a csoportoknak. + No comment provided by engineer. + Sender cancelled file transfer. A fájl küldője visszavonta az átvitelt. @@ -6864,7 +7216,7 @@ chat item action Sender may have deleted the connection request. - A küldője törölhette a meghívási kérést. + A kérés küldője törölhette a kapcsolódási kérést. No comment provided by engineer. @@ -6994,16 +7346,16 @@ chat item action Server protocol changed. - A kiszolgáló-protokoll módosult. + A kiszolgálóprotokoll módosult. alert title - - Server requires authorization to create queues, check password - A kiszolgálónak engedélyre van szüksége a sorba állítás létrehozásához, ellenőrizze a jelszavát + + Server requires authorization to create queues, check password. + A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze a jelszavát server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. A kiszolgálónak hitelesítésre van szüksége a feltöltéshez, ellenőrizze jelszavát server test error @@ -7077,6 +7429,11 @@ chat item action Beállítás a rendszer-hitelesítés helyett. No comment provided by engineer. + + Set member admission + Tagbefogadás beállítása + No comment provided by engineer. + Set message expiration in chats. Üzenetek eltűnési idejének módosítása a csevegésekben. @@ -7097,6 +7454,11 @@ chat item action Jelmondat beállítása az exportáláshoz No comment provided by engineer. + + Set profile bio and welcome message. + Névjegy és üdvözlőüzenet beállítása a profilokhoz. + No comment provided by engineer. + Set the message shown to new members! Megjelenítendő üzenet beállítása az új tagok számára! @@ -7168,6 +7530,16 @@ chat item action Megosztás No comment provided by engineer. + + Share old address + Régi cím megosztása + alert button + + + Share old link + Teljes hivatkozás megosztása + alert button + Share profile Profil megosztása @@ -7188,6 +7560,21 @@ chat item action Megosztás a partnerekkel No comment provided by engineer. + + Share your address + Saját cím megosztása + No comment provided by engineer. + + + Short SimpleX address + Rövid SimpleX-cím + No comment provided by engineer. + + + Short description + Rövid leírás + No comment provided by engineer. + Short link Rövid hivatkozás @@ -7210,7 +7597,7 @@ chat item action Show last messages - Legutóbbi üzenet előnézetének megjelenítése + Legutóbbi üzenetek előnézetének megjelenítése No comment provided by engineer. @@ -7225,7 +7612,7 @@ chat item action Show preview - Értesítés előnézete + Értesítésekben megjelenő információk No comment provided by engineer. @@ -7293,6 +7680,11 @@ chat item action SimpleX-cím vagy egyszer használható meghívó? No comment provided by engineer. + + SimpleX address settings + Beállítások automatikus elfogadása + alert title + SimpleX channel link SimpleX-csatornahivatkozás @@ -7330,14 +7722,19 @@ chat item action SimpleX one-time invitation - Egyszer használható SimpleX-meghívó + Egyszer használható SimpleX meghívó simplex link type SimpleX protocols reviewed by Trail of Bits. - A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva. + A SimpleX protokollokat a Trail of Bits auditálta. No comment provided by engineer. + + SimpleX relay link + SimpleX továbbítókiszolgáló-hivatkozás + simplex link type + Simplified incognito mode Egyszerűsített inkognitómód @@ -7551,6 +7948,11 @@ report reason TCP-kapcsolat No comment provided by engineer. + + TCP connection bg timeout + TCP-kapcsolat időtúllépése a háttérben + No comment provided by engineer. + TCP connection timeout TCP-kapcsolat időtúllépése @@ -7586,11 +7988,31 @@ report reason Kép készítése No comment provided by engineer. + + Tap Connect to chat + Koppintson a „Kapcsolódás” gombra a csevegéshez + No comment provided by engineer. + + + Tap Connect to send request + Koppintson a „Kapcsolódás” gombra a kérés elküldéséhez + No comment provided by engineer. + + + Tap Connect to use bot + Koppintson a „Kapcsolódás” gombra a bot használatához + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. No comment provided by engineer. + + Tap Join group + Koppintson a „Csatlakozás a csoporthoz” gombra + No comment provided by engineer. + Tap button Koppintson a @@ -7603,7 +8025,7 @@ report reason Tap to activate profile. - A profil aktiválásához koppintson az ikonra. + Koppintson ide a profil aktiválásához. No comment provided by engineer. @@ -7678,9 +8100,14 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + A cím rövid lesz és a profil meg lesz osztva a címen keresztül. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. - Az alkalmazás értesíteni fogja, amikor üzeneteket vagy meghívási kéréseket kap – ezt a beállítások menüben engedélyezheti. + Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kéréseket kap – ezt a beállítások menüben engedélyezheti. No comment provided by engineer. @@ -7700,7 +8127,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The code you scanned is not a SimpleX link QR code. - A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás. + A beolvasott QR-kód nem egy SimpleX-hivatkozás. No comment provided by engineer. @@ -7710,7 +8137,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The connection you accepted will be cancelled! - Az Ön által elfogadott kérelem vissza lesz vonva! + Az Ön által elfogadott kapcsolat vissza lesz vonva! No comment provided by engineer. @@ -7735,9 +8162,14 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The hash of the previous message is different. - Az előző üzenet hasítóértéke különbözik. + Az előző üzenet kivonata különbözik. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + A hivatkozás rövid lesz és a csoportprofil meg lesz osztva a hivatkozáson keresztül. + alert message + The message will be deleted for all members. Az üzenet az összes tag számára törölve lesz. @@ -7780,8 +8212,8 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The sender will NOT be notified - A feladó NEM fog értesítést kapni - No comment provided by engineer. + A kérés küldője NEM fog értesítést kapni + alert message The servers for new connections of your current chat profile **%@**. @@ -7873,16 +8305,6 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Ez a csoport már nem létezik. No comment provided by engineer. - - This is your own SimpleX address! - Ez a saját SimpleX-címe! - No comment provided by engineer. - - - This is your own one-time link! - Ez a saját egyszer használható meghívója! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől. @@ -7903,6 +8325,16 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Ez a beállítás csak az Ön jelenlegi **%@** nevű csevegési profiljában lévő üzenetekre vonatkozik. No comment provided by engineer. + + This setting is for your current profile **%@**. + Ez a beállítás csak a jelenlegi **%@** nevű csevegési profiljára vonatkozik. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Az üzeneteltűnési idő csak az új partnerekre vonatkozik. + No comment provided by engineer. + Title Cím @@ -7952,7 +8384,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To protect your privacy, SimpleX uses separate IDs for each of your contacts. - Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. + Adatainak védelme érdekében a SimpleX külön azonosítókat használ minden egyes kapcsolatához. No comment provided by engineer. @@ -7985,11 +8417,21 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll A küldéshez No comment provided by engineer. + + To send commands you must be connected. + A parancsok küldéséhez kapcsolódva kell lennie. + alert message + To support instant push notifications the chat database has to be migrated. Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Másik profil használatához a kapcsolatfelvételi kísérlet után törölje a csevegést, és használja újra a hivatkozást. + alert message + To use the servers of **%@**, accept conditions of use. A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket. @@ -8002,12 +8444,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Toggle chat list: - Csevegési lista átváltása: + Csevegési lista ki/be: No comment provided by engineer. Toggle incognito when connecting. - Inkognitóra váltás kapcsolódáskor. + Inkognitó profil használata kapcsolódáskor ki/be. No comment provided by engineer. @@ -8027,7 +8469,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Transport isolation - Átvitel-izoláció + Átvitelelkülönítés No comment provided by engineer. @@ -8147,7 +8589,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. - Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében. + Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakadások elkerülése érdekében. No comment provided by engineer. @@ -8227,11 +8669,41 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. No comment provided by engineer. + + Upgrade + Frissítés + alert button + + + Upgrade address + Cím frissítése + No comment provided by engineer. + + + Upgrade address? + Frissíti a címet? + alert message + Upgrade and open chat Fejlesztés és a csevegés megnyitása No comment provided by engineer. + + Upgrade group link? + Frissíti a csoporthivatkozást? + alert message + + + Upgrade link + Hivatkozás frissítése + No comment provided by engineer. + + + Upgrade your address + Cím frissítése + No comment provided by engineer. + Upload errors Feltöltési hibák @@ -8269,17 +8741,17 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use .onion hosts - Onion-kiszolgálók használata + Onion kiszolgálók használata No comment provided by engineer. Use SOCKS proxy - SOCKS-proxy használata + SOCKS proxy használata No comment provided by engineer. Use SimpleX Chat servers? - SimpleX Chat-kiszolgálók használata? + SimpleX Chat kiszolgálók használata? No comment provided by engineer. @@ -8300,7 +8772,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use current profile Jelenlegi profil használata - No comment provided by engineer. + new chat action Use for files @@ -8314,7 +8786,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use for new connections - Alkalmazás új kapcsolatokhoz + Használat új kapcsolatokhoz No comment provided by engineer. @@ -8324,13 +8796,18 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use iOS call interface - Az iOS hívási felületét használata + iOS hívási felület használata + No comment provided by engineer. + + + Use incognito profile + Inkognitóprofil használata No comment provided by engineer. Use new incognito profile Új inkognitóprofil használata - No comment provided by engineer. + new chat action Use only local notifications? @@ -8339,12 +8816,12 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use private routing with unknown servers when IP address is not protected. - Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + Privát útválasztás használata az ismeretlen kiszolgálókkal, ha az IP-cím nem védett. No comment provided by engineer. Use private routing with unknown servers. - Használjon privát útválasztást ismeretlen kiszolgálókkal. + Privát útválasztás használata ismeretlen kiszolgálókkal. No comment provided by engineer. @@ -8357,19 +8834,14 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Kiszolgálók használata No comment provided by engineer. - - Use short links (BETA) - Rövid hivatkozások használata (béta) - No comment provided by engineer. - Use the app while in the call. - Használja az alkalmazást hívás közben. + Alkalmazás használata hívás közben. No comment provided by engineer. Use the app with one hand. - Használja az alkalmazást egy kézzel. + Alkalmazás egy kézzel való használata. No comment provided by engineer. @@ -8389,7 +8861,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Using SimpleX Chat servers. - SimpleX Chat-kiszolgálók használatban. + SimpleX Chat kiszolgálók használatban. No comment provided by engineer. @@ -8567,6 +9039,11 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Az üdvözlőüzenet túl hosszú No comment provided by engineer. + + Welcome your contacts 👋 + Üdvözölje a partnereit 👋 + No comment provided by engineer. + What's new Újdonságok @@ -8589,7 +9066,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. - Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. + Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt. No comment provided by engineer. @@ -8624,12 +9101,12 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Without Tor or VPN, your IP address will be visible to file servers. - Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára. + Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára. No comment provided by engineer. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@. + Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára: %@. alert message @@ -8664,7 +9141,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso You accepted connection - Kapcsolat létrehozása + Ön elfogadta a kapcsolatot No comment provided by engineer. @@ -8690,12 +9167,12 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso You are already connecting to %@. A kapcsolódás már folyamatban van a következőhöz: %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! A kapcsolódás már folyamatban van ezen az egyszer használható meghívón keresztül! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8705,24 +9182,19 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso You are already joining the group %@. A csatlakozás már folyamatban van a(z) %@ nevű csoporthoz. - No comment provided by engineer. - - - You are already joining the group via this link! - A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? A csatlakozás már folyamatban van a csoporthoz! -Megismétli a meghívási kérést? - No comment provided by engineer. +Megismétli a csatlakozási kérést? + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8741,7 +9213,7 @@ Megismétli a meghívási kérést? You can accept calls from lock screen, without device and app authentication. - Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazás-hitelesítés nélkül. + A lezárási képernyőről is fogadhat hívásokat, eszköz- és alkalmazáshitelesítés nélkül. No comment provided by engineer. @@ -8761,7 +9233,7 @@ Megismétli a meghívási kérést? You can enable later via Settings - Később engedélyezheti a „Beállításokban” + Később engedélyezheti a beállításokban No comment provided by engineer. @@ -8781,7 +9253,7 @@ Megismétli a meghívási kérést? You can make it visible to your SimpleX contacts via Settings. - Láthatóvá teheti a SimpleXbeli partnerei számára a „Beállításokban”. + Láthatóvá teheti a SimpleXbeli partnerei számára a beállításokban. No comment provided by engineer. @@ -8806,7 +9278,7 @@ Megismétli a meghívási kérést? You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoportot Ön később törli, akkor nem fogja elveszíteni annak tagjait. + Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoporthivatkozást később törli, akkor nem fogja elveszíteni a csoport meglévő tagjait. No comment provided by engineer. @@ -8839,10 +9311,15 @@ Megismétli a meghívási kérést? A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél. alert message + + You can view your reports in Chat with admins. + A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben. + alert message + You can't send messages! Nem lehet üzeneteket küldeni! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8854,17 +9331,12 @@ Megismétli a meghívási kérést? Ön dönti el, hogy kivel beszélget. No comment provided by engineer. - - You have already requested connection via this address! - Már küldött egy meghívási kérést ezen a címen keresztül! - No comment provided by engineer. - You have already requested connection! Repeat connection request? - Ön már küldött egy meghívási kérést! -Megismétli a meghívási kérést? - No comment provided by engineer. + Ön már küldött egy kapcsolódási kérést! +Megismétli a kapcsolódási kérést? + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8893,7 +9365,7 @@ Megismétli a meghívási kérést? You may save the exported archive. - Az exportált archívumot elmentheti. + Mentheti az exportált archívumot. No comment provided by engineer. @@ -8926,6 +9398,11 @@ Megismétli a meghívási kérést? Ön megkapja az értesítéseket. token info + + You will be able to send messages **only after your request is accepted**. + Csak azután tud üzeneteket küldeni, **miután a kérését elfogadták**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! @@ -8938,7 +9415,7 @@ Megismétli a meghívási kérést? You will be connected when your connection request is accepted, please wait or check later! - Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, ha a kapcsolódási kérését elfogadják, várjon, vagy ellenőrizze később! No comment provided by engineer. @@ -8951,11 +9428,6 @@ Megismétli a meghívási kérést? Az alkalmazás elindításához vagy 30 másodpercnyi háttérben töltött idő után, az alkalmazáshoz való visszatéréshez hitelesítésre lesz szükség. No comment provided by engineer. - - You will connect to all group members. - Kapcsolódni fog a csoport összes tagjához. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak. @@ -8996,6 +9468,11 @@ Megismétli a meghívási kérést? Profil SimpleX-címe No comment provided by engineer. + + Your business contact + Üzleti partner + No comment provided by engineer. + Your calls Hívások @@ -9021,11 +9498,21 @@ Megismétli a meghívási kérést? Csevegési profilok No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + A csevegés át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányításkor. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. A kapcsolata át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányításkor. No comment provided by engineer. + + Your contact + Partner + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). A partnere a jelenleg megengedett maximális méretű (%@) fájlnál nagyobbat küldött. @@ -9043,7 +9530,7 @@ Megismétli a meghívási kérést? Your credentials may be sent unencrypted. - A hitelesítőadatai titkosítatlanul is elküldhetők. + A hitelesítési adati titkosítatlanul is elküldhetők. No comment provided by engineer. @@ -9056,6 +9543,11 @@ Megismétli a meghívási kérést? Jelenlegi profil No comment provided by engineer. + + Your group + Saját csoport + No comment provided by engineer. + Your preferences Beállítások @@ -9078,17 +9570,17 @@ Megismétli a meghívási kérést? Your profile is stored on your device and only shared with your contacts. - A profilja csak a partnereivel van megosztva. + A profilja az eszközén van tárolva és csak a partnereivel van megosztva. No comment provided by engineer. Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. - A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját. + A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX kiszolgálók nem láthatják a profilját. No comment provided by engineer. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. - A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára. + A profilja módosult. Ha menti, akkor a profilfrissítés el lesz küldve a partnerei számára. alert message @@ -9141,6 +9633,11 @@ Megismétli a meghívási kérést? gombra fent, majd válassza ki: No comment provided by engineer. + + accepted %@ + befogadta őt: %@ + rcv group event chat item + accepted call fogadott hívás @@ -9151,6 +9648,11 @@ Megismétli a meghívási kérést? elfogadott meghívó chat list item title + + accepted you + befogadta Önt + rcv group event chat item + admin adminisztrátor @@ -9171,6 +9673,11 @@ Megismétli a meghívási kérést? titkosítás elfogadása… chat item text + + all + összes + member criteria value + all members összes tag @@ -9193,7 +9700,7 @@ Megismétli a meghívási kérést? attempts - próbálkozások + kísérletek No comment provided by engineer. @@ -9208,12 +9715,12 @@ Megismétli a meghívási kérést? bad message ID - téves üzenet ID + hibás az üzenet azonosítója integrity error chat item bad message hash - érvénytelen az üzenet hasítóértéke + hibás az üzenet kivonata integrity error chat item @@ -9257,6 +9764,11 @@ marked deleted chat item preview text hívás… call status + + can't send messages + nem lehet üzeneteket küldeni + No comment provided by engineer. + cancelled %@ %@ visszavonva @@ -9307,11 +9819,6 @@ marked deleted chat item preview text kapcsolódott No comment provided by engineer. - - connected directly - közvetlenül kapcsolódott - rcv group event chat item - connecting kapcsolódás @@ -9362,6 +9869,16 @@ marked deleted chat item preview text %1$@ a következőre módosította a nevét: %2$@ profile update event chat item + + contact deleted + partner törölve + No comment provided by engineer. + + + contact disabled + partner letiltva + No comment provided by engineer. + contact has e2e encryption a partner e2e titkosítással rendelkezik @@ -9372,6 +9889,16 @@ marked deleted chat item preview text a partner nem rendelkezik e2e titkosítással No comment provided by engineer. + + contact not ready + a partner nem áll készen + No comment provided by engineer. + + + contact should accept… + a partnernek el kell fogadnia… + No comment provided by engineer. + creator készítő @@ -9425,7 +9952,7 @@ pref value deleted group - törölt csoport + törölte a csoportot rcv group event chat item @@ -9475,7 +10002,7 @@ pref value encryption agreed - titkosítás elfogadva + titkosítása elfogadva chat item text @@ -9485,7 +10012,7 @@ pref value encryption ok - titkosítás rendben + titkosítása rendben van chat item text @@ -9538,11 +10065,21 @@ pref value továbbított No comment provided by engineer. + + group + csoport + shown on group welcome message + group deleted a csoport törölve No comment provided by engineer. + + group is deleted + csoport törölve + No comment provided by engineer. + group profile updated csoportprofil frissítve @@ -9605,7 +10142,7 @@ pref value invitation to group %@ - meghívás a(z) %@ csoportba + meghívás a(z) %@ nevű csoportba group name @@ -9625,7 +10162,7 @@ pref value invited to connect - Függőben lévő meghívó + függőben lévő kapcsolat chat list item title @@ -9638,11 +10175,6 @@ pref value dőlt No comment provided by engineer. - - join as %@ - csatlakozás mint %@ - No comment provided by engineer. - left elhagyta a csoportot @@ -9668,6 +10200,11 @@ pref value kapcsolódott rcv group event chat item + + member has old version + a tag régi verziót használ + No comment provided by engineer. + message üzenet @@ -9695,7 +10232,7 @@ pref value moderated by %@ - moderálva lett %@ által + %@ moderálta ezt az üzenetet marked deleted chat item preview text @@ -9733,6 +10270,11 @@ pref value nincs szöveg copied message info in history + + not synchronized + nincs szinkronizálva + No comment provided by engineer. + observer megfigyelő @@ -9743,16 +10285,17 @@ pref value kikapcsolva enabled status group pref value +member criteria value time to disappear offered %@ - %@ ajánlotta + felajánlotta a következőt: %@ feature offered item offered %1$@: %2$@ - ajánlotta: %1$@, ekkor: %2$@ + felajánlotta a következőt: %1$@: %2$@ feature offered item @@ -9795,6 +10338,11 @@ time to disappear jóváhagyásra vár No comment provided by engineer. + + pending review + függőben lévő áttekintés + No comment provided by engineer. + quantum resistant e2e encryption végpontok közötti kvantumbiztos titkosítás @@ -9835,6 +10383,11 @@ time to disappear eltávolította a kapcsolattartási címet profile update event chat item + + removed from group + eltávolítva a csoportból + No comment provided by engineer. + removed profile picture eltávolította a profilképét @@ -9845,11 +10398,41 @@ time to disappear eltávolította Önt rcv group event chat item + + request is sent + kérés elküldve + No comment provided by engineer. + + + request to join rejected + csatlakozási kérés elutasítva + No comment provided by engineer. + + + requested connection + partneri kapcsolatot kért + rcv group event chat item + + + requested connection from group %@ + a(z) %@ nevű csoportból partneri kapcsolatot kért + rcv direct event chat item + requested to connect - Függőben lévő meghívási kérelem + függőben lévő kapcsolat chat list item title + + review + áttekintés + No comment provided by engineer. + + + reviewed by admins + áttekintve a moderátorok által + No comment provided by engineer. + saved mentett @@ -9857,7 +10440,7 @@ time to disappear saved from %@ - elmentve innen: %@ + mentve innen: %@ No comment provided by engineer. @@ -9882,19 +10465,14 @@ time to disappear security code changed - a biztonsági kód módosult + biztonsági kódja módosult chat item text - - send direct message - közvetlen üzenet küldése - No comment provided by engineer. - server queue info: %1$@ last received msg: %2$@ - a kiszolgáló sorbaállítási információi: %1$@ + a kiszolgáló várólista információi: %1$@ utoljára fogadott üzenet: %2$@ queue info @@ -9956,12 +10534,12 @@ utoljára fogadott üzenet: %2$@ updated group profile - frissítette a csoport profilját + frissítette a csoportprofilt rcv group event chat item updated profile - frissített profil + frissítette a profilját profile update event chat item @@ -9991,7 +10569,7 @@ utoljára fogadott üzenet: %2$@ via relay - egy továbbítókiszolgálón keresztül + továbbítókiszolgálón keresztül No comment provided by engineer. @@ -10039,10 +10617,10 @@ utoljára fogadott üzenet: %2$@ Ön No comment provided by engineer. - - you are invited to group - Ön meghívást kapott a csoportba - No comment provided by engineer. + + you accepted this member + Ön befogadta ezt a tagot + snd group event chat item you are observer @@ -10143,7 +10721,7 @@ utoljára fogadott üzenet: %2$@ SimpleX needs access to Photo Library for saving captured and received media - A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez + A SimpleXnek hozzáférésre van szüksége a galériához a rögzített és fogadott média mentéséhez Privacy - Photo Library Additions Usage Description diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index cf5f61918f..6ddcf7d6d1 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -563,8 +563,19 @@ time interval Accetta accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Accetta come membro + alert action + + + Accept as observer + Accetta come osservatore + alert action + Accept conditions Accetta le condizioni @@ -575,6 +586,11 @@ swipe action Accettare la richiesta di connessione? No comment provided by engineer. + + Accept contact request + Accetta la richiesta di contatto + alert title + Accept contact request from %@? Accettare la richiesta di contatto da %@? @@ -583,9 +599,14 @@ swipe action Accept incognito Accetta in incognito - accept contact request via notification + alert action swipe action + + Accept member + Accetta membro + alert title + Accepted conditions Condizioni accettate @@ -626,6 +647,11 @@ swipe action Aggiungi elenco No comment provided by engineer. + + Add message + Aggiungi un messaggio + placeholder for sending contact request + Add profile Aggiungi profilo @@ -841,6 +867,11 @@ swipe action Consenti downgrade No comment provided by engineer. + + Allow files and media only if your contact allows them. + Consenti file e contenuti multimediali solo se il tuo contatto li consente. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore) @@ -926,6 +957,11 @@ swipe action Permetti ai tuoi contatti di inviare messaggi a tempo. No comment provided by engineer. + + Allow your contacts to send files and media. + Consenti ai tuoi contatti di inviare file e contenuti multimediali. + No comment provided by engineer. + Allow your contacts to send voice messages. Permetti ai tuoi contatti di inviare messaggi vocali. @@ -939,12 +975,12 @@ swipe action Already connecting! Già in connessione! - No comment provided by engineer. + new chat sheet title Already joining the group! Già in ingresso nel gruppo! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1151,11 +1187,6 @@ swipe action Auto-accetta le immagini No comment provided by engineer. - - Auto-accept settings - Accetta automaticamente le impostazioni - alert title - Back Indietro @@ -1231,6 +1262,16 @@ swipe action Esperienza utente migliorata No comment provided by engineer. + + Bio + Bio + No comment provided by engineer. + + + Bio too large + Bio troppo lunga + alert title + Black Nero @@ -1281,6 +1322,11 @@ swipe action Sfocatura dei file multimediali No comment provided by engineer. + + Bot + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Sia tu che il tuo contatto potete aggiungere reazioni ai messaggi. @@ -1301,6 +1347,11 @@ swipe action Sia tu che il tuo contatto potete inviare messaggi a tempo. No comment provided by engineer. + + Both you and your contact can send files and media. + Sia tu che il tuo contatto potete inviare file e contenuti multimediali. + No comment provided by engineer. + Both you and your contact can send voice messages. Sia tu che il tuo contatto potete inviare messaggi vocali. @@ -1321,6 +1372,11 @@ swipe action Chat di lavoro No comment provided by engineer. + + Business connection + Connessione lavorativa + No comment provided by engineer. + Businesses Lavorative @@ -1370,6 +1426,11 @@ swipe action Impossibile chiamare il membro No comment provided by engineer. + + Can't change profile + Impossibile cambiare profilo + alert title + Can't invite contact! Impossibile invitare il contatto! @@ -1389,7 +1450,8 @@ swipe action Cancel Annulla alert action -alert button +alert button +new chat action Cancel migration @@ -1495,7 +1557,7 @@ set passcode view Chat already exists! La chat esiste già! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1582,11 +1644,31 @@ set passcode view La chat verrà eliminata solo per te, non è reversibile! No comment provided by engineer. + + Chat with admins + Chat con amministratori + chat toolbar + + + Chat with member + Chatta con il membro + No comment provided by engineer. + + + Chat with members before they join. + Chatta con i membri prima che si uniscano. + No comment provided by engineer. + Chats Chat No comment provided by engineer. + + Chats with members + Chat con membri + No comment provided by engineer. + Check messages every 20 min. Controlla i messaggi ogni 20 min. @@ -1812,9 +1894,9 @@ set passcode view Connetti automaticamente No comment provided by engineer. - - Connect incognito - Connetti in incognito + + Connect faster! 🚀 + Connettiti più velocemente! 🚀 No comment provided by engineer. @@ -1827,44 +1909,39 @@ set passcode view Connettiti più velocemente ai tuoi amici. No comment provided by engineer. - - Connect to yourself? - Connettersi a te stesso? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Connettersi a te stesso? Questo è il tuo indirizzo SimpleX! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Connettersi a te stesso? Questo è il tuo link una tantum! - No comment provided by engineer. + new chat sheet title Connect via contact address Connettere via indirizzo del contatto - No comment provided by engineer. + new chat sheet title Connect via link Connetti via link - No comment provided by engineer. + new chat sheet title Connect via one-time link Connetti via link una tantum - No comment provided by engineer. + new chat sheet title Connect with %@ Connettersi con %@ - No comment provided by engineer. + new chat action Connected @@ -1929,7 +2006,7 @@ Questo è il tuo link una tantum! Connection error Errore di connessione - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1976,7 +2053,7 @@ Questo è il tuo link una tantum! Connection timeout Connessione scaduta - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2028,6 +2105,11 @@ Questo è il tuo link una tantum! Preferenze del contatto No comment provided by engineer. + + Contact requests from groups + Richieste di contatto dai gruppi + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Il contatto verrà eliminato - non è reversibile! @@ -2143,9 +2225,9 @@ Questo è il tuo link una tantum! Crea coda server test step - - Create secret group - Crea gruppo segreto + + Create your address + Crea il tuo indirizzo No comment provided by engineer. @@ -2402,6 +2484,11 @@ swipe action Eliminare il profilo di chat? No comment provided by engineer. + + Delete chat with member? + Eliminare la chat con il membro? + alert title + Delete chat? Eliminare la chat? @@ -2597,11 +2684,21 @@ swipe action Ricevute di consegna! No comment provided by engineer. + + Deprecated options + Opzioni deprecate + No comment provided by engineer. + Description Descrizione No comment provided by engineer. + + Description too large + Descrizione troppo lunga + alert title + Desktop address Indirizzo desktop @@ -2825,7 +2922,7 @@ swipe action Don't show again Non mostrare più - No comment provided by engineer. + alert action Done @@ -2908,6 +3005,11 @@ chat item action Modifica il profilo del gruppo No comment provided by engineer. + + Empty message! + Messaggio vuoto! + No comment provided by engineer. + Enable Attiva @@ -2943,6 +3045,11 @@ chat item action Attiva l'accesso alla fotocamera No comment provided by engineer. + + Enable disappearing messages by default. + Attiva i messaggi a tempo in modo predefinito. + No comment provided by engineer. + Enable for all Attiva per tutti @@ -3143,6 +3250,11 @@ chat item action Errore nell'accettazione della richiesta di contatto No comment provided by engineer. + + Error accepting member + Errore di accettazione del membro + alert title + Error adding member(s) Errore di aggiunta membro/i @@ -3153,11 +3265,21 @@ chat item action Errore di aggiunta del server alert title + + Error adding short link + Errore di aggiunta link breve + No comment provided by engineer. + Error changing address Errore nella modifica dell'indirizzo No comment provided by engineer. + + Error changing chat profile + Errore cambiando il profilo di chat + alert title + Error changing connection profile Errore nel cambio di profilo di connessione @@ -3171,7 +3293,7 @@ chat item action Error changing setting Errore nella modifica dell'impostazione - No comment provided by engineer. + alert title Error changing to incognito! @@ -3186,7 +3308,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Errore di connessione al server di inoltro %@. Riprova più tardi. - No comment provided by engineer. + alert message Error creating address @@ -3233,15 +3355,20 @@ chat item action Errore decifrando il file No comment provided by engineer. + + Error deleting chat + Errore di eliminazione della chat con il membro + alert title + Error deleting chat database Errore nell'eliminazione del database della chat - No comment provided by engineer. + alert title Error deleting chat! Errore nell'eliminazione della chat! - No comment provided by engineer. + alert title Error deleting connection @@ -3251,12 +3378,12 @@ chat item action Error deleting database Errore nell'eliminazione del database - No comment provided by engineer. + alert title Error deleting old database Errore nell'eliminazione del database vecchio - No comment provided by engineer. + alert title Error deleting token @@ -3291,7 +3418,7 @@ chat item action Error exporting chat database Errore nell'esportazione del database della chat - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3301,7 +3428,7 @@ chat item action Error importing chat database Errore nell'importazione del database della chat - No comment provided by engineer. + alert title Error joining group @@ -3323,6 +3450,11 @@ chat item action Errore di apertura della chat No comment provided by engineer. + + Error opening group + Errore di preparazione del gruppo + No comment provided by engineer. + Error receiving file Errore nella ricezione del file @@ -3343,10 +3475,15 @@ chat item action Errore di registrazione per le notifiche alert title + + Error rejecting contact request + Errore nel rifiuto della richiesta di contatto + alert title + Error removing member Errore nella rimozione del membro - No comment provided by engineer. + alert title Error reordering lists @@ -3418,6 +3555,11 @@ chat item action Errore nell'invio del messaggio No comment provided by engineer. + + Error setting auto-accept + Errore impostando l'accettazione automatica + No comment provided by engineer. + Error setting delivery receipts! Errore nell'impostazione delle ricevute di consegna! @@ -3436,7 +3578,7 @@ chat item action Error switching profile Errore nel cambio di profilo - No comment provided by engineer. + alert title Error switching profile! @@ -3500,6 +3642,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Errore: l'URL non è valido @@ -3679,6 +3825,11 @@ snd error text File e multimediali chat feature + + Files and media are prohibited in this chat. + File e contenuti multimediali sono vietati in questa chat. + No comment provided by engineer. + Files and media are prohibited. File e contenuti multimediali sono vietati in questo gruppo. @@ -3719,6 +3870,23 @@ snd error text Trova le chat più velocemente No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Correggi @@ -3830,9 +3998,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Il server di inoltro %1$@ non è riuscito a connettersi al server di destinazione %2$@. Riprova più tardi. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3926,7 +4094,7 @@ Errore: %2$@ Group already exists! Il gruppo esiste già! - No comment provided by engineer. + new chat sheet title Group display name @@ -3993,6 +4161,11 @@ Errore: %2$@ Il profilo del gruppo è memorizzato sui dispositivi dei membri, non sui server. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Il profilo del gruppo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato ai membri del gruppo. + alert message + Group welcome message Messaggio di benvenuto del gruppo @@ -4375,7 +4548,7 @@ Altri miglioramenti sono in arrivo! Invalid link Link non valido - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4488,37 +4661,32 @@ Altri miglioramenti sono in arrivo! Entra swipe action + + Join as %@ + entra come %@ + No comment provided by engineer. + Join group Entra nel gruppo - No comment provided by engineer. + new chat sheet title Join group conversations Entra in conversazioni di gruppo No comment provided by engineer. - - Join group? - Entrare nel gruppo? - No comment provided by engineer. - Join incognito Entra in incognito No comment provided by engineer. - - Join with current profile - Entra con il profilo attuale - No comment provided by engineer. - Join your group? This is your link for group %@! Entrare nel tuo gruppo? Questo è il tuo link per il gruppo %@! - No comment provided by engineer. + new chat action Joining group @@ -4545,6 +4713,11 @@ Questo è il tuo link per il gruppo %@! Tenere l'invito inutilizzato? alert title + + Keep your chats clean + Mantieni le chat pulite + No comment provided by engineer. + Keep your connections Mantieni le tue connessioni @@ -4600,6 +4773,11 @@ Questo è il tuo link per il gruppo %@! Uscire dal gruppo? No comment provided by engineer. + + Less traffic on mobile networks. + Meno traffico sulle reti mobili. + No comment provided by engineer. + Let's talk in SimpleX Chat Parliamo in SimpleX Chat @@ -4655,6 +4833,11 @@ Questo è il tuo link per il gruppo %@! Messaggi in diretta No comment provided by engineer. + + Loading profile… + Caricamento del profilo… + in progress text + Local name Nome locale @@ -4730,11 +4913,26 @@ Questo è il tuo link per il gruppo %@! Membro No comment provided by engineer. + + Member %@ + Membro %@ + past/unknown group member + + + Member admission + Ammissione dei membri + No comment provided by engineer. + Member inactive Membro inattivo item status text + + Member is deleted - can't accept request + Il membro è eliminato - impossibile accettare la richiesta + No comment provided by engineer. + Member reports Segnalazioni dei membri @@ -4765,6 +4963,11 @@ Questo è il tuo link per il gruppo %@! Il membro verrà rimosso dal gruppo, non è reversibile! No comment provided by engineer. + + Member will join the group, accept member? + Il membro entrerà nel gruppo, accettarlo? + alert message + Members can add message reactions. I membri del gruppo possono aggiungere reazioni ai messaggi. @@ -4840,6 +5043,11 @@ Questo è il tuo link per il gruppo %@! Messaggio inoltrato item status text + + Message instantly once you tap Connect. + Parla immediatamente appena tocchi Connetti. + No comment provided by engineer. + Message may be delivered later if member becomes active. Il messaggio può essere consegnato più tardi se il membro diventa attivo. @@ -4915,6 +5123,11 @@ Questo è il tuo link per il gruppo %@! Messaggi No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + I messaggi sono protetti da **crittografia end-to-end**. + No comment provided by engineer. + Messages from %@ will be shown! I messaggi da %@ verranno mostrati! @@ -5170,6 +5383,11 @@ Questo è il tuo link per il gruppo %@! Nuovi eventi notification + + New group role: Moderator + Nuovo ruolo nei gruppi: Moderatore + No comment provided by engineer. + New in %@ Novità nella %@ @@ -5185,6 +5403,11 @@ Questo è il tuo link per il gruppo %@! Nuovo ruolo del membro No comment provided by engineer. + + New member wants to join the group. + Un nuovo membro vuole entrare nel gruppo. + rcv group event chat item + New message Nuovo messaggio @@ -5225,6 +5448,11 @@ Questo è il tuo link per il gruppo %@! Nessuna chat nell'elenco %@ No comment provided by engineer. + + No chats with members + Nessuna chat con membri + No comment provided by engineer. + No contacts selected Nessun contatto selezionato @@ -5305,6 +5533,11 @@ Questo è il tuo link per il gruppo %@! Nessuna autorizzazione per registrare messaggi vocali No comment provided by engineer. + + No private routing session + Nessuna sessione di instradamento privato + alert title + No push server Locale @@ -5417,7 +5650,9 @@ Questo è il tuo link per il gruppo %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5508,6 +5743,11 @@ Richiede l'attivazione della VPN. Solo tu puoi inviare messaggi a tempo. No comment provided by engineer. + + Only you can send files and media. + Solo tu puoi inviare file e contenuti multimediali. + No comment provided by engineer. + Only you can send voice messages. Solo tu puoi inviare messaggi vocali. @@ -5533,6 +5773,11 @@ Richiede l'attivazione della VPN. Solo il tuo contatto può inviare messaggi a tempo. No comment provided by engineer. + + Only your contact can send files and media. + Solo il tuo contatto può inviare file e contenuti multimediali. + No comment provided by engineer. + Only your contact can send voice messages. Solo il tuo contatto può inviare messaggi vocali. @@ -5556,25 +5801,36 @@ Richiede l'attivazione della VPN. Open chat Apri chat - No comment provided by engineer. + new chat action Open chat console Apri la console della chat authentication reason + + Open clean link + Apri link pulito + alert action + Open conditions Apri le condizioni No comment provided by engineer. + + Open full link + Apri link completo + alert action + Open group Apri gruppo - No comment provided by engineer. + new chat action Open link? + Aprire il link? alert title @@ -5582,6 +5838,36 @@ Richiede l'attivazione della VPN. Apri migrazione ad un altro dispositivo authentication reason + + Open new chat + Apri una chat nuova + new chat action + + + Open new group + Apri un gruppo nuovo + new chat action + + + Open to accept + Apri per accettare + No comment provided by engineer. + + + Open to connect + Apri per connettere + No comment provided by engineer. + + + Open to join + Apri per entrare + No comment provided by engineer. + + + Open to use bot + Apri per usare il bot + No comment provided by engineer. + Opening app… Apertura dell'app… @@ -5689,11 +5975,6 @@ Richiede l'attivazione della VPN. Password per mostrare No comment provided by engineer. - - Past member %@ - Membro passato %@ - past/unknown group member - Paste desktop address Incolla l'indirizzo desktop @@ -5764,7 +6045,7 @@ Si prega di condividere qualsiasi altro problema con gli sviluppatori. Please check your network connection with %@ and try again. Controlla la tua connessione di rete con %@ e riprova. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5828,6 +6109,11 @@ Errore: %@ Prova a disattivare e riattivare le notifiche. token info + + Please wait for group moderators to review your request to join the group. + Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo. + snd group event chat item + Please wait for token activation to complete. Attendi il completamento dell'attivazione del token. @@ -5848,11 +6134,6 @@ Errore: %@ Porta No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata - server test error - Preserve the last message draft, with attachments. Conserva la bozza dell'ultimo messaggio, con gli allegati. @@ -5936,7 +6217,12 @@ Errore: %@ Private routing error Errore di instradamento privato - No comment provided by engineer. + alert title + + + Private routing timeout + Scadenza dell'instradamento privato + alert title Profile and server connections @@ -6040,6 +6326,11 @@ Attivalo nelle impostazioni *Rete e server*. Proteggi i tuoi profili di chat con una password! No comment provided by engineer. + + Protocol background timeout + Scadenza del protocollo in sec. piano + No comment provided by engineer. + Protocol timeout Scadenza del protocollo @@ -6268,7 +6559,8 @@ Attivalo nelle impostazioni *Rete e server*. Reject Rifiuta - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6279,7 +6571,12 @@ swipe action Reject contact request Rifiuta la richiesta di contatto - No comment provided by engineer. + alert title + + + Reject member? + Rifiutare il membro? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6306,6 +6603,11 @@ swipe action Rimuovi immagine No comment provided by engineer. + + Remove link tracking + Rimuovi il tracciamento del link + No comment provided by engineer. + Remove member Rimuovi membro @@ -6321,6 +6623,11 @@ swipe action Rimuovere la password dal portachiavi? No comment provided by engineer. + + Removes messages and blocks members. + Rimuove i messaggi e blocca i membri. + No comment provided by engineer. + Renegotiate Rinegoziare @@ -6336,11 +6643,6 @@ swipe action Rinegoziare la crittografia? No comment provided by engineer. - - Repeat connection request? - Ripetere la richiesta di connessione? - No comment provided by engineer. - Repeat download Ripeti scaricamento @@ -6351,11 +6653,6 @@ swipe action Ripeti importazione No comment provided by engineer. - - Repeat join request? - Ripetere la richiesta di ingresso? - No comment provided by engineer. - Repeat upload Ripeti caricamento @@ -6391,6 +6688,11 @@ swipe action Motivo della segnalazione? No comment provided by engineer. + + Report sent to moderators + Segnalazione inviata ai moderatori + alert title + Report spam: only group moderators will see it. Segnala spam: solo i moderatori del gruppo lo vedranno. @@ -6494,7 +6796,7 @@ swipe action Retry Riprova - No comment provided by engineer. + alert action Reveal @@ -6506,6 +6808,21 @@ swipe action Leggi le condizioni No comment provided by engineer. + + Review group members + Revisiona i membri del gruppo + No comment provided by engineer. + + + Review members + Revisiona i membri + admission stage + + + Review members before admitting ("knocking"). + Revisiona i membri prima di ammetterli ("bussare"). + admission stage description + Revoke Revoca @@ -6562,6 +6879,16 @@ chat item action Salva (e avvisa i contatti) alert button + + Save (and notify members) + Salva (e informa i membri) + alert button + + + Save admission settings? + Salvare le impostazioni di ammissione? + alert title + Save and notify contact Salva e avvisa il contatto @@ -6587,6 +6914,11 @@ chat item action Salva il profilo del gruppo No comment provided by engineer. + + Save group profile? + Salvare il profilo del gruppo? + alert title + Save list Salva elenco @@ -6782,6 +7114,11 @@ chat item action Invia un messaggio in diretta: si aggiornerà per i destinatari mentre lo digiti No comment provided by engineer. + + Send contact request? + Inviare una richiesta di contatto? + No comment provided by engineer. + Send delivery receipts to Invia ricevute di consegna a @@ -6847,6 +7184,16 @@ chat item action Invia ricevute No comment provided by engineer. + + Send request + Invia richiesta + No comment provided by engineer. + + + Send request without message + Invia richiesta senza messaggio + No comment provided by engineer. + Send them from gallery or custom keyboards. Inviali dalla galleria o dalle tastiere personalizzate. @@ -6857,6 +7204,11 @@ chat item action Invia fino a 100 ultimi messaggi ai nuovi membri. No comment provided by engineer. + + Send your private feedback to groups. + Invia i tuoi commenti privati ai gruppi. + No comment provided by engineer. + Sender cancelled file transfer. Il mittente ha annullato il trasferimento del file. @@ -6997,13 +7349,13 @@ chat item action Il protocollo del server è cambiato. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Il server richiede l'autorizzazione di creare code, controlla la password server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Il server richiede l'autorizzazione per il caricamento, controllare la password server test error @@ -7077,6 +7429,11 @@ chat item action Impostalo al posto dell'autenticazione di sistema. No comment provided by engineer. + + Set member admission + Imposta l'ammissione dei membri + No comment provided by engineer. + Set message expiration in chats. Imposta la scadenza dei messaggi nelle chat. @@ -7097,6 +7454,11 @@ chat item action Imposta la password per esportare No comment provided by engineer. + + Set profile bio and welcome message. + Imposta la bio del profilo e il messaggio di benvenuto. + No comment provided by engineer. + Set the message shown to new members! Imposta il messaggio mostrato ai nuovi membri! @@ -7168,6 +7530,16 @@ chat item action Condividi link No comment provided by engineer. + + Share old address + Condividi l'indirizzo vecchio + alert button + + + Share old link + Condividi il link completo + alert button + Share profile Condividi il profilo @@ -7188,6 +7560,21 @@ chat item action Condividi con i contatti No comment provided by engineer. + + Share your address + Condividi il tuo indirizzo + No comment provided by engineer. + + + Short SimpleX address + Indirizzo breve di SimpleX + No comment provided by engineer. + + + Short description + Descrizione breve + No comment provided by engineer. + Short link Link breve @@ -7293,6 +7680,11 @@ chat item action Indirizzo SimpleX o link una tantum? No comment provided by engineer. + + SimpleX address settings + Accetta automaticamente le impostazioni + alert title + SimpleX channel link Link del canale SimpleX @@ -7338,6 +7730,11 @@ chat item action Protocolli di SimpleX esaminati da Trail of Bits. No comment provided by engineer. + + SimpleX relay link + Link del relay SimpleX + simplex link type + Simplified incognito mode Modalità incognito semplificata @@ -7551,6 +7948,11 @@ report reason Connessione TCP No comment provided by engineer. + + TCP connection bg timeout + Scadenza conness. TCP in sec. piano + No comment provided by engineer. + TCP connection timeout Scadenza connessione TCP @@ -7586,11 +7988,31 @@ report reason Scatta foto No comment provided by engineer. + + Tap Connect to chat + Tocca Connetti per chattare + No comment provided by engineer. + + + Tap Connect to send request + Tocca Connetti per inviare la richiesta + No comment provided by engineer. + + + Tap Connect to use bot + Tocca Connetti per usare il bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi. No comment provided by engineer. + + Tap Join group + Tocca Entra nel gruppo + No comment provided by engineer. + Tap button Tocca il pulsante @@ -7678,6 +8100,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + L'indirizzo sarà breve e il tuo profilo verrà condiviso attraverso l'indirizzo. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare. @@ -7738,6 +8165,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.L'hash del messaggio precedente è diverso. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + Il link sarà breve e il profilo del gruppo verrà condiviso attraverso il link. + alert message + The message will be deleted for all members. Il messaggio verrà eliminato per tutti i membri. @@ -7781,7 +8213,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The sender will NOT be notified Il mittente NON verrà avvisato - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7873,16 +8305,6 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Questo gruppo non esiste più. No comment provided by engineer. - - This is your own SimpleX address! - Questo è il tuo indirizzo SimpleX! - No comment provided by engineer. - - - This is your own one-time link! - Questo è il tuo link una tantum! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. Questo link richiede una versione più recente dell'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile. @@ -7903,6 +8325,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Questa impostazione si applica ai messaggi del profilo di chat attuale **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + Questa impostazione è per il tuo profilo attuale **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Il tempo di scomparsa è impostato solo per i contatti nuovi. + No comment provided by engineer. + Title Titoli @@ -7985,11 +8417,21 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Per inviare No comment provided by engineer. + + To send commands you must be connected. + Per inviare comandi devi essere connesso/a. + alert message + To support instant push notifications the chat database has to be migrated. Per supportare le notifiche push istantanee, il database della chat deve essere migrato. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Per usare un altro profilo dopo il tentativo di connessione, elimina la chat e usa di nuovo il link. + alert message + To use the servers of **%@**, accept conditions of use. Per usare i server di **%@**, accetta le condizioni d'uso. @@ -8227,11 +8669,41 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e L'aggiornamento delle impostazioni riconnetterà il client a tutti i server. No comment provided by engineer. + + Upgrade + Aggiorna + alert button + + + Upgrade address + Aggiorna l'indirizzo + No comment provided by engineer. + + + Upgrade address? + Aggiornare l'indirizzo? + alert message + Upgrade and open chat Aggiorna e apri chat No comment provided by engineer. + + Upgrade group link? + Aggiornare il link del gruppo? + alert message + + + Upgrade link + Aggiungi link + No comment provided by engineer. + + + Upgrade your address + Aggiorna il tuo indirizzo + No comment provided by engineer. + Upload errors Errori di invio @@ -8300,7 +8772,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use current profile Usa il profilo attuale - No comment provided by engineer. + new chat action Use for files @@ -8327,10 +8799,15 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa interfaccia di chiamata iOS No comment provided by engineer. + + Use incognito profile + Usa profilo in incognito + No comment provided by engineer. + Use new incognito profile Usa nuovo profilo in incognito - No comment provided by engineer. + new chat action Use only local notifications? @@ -8357,11 +8834,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa i server No comment provided by engineer. - - Use short links (BETA) - Usa link brevi (BETA) - No comment provided by engineer. - Use the app while in the call. Usa l'app mentre sei in chiamata. @@ -8567,6 +9039,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Il messaggio di benvenuto è troppo lungo No comment provided by engineer. + + Welcome your contacts 👋 + Dai il benvenuto ai tuoi contatti 👋 + No comment provided by engineer. + What's new Novità @@ -8690,12 +9167,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e You are already connecting to %@. Ti stai già connettendo a %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Ti stai già connettendo tramite questo link una tantum! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8705,24 +9182,19 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e You are already joining the group %@. Stai già entrando nel gruppo %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Stai già entrando nel gruppo tramite questo link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Stai già entrando nel gruppo tramite questo link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Stai già entrando nel gruppo! Ripetere la richiesta di ingresso? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8839,10 +9311,15 @@ Ripetere la richiesta di ingresso? Puoi vedere di nuovo il link di invito nei dettagli di connessione. alert message + + You can view your reports in Chat with admins. + Puoi vedere le tue segnalazioni nella chat con gli amministratori. + alert message + You can't send messages! Non puoi inviare messaggi! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8854,17 +9331,12 @@ Ripetere la richiesta di ingresso? Sei tu a decidere chi può connettersi. No comment provided by engineer. - - You have already requested connection via this address! - Hai già richiesto la connessione tramite questo indirizzo! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Hai già richiesto la connessione! Ripetere la richiesta di connessione? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8926,6 +9398,11 @@ Ripetere la richiesta di connessione? Dovresti ricevere le notifiche. token info + + You will be able to send messages **only after your request is accepted**. + Potrai inviare messaggi **solo dopo che la tua richiesta verrà accettata**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Verrai connesso/a al gruppo quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi! @@ -8951,11 +9428,6 @@ Ripetere la richiesta di connessione? Dovrai autenticarti quando avvii o riapri l'app dopo 30 secondi in secondo piano. No comment provided by engineer. - - You will connect to all group members. - Ti connetterai a tutti i membri del gruppo. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi. @@ -8996,6 +9468,11 @@ Ripetere la richiesta di connessione? Il tuo indirizzo SimpleX No comment provided by engineer. + + Your business contact + Il tuo contatto lavorativo + No comment provided by engineer. + Your calls Le tue chiamate @@ -9021,11 +9498,21 @@ Ripetere la richiesta di connessione? I tuoi profili di chat No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + La tua chat è stata spostata su %@ , ma si è verificato un errore imprevisto mentre venivi reindirizzato/a al profilo. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. La tua connessione è stata spostata a %@, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo. No comment provided by engineer. + + Your contact + Il tuo contatto + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@). @@ -9056,6 +9543,11 @@ Ripetere la richiesta di connessione? Il tuo profilo attuale No comment provided by engineer. + + Your group + Il tuo gruppo + No comment provided by engineer. + Your preferences Le tue preferenze @@ -9141,6 +9633,11 @@ Ripetere la richiesta di connessione? sopra, quindi scegli: No comment provided by engineer. + + accepted %@ + %@ accettato + rcv group event chat item + accepted call chiamata accettata @@ -9151,6 +9648,11 @@ Ripetere la richiesta di connessione? invito accettato chat list item title + + accepted you + ti ha accettato/a + rcv group event chat item + admin amministratore @@ -9171,6 +9673,11 @@ Ripetere la richiesta di connessione? concordando la crittografia… chat item text + + all + tutti + member criteria value + all members tutti i membri @@ -9257,6 +9764,11 @@ marked deleted chat item preview text chiamata… call status + + can't send messages + impossibile inviare messaggi + No comment provided by engineer. + cancelled %@ annullato %@ @@ -9307,11 +9819,6 @@ marked deleted chat item preview text connesso/a No comment provided by engineer. - - connected directly - si è connesso/a direttamente - rcv group event chat item - connecting in connessione @@ -9362,6 +9869,16 @@ marked deleted chat item preview text contatto %1$@ cambiato in %2$@ profile update event chat item + + contact deleted + contatto eliminato + No comment provided by engineer. + + + contact disabled + contatto disattivato + No comment provided by engineer. + contact has e2e encryption il contatto ha la crittografia e2e @@ -9372,6 +9889,16 @@ marked deleted chat item preview text il contatto non ha la crittografia e2e No comment provided by engineer. + + contact not ready + contatto non pronto + No comment provided by engineer. + + + contact should accept… + il contatto dovrebbe accettare… + No comment provided by engineer. + creator creatore @@ -9538,11 +10065,21 @@ pref value inoltrato No comment provided by engineer. + + group + gruppo + shown on group welcome message + group deleted gruppo eliminato No comment provided by engineer. + + group is deleted + il gruppo è eliminato + No comment provided by engineer. + group profile updated profilo del gruppo aggiornato @@ -9638,11 +10175,6 @@ pref value corsivo No comment provided by engineer. - - join as %@ - entra come %@ - No comment provided by engineer. - left è uscito/a @@ -9668,6 +10200,11 @@ pref value si è connesso/a rcv group event chat item + + member has old version + il membro ha una versione vecchia + No comment provided by engineer. + message messaggio @@ -9733,6 +10270,11 @@ pref value nessun testo copied message info in history + + not synchronized + non sincronizzato + No comment provided by engineer. + observer osservatore @@ -9743,6 +10285,7 @@ pref value off enabled status group pref value +member criteria value time to disappear @@ -9795,6 +10338,11 @@ time to disappear in attesa di approvazione No comment provided by engineer. + + pending review + in attesa di revisione + No comment provided by engineer. + quantum resistant e2e encryption crittografia e2e resistente alla quantistica @@ -9835,6 +10383,11 @@ time to disappear indirizzo di contatto rimosso profile update event chat item + + removed from group + rimosso dal gruppo + No comment provided by engineer. + removed profile picture immagine del profilo rimossa @@ -9845,11 +10398,41 @@ time to disappear ti ha rimosso/a rcv group event chat item + + request is sent + richiesta inviata + No comment provided by engineer. + + + request to join rejected + richiesta di entrare rifiutata + No comment provided by engineer. + + + requested connection + connessione richiesta + rcv group event chat item + + + requested connection from group %@ + connessione richiesta dal gruppo %@ + rcv direct event chat item + requested to connect richiesto di connettersi chat list item title + + review + revisiona + No comment provided by engineer. + + + reviewed by admins + revisionato dagli amministratori + No comment provided by engineer. + saved salvato @@ -9885,11 +10468,6 @@ time to disappear codice di sicurezza modificato chat item text - - send direct message - invia messaggio diretto - No comment provided by engineer. - server queue info: %1$@ @@ -10039,10 +10617,10 @@ ultimo msg ricevuto: %2$@ tu No comment provided by engineer. - - you are invited to group - sei stato/a invitato/a al gruppo - No comment provided by engineer. + + you accepted this member + hai accettato questo membro + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 27134216a7..0ca54bb3d9 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -192,6 +192,7 @@ %d seconds(s) + %d 秒 delete after time @@ -206,7 +207,6 @@ %lld - No comment provided by engineer. @@ -465,6 +465,7 @@ time interval 1 year + 1年 delete after time @@ -548,6 +549,7 @@ time interval About operators + オペレーターについて No comment provided by engineer. @@ -559,10 +561,22 @@ time interval 承諾 accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + メンバーとして承認する + alert action + + + Accept as observer + オブザーバーとして承認する + alert action + Accept conditions + 条件に同意する No comment provided by engineer. @@ -570,6 +584,11 @@ swipe action 接続要求を承認? No comment provided by engineer. + + Accept contact request + 連絡先リクエストを受け入れる + alert title + Accept contact request from %@? %@ からの連絡要求を受け入れますか? @@ -578,15 +597,22 @@ swipe action Accept incognito シークレットモードで承諾 - accept contact request via notification + alert action swipe action + + Accept member + メンバーを承認する + alert title + Accepted conditions + 承諾された条件 No comment provided by engineer. Acknowledged + 了承済み No comment provided by engineer. @@ -614,6 +640,10 @@ swipe action Add list No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile プロフィールを追加 @@ -810,6 +840,10 @@ swipe action Allow downgrade No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) 送信相手も永久メッセージ削除を許可する時のみに許可する。(24時間) @@ -894,6 +928,10 @@ swipe action 送信相手が消えるメッセージを送るのを許可する。 No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. 送信相手からの音声メッセージを許可する。 @@ -907,12 +945,12 @@ swipe action Already connecting! 既に接続中です! - No comment provided by engineer. + new chat sheet title Already joining the group! すでにグループに参加しています! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1107,10 +1145,6 @@ swipe action 画像を自動的に受信 No comment provided by engineer. - - Auto-accept settings - alert title - Back 戻る @@ -1175,6 +1209,14 @@ swipe action Better user experience No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black No comment provided by engineer. @@ -1215,6 +1257,10 @@ swipe action Blur media No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. 自分も相手もメッセージへのリアクションを追加できます。 @@ -1235,6 +1281,10 @@ swipe action あなたと連絡相手が消えるメッセージを送信できます。 No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. あなたと連絡相手が音声メッセージを送信できます。 @@ -1253,6 +1303,10 @@ swipe action Business chats No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses No comment provided by engineer. @@ -1294,6 +1348,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! 連絡先を招待できません! @@ -1312,7 +1370,8 @@ swipe action Cancel 中止 alert action -alert button +alert button +new chat action Cancel migration @@ -1409,7 +1468,7 @@ set passcode view Chat already exists! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1488,11 +1547,27 @@ set passcode view Chat will be deleted for you - this cannot be undone! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats チャット No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1695,9 +1770,8 @@ set passcode view Connect automatically No comment provided by engineer. - - Connect incognito - シークレットモードで接続 + + Connect faster! 🚀 No comment provided by engineer. @@ -1710,37 +1784,33 @@ set passcode view 友達ともっと速くつながりましょう。 No comment provided by engineer. - - Connect to yourself? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! - No comment provided by engineer. + new chat sheet title Connect via contact address - No comment provided by engineer. + new chat sheet title Connect via link リンク経由で接続 - No comment provided by engineer. + new chat sheet title Connect via one-time link ワンタイムリンクで接続 - No comment provided by engineer. + new chat sheet title Connect with %@ - No comment provided by engineer. + new chat action Connected @@ -1804,7 +1874,7 @@ This is your own one-time link! Connection error 接続エラー - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1845,7 +1915,7 @@ This is your own one-time link! Connection timeout 接続タイムアウト - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1893,6 +1963,10 @@ This is your own one-time link! 連絡先の設定 No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! No comment provided by engineer. @@ -1998,9 +2072,8 @@ This is your own one-time link! キューの作成 server test step - - Create secret group - シークレットグループを作成する + + Create your address No comment provided by engineer. @@ -2244,6 +2317,10 @@ swipe action チャットのプロフィールを削除しますか? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? No comment provided by engineer. @@ -2428,11 +2505,19 @@ swipe action 配信通知! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description 説明 No comment provided by engineer. + + Description too large + alert title + Desktop address No comment provided by engineer. @@ -2637,7 +2722,7 @@ swipe action Don't show again 次から表示しない - No comment provided by engineer. + alert action Done @@ -2710,6 +2795,10 @@ chat item action グループのプロフィールを編集 No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable 有効 @@ -2743,6 +2832,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all すべて有効 @@ -2931,6 +3024,10 @@ chat item action 連絡先リクエストの承諾にエラー発生 No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) メンバー追加にエラー発生 @@ -2940,11 +3037,19 @@ chat item action Error adding server alert title + + Error adding short link + No comment provided by engineer. + Error changing address アドレス変更にエラー発生 No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile No comment provided by engineer. @@ -2957,7 +3062,7 @@ chat item action Error changing setting 設定変更にエラー発生 - No comment provided by engineer. + alert title Error changing to incognito! @@ -2969,7 +3074,7 @@ chat item action Error connecting to forwarding server %@. Please try later. - No comment provided by engineer. + alert message Error creating address @@ -3013,15 +3118,19 @@ chat item action ファイルの復号エラー No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database チャットデータベース削除にエラー発生 - No comment provided by engineer. + alert title Error deleting chat! チャット削除にエラー発生! - No comment provided by engineer. + alert title Error deleting connection @@ -3031,12 +3140,12 @@ chat item action Error deleting database データベースの削除にエラー発生 - No comment provided by engineer. + alert title Error deleting old database 古いデータベースを削除にエラー発生 - No comment provided by engineer. + alert title Error deleting token @@ -3069,7 +3178,7 @@ chat item action Error exporting chat database チャットデータベースのエキスポートにエラー発生 - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3078,7 +3187,7 @@ chat item action Error importing chat database チャットデータベースのインポートにエラー発生 - No comment provided by engineer. + alert title Error joining group @@ -3097,6 +3206,10 @@ chat item action Error opening chat No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file ファイル受信にエラー発生 @@ -3114,10 +3227,14 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + alert title + Error removing member メンバー除名にエラー発生 - No comment provided by engineer. + alert title Error reordering lists @@ -3183,6 +3300,10 @@ chat item action メッセージ送信にエラー発生 No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! No comment provided by engineer. @@ -3199,7 +3320,7 @@ chat item action Error switching profile - No comment provided by engineer. + alert title Error switching profile! @@ -3259,6 +3380,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid エラー: 無効なURL @@ -3418,6 +3543,10 @@ snd error text ファイルとメディア chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. このグループでは、ファイルとメディアは禁止されています。 @@ -3455,6 +3584,23 @@ snd error text チャットを素早く検索 No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + サーバアドレスの証明証IDが正しくないかもしれません + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix 修正 @@ -3551,8 +3697,8 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3633,7 +3779,7 @@ Error: %2$@ Group already exists! - No comment provided by engineer. + new chat sheet title Group display name @@ -3700,6 +3846,10 @@ Error: %2$@ グループのプロフィールはサーバではなく、メンバーの端末に保存されます。 No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message グループのウェルカムメッセージ @@ -4053,7 +4203,7 @@ More improvements are coming soon! Invalid link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4161,32 +4311,29 @@ More improvements are coming soon! 参加 swipe action + + Join as %@ + %@ として参加 + No comment provided by engineer. + Join group グループに参加 - No comment provided by engineer. + new chat sheet title Join group conversations No comment provided by engineer. - - Join group? - No comment provided by engineer. - Join incognito シークレットモードで参加 No comment provided by engineer. - - Join with current profile - No comment provided by engineer. - Join your group? This is your link for group %@! - No comment provided by engineer. + new chat action Joining group @@ -4209,6 +4356,10 @@ This is your link for group %@! Keep unused invitation? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections 接続を維持 @@ -4262,6 +4413,10 @@ This is your link for group %@! グループを脱退しますか? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat SimpleXチャットで会話しよう @@ -4311,6 +4466,10 @@ This is your link for group %@! ライブメッセージ No comment provided by engineer. + + Loading profile… + in progress text + Local name ローカルネーム @@ -4384,10 +4543,22 @@ This is your link for group %@! メンバー No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4415,6 +4586,10 @@ This is your link for group %@! メンバーをグループから除名する (※元に戻せません※)! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. グループメンバーはメッセージへのリアクションを追加できます。 @@ -4483,6 +4658,10 @@ This is your link for group %@! Message forwarded item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. item status description @@ -4549,6 +4728,10 @@ This is your link for group %@! メッセージ & ファイル No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! No comment provided by engineer. @@ -4778,6 +4961,10 @@ This is your link for group %@! New events notification + + New group role: Moderator + No comment provided by engineer. + New in %@ %@ の新機能 @@ -4792,6 +4979,10 @@ This is your link for group %@! 新しいメンバーの役割 No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message 新しいメッセージ @@ -4828,6 +5019,10 @@ This is your link for group %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected 連絡先が選択されてません @@ -4900,6 +5095,10 @@ This is your link for group %@! 音声メッセージを録音する権限がありません No comment provided by engineer. + + No private routing session + alert title + No push server 自分のみ @@ -4998,7 +5197,9 @@ This is your link for group %@! Ok OK - alert button + alert action +alert button +new chat action Old database @@ -5085,6 +5286,10 @@ VPN を有効にする必要があります。 消えるメッセージを送れるのはあなただけです。 No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. 音声メッセージを送れるのはあなただけです。 @@ -5110,6 +5315,10 @@ VPN を有効にする必要があります。 消えるメッセージを送れるのはあなたの連絡相手だけです。 No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. 音声メッセージを送れるのはあなたの連絡相手だけです。 @@ -5132,20 +5341,28 @@ VPN を有効にする必要があります。 Open chat チャットを開く - No comment provided by engineer. + new chat action Open chat console チャットのコンソールを開く authentication reason + + Open clean link + alert action + Open conditions No comment provided by engineer. + + Open full link + alert action + Open group - No comment provided by engineer. + new chat action Open link? @@ -5155,6 +5372,30 @@ VPN を有効にする必要があります。 Open migration to another device authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… No comment provided by engineer. @@ -5248,10 +5489,6 @@ VPN を有効にする必要があります。 パスワードを表示する No comment provided by engineer. - - Past member %@ - past/unknown group member - Paste desktop address No comment provided by engineer. @@ -5313,7 +5550,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. %@ を使用してネットワーク接続を確認し、再試行してください。 - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5373,6 +5610,10 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5390,11 +5631,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - サーバアドレスの証明証IDが正しくないかもしれません - server test error - Preserve the last message draft, with attachments. 添付を含めて、下書きを保存する。 @@ -5468,7 +5704,11 @@ Error: %@ Private routing error - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5565,6 +5805,10 @@ Enable in *Network & servers* settings. チャットのプロフィールをパスワードで保護します! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout プロトコル・タイムアウト @@ -5771,7 +6015,8 @@ Enable in *Network & servers* settings. Reject 拒否 - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -5782,7 +6027,11 @@ swipe action Reject contact request 連絡要求を拒否する - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -5807,6 +6056,10 @@ swipe action Remove image No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member メンバーを除名する @@ -5822,6 +6075,10 @@ swipe action キーチェーンからパスフレーズを削除しますか? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate 再ネゴシエート @@ -5837,10 +6094,6 @@ swipe action 暗号化を再ネゴシエートしますか? No comment provided by engineer. - - Repeat connection request? - No comment provided by engineer. - Repeat download No comment provided by engineer. @@ -5849,10 +6102,6 @@ swipe action Repeat import No comment provided by engineer. - - Repeat join request? - No comment provided by engineer. - Repeat upload No comment provided by engineer. @@ -5882,6 +6131,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -5974,7 +6227,7 @@ swipe action Retry - No comment provided by engineer. + alert action Reveal @@ -5985,6 +6238,18 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke 取り消す @@ -6037,6 +6302,14 @@ chat item action 保存(連絡先に通知) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact 保存して、連絡先にに知らせる @@ -6061,6 +6334,10 @@ chat item action グループプロフィールの保存 No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6240,6 +6517,10 @@ chat item action ライブメッセージを送信 (入力しながら宛先の画面で更新される) No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to No comment provided by engineer. @@ -6298,6 +6579,14 @@ chat item action Send receipts No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. ギャラリーまたはカスタム キーボードから送信します。 @@ -6307,6 +6596,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. 送信者がファイル転送をキャンセルしました。 @@ -6428,13 +6721,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. キューを作成するにはサーバーの認証が必要です。パスワードを確認してください server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. アップロードにはサーバーの認証が必要です。パスワードを確認してください server test error @@ -6500,6 +6793,10 @@ chat item action システム認証の代わりに設定します。 No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -6518,6 +6815,10 @@ chat item action 暗証フレーズを設定してからエクスポート No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! 新しいメンバーに表示されるメッセージを設定してください! @@ -6583,6 +6884,14 @@ chat item action リンクを送る No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile No comment provided by engineer. @@ -6600,6 +6909,18 @@ chat item action 連絡先と共有する No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -6696,6 +7017,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + alert title + SimpleX channel link simplex link type @@ -6737,6 +7062,10 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode シークレットモードの簡素化 @@ -6928,6 +7257,10 @@ report reason TCP connection No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout TCP接続タイムアウト @@ -6961,10 +7294,26 @@ report reason 写真を撮影 No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button ボタンをタップ @@ -7047,6 +7396,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. アプリは、メッセージや連絡先のリクエストを受信したときに通知することができます - 設定を開いて有効にしてください。 @@ -7103,6 +7456,10 @@ It can happen because of some bug or when the connection is compromised.以前のメッセージとハッシュ値が異なります。 No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. メッセージはすべてのメンバーに対して削除されます。 @@ -7142,7 +7499,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified 送信者には通知されません - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7223,14 +7580,6 @@ It can happen because of some bug or when the connection is compromised.このグループはもう存在しません。 No comment provided by engineer. - - This is your own SimpleX address! - No comment provided by engineer. - - - This is your own one-time link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7248,6 +7597,14 @@ It can happen because of some bug or when the connection is compromised.この設定は現在のチャットプロフィール **%@** のメッセージに適用されます。 No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title No comment provided by engineer. @@ -7322,11 +7679,19 @@ You will be prompted to complete authentication before this feature is enabled.< To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. インスタント プッシュ通知をサポートするには、チャット データベースを移行する必要があります。 No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -7543,11 +7908,35 @@ To connect, please ask your contact to create another connection link and check 設定を更新すると、全サーバにクライントの再接続が行われます。 No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat アップグレードしてチャットを開く No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors No comment provided by engineer. @@ -7607,7 +7996,7 @@ To connect, please ask your contact to create another connection link and check Use current profile 現在のプロファイルを使用する - No comment provided by engineer. + new chat action Use for files @@ -7631,10 +8020,14 @@ To connect, please ask your contact to create another connection link and check iOS通話インターフェースを使用する No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile 新しいシークレットプロファイルを使用する - No comment provided by engineer. + new chat action Use only local notifications? @@ -7657,10 +8050,6 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. No comment provided by engineer. @@ -7846,6 +8235,10 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new 新着情報 @@ -7954,11 +8347,11 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -7966,20 +8359,16 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. - No comment provided by engineer. - - - You are already joining the group via this link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8088,10 +8477,14 @@ Repeat join request? You can view invitation link again in connection details. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! メッセージを送信できませんでした! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8103,14 +8496,10 @@ Repeat join request? あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 No comment provided by engineer. - - You have already requested connection via this address! - No comment provided by engineer. - You have already requested connection! Repeat connection request? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8168,6 +8557,10 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! グループのホスト端末がオンラインになったら、接続されます。後でチェックするか、しばらくお待ちください! @@ -8192,10 +8585,6 @@ Repeat connection request? 起動時、または非アクティブ状態で30秒が経った後に戻ると、認証する必要となります。 No comment provided by engineer. - - You will connect to all group members. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. ミュートされたプロフィールがアクティブな場合でも、そのプロフィールからの通話や通知は引き続き受信します。 @@ -8235,6 +8624,10 @@ Repeat connection request? あなたのSimpleXアドレス No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls あなたの通話 @@ -8259,8 +8652,16 @@ Repeat connection request? あなたのチャットプロフィール No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact No comment provided by engineer. @@ -8292,6 +8693,10 @@ Repeat connection request? 現在のプロフィール No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences あなたの設定 @@ -8374,6 +8779,10 @@ Repeat connection request? 上で選んでください: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call 受けた通話 @@ -8383,6 +8792,10 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin 管理者 @@ -8402,6 +8815,10 @@ Repeat connection request? 暗号化に同意しています… chat item text + + all + member criteria value + all members feature role @@ -8479,6 +8896,10 @@ marked deleted chat item preview text 発信中… call status + + can't send messages + No comment provided by engineer. + cancelled %@ キャンセルされました %@ @@ -8529,10 +8950,6 @@ marked deleted chat item preview text 接続中 No comment provided by engineer. - - connected directly - rcv group event chat item - connecting 接続待ち @@ -8582,6 +8999,14 @@ marked deleted chat item preview text contact %1$@ changed to %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption 連絡先はエンドツーエンド暗号化があります @@ -8592,6 +9017,14 @@ marked deleted chat item preview text 連絡先はエンドツーエンド暗号化がありません No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator 作成者 @@ -8753,11 +9186,19 @@ pref value forwarded No comment provided by engineer. + + group + shown on group welcome message + group deleted グループ削除済み No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated グループのプロフィールが更新されました @@ -8851,11 +9292,6 @@ pref value 斜体 No comment provided by engineer. - - join as %@ - %@ として参加 - No comment provided by engineer. - left 脱退 @@ -8880,6 +9316,10 @@ pref value 接続中 rcv group event chat item + + member has old version + No comment provided by engineer. + message No comment provided by engineer. @@ -8943,6 +9383,10 @@ pref value テキストなし copied message info in history + + not synchronized + No comment provided by engineer. + observer オブザーバー @@ -8953,6 +9397,7 @@ pref value オフ enabled status group pref value +member criteria value time to disappear @@ -9000,6 +9445,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -9037,6 +9486,10 @@ time to disappear removed contact address profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture profile update event chat item @@ -9046,10 +9499,34 @@ time to disappear あなたを除名しました rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved No comment provided by engineer. @@ -9082,10 +9559,6 @@ time to disappear セキュリティコードが変更されました chat item text - - send direct message - No comment provided by engineer. - server queue info: %1$@ @@ -9220,10 +9693,9 @@ last received msg: %2$@ you No comment provided by engineer. - - you are invited to group - グループ招待が届きました - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index 019f63cbc0..ca51a875c7 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -2250,8 +2250,8 @@ We will be adding server redundancy to prevent lost messages. Please store passphrase securely, you will NOT be able to change it if you lose it. No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -2603,8 +2603,8 @@ We will be adding server redundancy to prevent lost messages. Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error @@ -3696,7 +3696,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -3873,8 +3873,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. @@ -4641,8 +4641,8 @@ This is your own one-time link! All new messages from %@ will be hidden! %@로부터의 모든 새 메세지가 숨겨집니다! - - Auto-accept settings + + SimpleX address settings 자동-수락 설정 diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index 0f795170c6..4b51d66a34 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -2047,8 +2047,8 @@ We will be adding server redundancy to prevent lost messages. Please store passphrase securely, you will NOT be able to change it if you lose it. No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -2375,8 +2375,8 @@ We will be adding server redundancy to prevent lost messages. Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error @@ -3439,7 +3439,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -3616,8 +3616,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 4008c57ac0..a13e4cd80d 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -394,8 +394,8 @@ - connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! - delivery receipts (up to 20 members). - faster and more stable. - - verbinding maken met [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! -- ontvangst bevestiging(tot 20 leden). + - verbinding maken met [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! +- ontvangst bevestiging(tot 20 leden). - sneller en stabieler. No comment provided by engineer. @@ -563,8 +563,19 @@ time interval Accepteer accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Accepteren als lid + alert action + + + Accept as observer + Accepteren als waarnemer + alert action + Accept conditions Accepteer voorwaarden @@ -575,6 +586,10 @@ swipe action Accepteer contact No comment provided by engineer. + + Accept contact request + alert title + Accept contact request from %@? Accepteer contactverzoek van %@? @@ -583,9 +598,14 @@ swipe action Accept incognito Accepteer incognito - accept contact request via notification + alert action swipe action + + Accept member + Lid accepteren + alert title + Accepted conditions Geaccepteerde voorwaarden @@ -626,6 +646,10 @@ swipe action Lijst toevoegen No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile Profiel toevoegen @@ -798,6 +822,7 @@ swipe action All servers + Alle servers No comment provided by engineer. @@ -840,6 +865,10 @@ swipe action Downgraden toestaan No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) @@ -925,6 +954,10 @@ swipe action Sta toe dat uw contacten verdwijnende berichten verzenden. No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. Sta toe dat uw contacten spraak berichten verzenden. @@ -938,12 +971,12 @@ swipe action Already connecting! Al bezig met verbinden! - No comment provided by engineer. + new chat sheet title Already joining the group! Al lid van de groep! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1150,11 +1183,6 @@ swipe action Afbeeldingen automatisch accepteren No comment provided by engineer. - - Auto-accept settings - Instellingen automatisch accepteren - alert title - Back Terug @@ -1230,6 +1258,14 @@ swipe action Betere gebruikerservaring No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black Zwart @@ -1280,6 +1316,10 @@ swipe action Vervaag media No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Zowel u als uw contact kunnen bericht reacties toevoegen. @@ -1300,6 +1340,10 @@ swipe action Zowel jij als je contact kunnen verdwijnende berichten sturen. No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. Zowel jij als je contact kunnen spraak berichten verzenden. @@ -1320,6 +1364,10 @@ swipe action Zakelijke chats No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses bedrijven @@ -1369,6 +1417,10 @@ swipe action Kan lid niet bellen No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Kan contact niet uitnodigen! @@ -1388,7 +1440,8 @@ swipe action Cancel Annuleren alert action -alert button +alert button +new chat action Cancel migration @@ -1494,7 +1547,7 @@ set passcode view Chat already exists! Chat bestaat al! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1581,11 +1634,30 @@ set passcode view De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt! No comment provided by engineer. + + Chat with admins + Chat met beheerders + chat toolbar + + + Chat with member + Chat met lid + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats Chats No comment provided by engineer. + + Chats with members + Chats met leden + No comment provided by engineer. + Check messages every 20 min. Controleer uw berichten elke 20 minuten. @@ -1811,9 +1883,8 @@ set passcode view Automatisch verbinden No comment provided by engineer. - - Connect incognito - Verbind incognito + + Connect faster! 🚀 No comment provided by engineer. @@ -1826,44 +1897,39 @@ set passcode view Maak sneller verbinding met je vrienden. No comment provided by engineer. - - Connect to yourself? - Verbinding maken met jezelf? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Verbinding maken met jezelf? Dit is uw eigen SimpleX adres! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Verbinding maken met jezelf? Dit is uw eigen eenmalige link! - No comment provided by engineer. + new chat sheet title Connect via contact address Verbinding maken via contactadres - No comment provided by engineer. + new chat sheet title Connect via link Maak verbinding via link - No comment provided by engineer. + new chat sheet title Connect via one-time link Verbinden via een eenmalige link? - No comment provided by engineer. + new chat sheet title Connect with %@ Verbonden met %@ - No comment provided by engineer. + new chat action Connected @@ -1928,7 +1994,7 @@ Dit is uw eigen eenmalige link! Connection error Verbindingsfout - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1975,7 +2041,7 @@ Dit is uw eigen eenmalige link! Connection timeout Timeout verbinding - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2027,6 +2093,10 @@ Dit is uw eigen eenmalige link! Contact voorkeuren No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Het contact wordt verwijderd. Dit kan niet ongedaan worden gemaakt! @@ -2142,9 +2212,8 @@ Dit is uw eigen eenmalige link! Maak een wachtrij server test step - - Create secret group - Maak een geheime groep aan + + Create your address No comment provided by engineer. @@ -2401,6 +2470,11 @@ swipe action Chatprofiel verwijderen? No comment provided by engineer. + + Delete chat with member? + Chat met lid verwijderen? + alert title + Delete chat? Chat verwijderen? @@ -2596,11 +2670,19 @@ swipe action Ontvangstbewijzen! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Beschrijving No comment provided by engineer. + + Description too large + alert title + Desktop address Desktop adres @@ -2824,7 +2906,7 @@ swipe action Don't show again Niet meer weergeven - No comment provided by engineer. + alert action Done @@ -2907,6 +2989,10 @@ chat item action Groep profiel bewerken No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable Inschakelen @@ -2942,6 +3028,10 @@ chat item action Schakel cameratoegang in No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Inschakelen voor iedereen @@ -3142,6 +3232,11 @@ chat item action Fout bij het accepteren van een contactverzoek No comment provided by engineer. + + Error accepting member + Fout bij het accepteren van lid + alert title + Error adding member(s) Fout bij het toevoegen van leden @@ -3152,11 +3247,19 @@ chat item action Fout bij toevoegen server alert title + + Error adding short link + No comment provided by engineer. + Error changing address Fout bij wijzigen van adres No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile Fout bij wijzigen van verbindingsprofiel @@ -3170,7 +3273,7 @@ chat item action Error changing setting Fout bij wijzigen van instelling - No comment provided by engineer. + alert title Error changing to incognito! @@ -3185,7 +3288,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw. - No comment provided by engineer. + alert message Error creating address @@ -3232,15 +3335,20 @@ chat item action Fout bij het ontsleutelen van bestand No comment provided by engineer. + + Error deleting chat + Fout bij het verwijderen van chat met lid + alert title + Error deleting chat database Fout bij het verwijderen van de chat database - No comment provided by engineer. + alert title Error deleting chat! Fout bij verwijderen gesprek! - No comment provided by engineer. + alert title Error deleting connection @@ -3250,12 +3358,12 @@ chat item action Error deleting database Fout bij het verwijderen van de database - No comment provided by engineer. + alert title Error deleting old database Fout bij het verwijderen van de oude database - No comment provided by engineer. + alert title Error deleting token @@ -3290,7 +3398,7 @@ chat item action Error exporting chat database Fout bij het exporteren van de chat database - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3300,7 +3408,7 @@ chat item action Error importing chat database Fout bij het importeren van de chat database - No comment provided by engineer. + alert title Error joining group @@ -3322,6 +3430,10 @@ chat item action Fout bij het openen van de chat No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file Fout bij ontvangen van bestand @@ -3342,10 +3454,14 @@ chat item action Fout bij registreren voor meldingen alert title + + Error rejecting contact request + alert title + Error removing member Fout bij verwijderen van lid - No comment provided by engineer. + alert title Error reordering lists @@ -3417,6 +3533,10 @@ chat item action Fout bij verzenden van bericht No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Fout bij het instellen van ontvangst bevestiging! @@ -3435,7 +3555,7 @@ chat item action Error switching profile Fout bij wisselen van profiel - No comment provided by engineer. + alert title Error switching profile! @@ -3499,6 +3619,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Fout: URL is ongeldig @@ -3678,6 +3802,10 @@ snd error text Bestanden en media chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Bestanden en media zijn niet toegestaan. @@ -3718,6 +3846,23 @@ snd error text Vind chats sneller No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Mogelijk is de certificaat vingerafdruk in het server adres onjuist + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Herstel @@ -3829,9 +3974,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - De doorstuurserver %@ kon geen verbinding maken met de bestemmingsserver %@. Probeer het later opnieuw. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + De doorstuurserver %1$@ kon geen verbinding maken met de bestemmingsserver %2$@. Probeer het later opnieuw. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3925,7 +4070,7 @@ Fout: %2$@ Group already exists! Groep bestaat al! - No comment provided by engineer. + new chat sheet title Group display name @@ -3992,6 +4137,10 @@ Fout: %2$@ Groep profiel wordt opgeslagen op de apparaten van de leden, niet op de servers. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Groep welkom bericht @@ -4374,7 +4523,7 @@ Binnenkort meer verbeteringen! Invalid link Ongeldige link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4487,37 +4636,32 @@ Binnenkort meer verbeteringen! Word lid swipe action + + Join as %@ + deelnemen als %@ + No comment provided by engineer. + Join group Word lid van groep - No comment provided by engineer. + new chat sheet title Join group conversations Neem deel aan groepsgesprekken No comment provided by engineer. - - Join group? - Deelnemen aan groep? - No comment provided by engineer. - Join incognito Doe incognito mee No comment provided by engineer. - - Join with current profile - Word lid met huidig profiel - No comment provided by engineer. - Join your group? This is your link for group %@! Sluit u aan bij uw groep? Dit is jouw link voor groep %@! - No comment provided by engineer. + new chat action Joining group @@ -4544,6 +4688,10 @@ Dit is jouw link voor groep %@! Ongebruikte uitnodiging bewaren? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections Behoud uw verbindingen @@ -4599,6 +4747,10 @@ Dit is jouw link voor groep %@! Groep verlaten? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Laten we praten in SimpleX Chat @@ -4654,6 +4806,10 @@ Dit is jouw link voor groep %@! Live berichten No comment provided by engineer. + + Loading profile… + in progress text + Local name Lokale naam @@ -4729,11 +4885,24 @@ Dit is jouw link voor groep %@! Lid No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + Toelating van leden + No comment provided by engineer. + Member inactive Lid inactief item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports Ledenrapporten @@ -4764,6 +4933,11 @@ Dit is jouw link voor groep %@! Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt! No comment provided by engineer. + + Member will join the group, accept member? + Lid zal toetreden tot de groep, lid accepteren? + alert message + Members can add message reactions. Groepsleden kunnen bericht reacties toevoegen. @@ -4839,6 +5013,10 @@ Dit is jouw link voor groep %@! Bericht doorgestuurd item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. Het bericht kan later worden bezorgd als het lid actief wordt. @@ -4914,6 +5092,10 @@ Dit is jouw link voor groep %@! Berichten No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! Berichten van %@ worden getoond! @@ -5169,6 +5351,10 @@ Dit is jouw link voor groep %@! Nieuwe gebeurtenissen notification + + New group role: Moderator + No comment provided by engineer. + New in %@ Nieuw in %@ @@ -5184,6 +5370,11 @@ Dit is jouw link voor groep %@! Nieuwe leden rol No comment provided by engineer. + + New member wants to join the group. + Nieuw lid wil zich bij de groep aansluiten. + rcv group event chat item + New message nieuw bericht @@ -5224,6 +5415,11 @@ Dit is jouw link voor groep %@! Geen chats in lijst %@ No comment provided by engineer. + + No chats with members + Geen chats met leden + No comment provided by engineer. + No contacts selected Geen contacten geselecteerd @@ -5304,6 +5500,10 @@ Dit is jouw link voor groep %@! Geen toestemming om spraakbericht op te nemen No comment provided by engineer. + + No private routing session + alert title + No push server Lokaal @@ -5398,7 +5598,7 @@ Dit is jouw link voor groep %@! Now admins can: - delete members' messages. - disable members ("observer" role) - Nu kunnen beheerders: + Nu kunnen beheerders: - berichten van leden verwijderen. - schakel leden uit ("waarnemer" rol) No comment provided by engineer. @@ -5416,7 +5616,9 @@ Dit is jouw link voor groep %@! Ok OK - alert button + alert action +alert button +new chat action Old database @@ -5507,6 +5709,10 @@ Vereist het inschakelen van VPN. Alleen jij kunt verdwijnende berichten verzenden. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Alleen jij kunt spraak berichten verzenden. @@ -5532,6 +5738,10 @@ Vereist het inschakelen van VPN. Alleen uw contact kan verdwijnende berichten verzenden. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Alleen uw contact kan spraak berichten verzenden. @@ -5555,25 +5765,34 @@ Vereist het inschakelen van VPN. Open chat Chat openen - No comment provided by engineer. + new chat action Open chat console Chat console openen authentication reason + + Open clean link + alert action + Open conditions Open voorwaarden No comment provided by engineer. + + Open full link + alert action + Open group Open groep - No comment provided by engineer. + new chat action Open link? + Link openen? alert title @@ -5581,6 +5800,30 @@ Vereist het inschakelen van VPN. Open de migratie naar een ander apparaat authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… App openen… @@ -5688,11 +5931,6 @@ Vereist het inschakelen van VPN. Wachtwoord om weer te geven No comment provided by engineer. - - Past member %@ - Voormalig lid %@ - past/unknown group member - Paste desktop address Desktopadres plakken @@ -5763,7 +6001,7 @@ Deel eventuele andere problemen met de ontwikkelaars. Please check your network connection with %@ and try again. Controleer uw netwerkverbinding met %@ en probeer het opnieuw. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5827,6 +6065,11 @@ Fout: %@ Probeer meldingen uit en weer in te schakelen. token info + + Please wait for group moderators to review your request to join the group. + Wacht totdat de moderators van de groep uw verzoek tot lidmaatschap van de groep hebben beoordeeld. + snd group event chat item + Please wait for token activation to complete. Wacht tot de tokenactivering voltooid is. @@ -5847,11 +6090,6 @@ Fout: %@ Poort No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Mogelijk is de certificaat vingerafdruk in het server adres onjuist - server test error - Preserve the last message draft, with attachments. Bewaar het laatste berichtconcept, met bijlagen. @@ -5935,7 +6173,11 @@ Fout: %@ Private routing error Fout in privéroutering - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -6039,6 +6281,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Bescherm je chatprofielen met een wachtwoord! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout Protocol timeout @@ -6267,7 +6513,8 @@ Schakel dit in in *Netwerk en servers*-instellingen. Reject Afwijzen - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6278,7 +6525,12 @@ swipe action Reject contact request Contactverzoek afwijzen - No comment provided by engineer. + alert title + + + Reject member? + Lid afwijzen? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6305,6 +6557,10 @@ swipe action Verwijder afbeelding No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Lid verwijderen @@ -6320,6 +6576,10 @@ swipe action Wachtwoord van de keychain verwijderen? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Opnieuw onderhandelen @@ -6335,11 +6595,6 @@ swipe action Heronderhandelen over versleuteling? No comment provided by engineer. - - Repeat connection request? - Verbindingsverzoek herhalen? - No comment provided by engineer. - Repeat download Herhaal het downloaden @@ -6350,11 +6605,6 @@ swipe action Herhaal import No comment provided by engineer. - - Repeat join request? - Deelnameverzoek herhalen? - No comment provided by engineer. - Repeat upload Herhaal het uploaden @@ -6390,6 +6640,11 @@ swipe action Reden melding? No comment provided by engineer. + + Report sent to moderators + Rapport verzonden naar moderators + alert title + Report spam: only group moderators will see it. Spam melden: alleen groepsmoderators kunnen het zien. @@ -6493,7 +6748,7 @@ swipe action Retry Opnieuw proberen - No comment provided by engineer. + alert action Reveal @@ -6505,6 +6760,20 @@ swipe action Voorwaarden bekijken No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + Leden beoordelen + admission stage + + + Review members before admitting ("knocking"). + Controleer de leden voordat u ze toelaat ('knocking'). + admission stage description + Revoke Intrekken @@ -6561,6 +6830,15 @@ chat item action Bewaar (en informeer contacten) alert button + + Save (and notify members) + alert button + + + Save admission settings? + Toegangsinstellingen opslaan? + alert title + Save and notify contact Opslaan en Contact melden @@ -6586,6 +6864,10 @@ chat item action Groep profiel opslaan No comment provided by engineer. + + Save group profile? + alert title + Save list Lijst opslaan @@ -6781,6 +7063,10 @@ chat item action Stuur een live bericht, het wordt bijgewerkt voor de ontvanger(s) terwijl u het typt No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to Stuur ontvangstbewijzen naar @@ -6846,6 +7132,14 @@ chat item action Ontvangstbewijzen verzenden No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Stuur ze vanuit de galerij of aangepaste toetsenborden. @@ -6856,6 +7150,10 @@ chat item action Stuur tot 100 laatste berichten naar nieuwe leden. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. Afzender heeft bestandsoverdracht geannuleerd. @@ -6996,13 +7294,13 @@ chat item action Serverprotocol gewijzigd. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Server vereist autorisatie om te uploaden, wachtwoord controleren server test error @@ -7076,6 +7374,11 @@ chat item action Stel het in in plaats van systeemverificatie. No comment provided by engineer. + + Set member admission + Toegang voor leden instellen + No comment provided by engineer. + Set message expiration in chats. Stel de berichtvervaldatum in chats in. @@ -7096,6 +7399,10 @@ chat item action Wachtwoord instellen om te exporteren No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Stel het getoonde bericht in voor nieuwe leden! @@ -7167,6 +7474,14 @@ chat item action Deel link No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile Profiel delen @@ -7187,8 +7502,21 @@ chat item action Delen met contacten No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link + Korte link No comment provided by engineer. @@ -7291,8 +7619,14 @@ chat item action SimpleX adres of eenmalige link? No comment provided by engineer. + + SimpleX address settings + Instellingen automatisch accepteren + alert title + SimpleX channel link + SimpleX channel link simplex link type @@ -7335,6 +7669,10 @@ chat item action SimpleX-protocollen beoordeeld door Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode Vereenvoudigde incognitomodus @@ -7548,6 +7886,10 @@ report reason TCP verbinding No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout Timeout van TCP-verbinding @@ -7583,11 +7925,27 @@ report reason Foto nemen No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Tik op SimpleX-adres maken in het menu om het later te maken. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button Tik op de knop @@ -7675,6 +8033,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen. @@ -7735,6 +8097,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De hash van het vorige bericht is anders. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. Het bericht wordt verwijderd voor alle leden. @@ -7778,7 +8144,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The sender will NOT be notified De afzender wordt NIET op de hoogte gebracht - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7870,18 +8236,9 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Deze groep bestaat niet meer. No comment provided by engineer. - - This is your own SimpleX address! - Dit is uw eigen SimpleX adres! - No comment provided by engineer. - - - This is your own one-time link! - Dit is uw eigen eenmalige link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Voor deze link is een nieuwere app-versie vereist. Werk de app bij of vraag je contactpersoon om een compatibele link te sturen. No comment provided by engineer. @@ -7899,6 +8256,14 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Deze instelling is van toepassing op berichten in je huidige chatprofiel **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title Titel @@ -7981,11 +8346,19 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Om te verzenden No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Om de servers van **%@** te gebruiken, moet u de gebruiksvoorwaarden accepteren. @@ -8185,6 +8558,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Unsupported connection link + Niet-ondersteunde verbindingslink No comment provided by engineer. @@ -8222,11 +8596,35 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Door de instellingen bij te werken, wordt de client opnieuw verbonden met alle servers. No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat Upgrade en open chat No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors Upload fouten @@ -8284,6 +8682,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Use TCP port 443 for preset servers only. + Gebruik TCP-poort 443 alleen voor vooraf ingestelde servers. No comment provided by engineer. @@ -8294,7 +8693,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Use current profile Gebruik het huidige profiel - No comment provided by engineer. + new chat action Use for files @@ -8321,10 +8720,14 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak De iOS-oproepinterface gebruiken No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile Gebruik een nieuw incognitoprofiel - No comment provided by engineer. + new chat action Use only local notifications? @@ -8351,10 +8754,6 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Gebruik de app tijdens het gesprek. @@ -8560,6 +8959,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Welkom bericht is te lang No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new Wat is er nieuw @@ -8683,12 +9086,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You are already connecting to %@. U maakt al verbinding met %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Je maakt al verbinding via deze eenmalige link! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8698,24 +9101,19 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You are already joining the group %@. Je bent al lid van de groep %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Je wordt al lid van de groep via deze link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Je wordt al lid van de groep via deze link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Je sluit je al aan bij de groep! Deelnameverzoek herhalen? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8832,10 +9230,15 @@ Deelnameverzoek herhalen? U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails. alert message + + You can view your reports in Chat with admins. + U kunt uw rapporten bekijken in Chat met beheerders. + alert message + You can't send messages! Je kunt geen berichten versturen! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8847,17 +9250,12 @@ Deelnameverzoek herhalen? Jij bepaalt wie er verbinding mag maken. No comment provided by engineer. - - You have already requested connection via this address! - U heeft al een verbinding aangevraagd via dit adres! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Je hebt al verbinding aangevraagd! Verbindingsverzoek herhalen? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8919,6 +9317,10 @@ Verbindingsverzoek herhalen? U zou meldingen moeten ontvangen. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Je wordt verbonden met de groep wanneer het apparaat van de groep host online is, even geduld a.u.b. of controleer het later! @@ -8944,11 +9346,6 @@ Verbindingsverzoek herhalen? U moet zich authenticeren wanneer u de app na 30 seconden op de achtergrond start of hervat. No comment provided by engineer. - - You will connect to all group members. - Je maakt verbinding met alle leden. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn. @@ -8989,6 +9386,10 @@ Verbindingsverzoek herhalen? Uw SimpleX adres No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls Uw oproepen @@ -9014,11 +9415,19 @@ Verbindingsverzoek herhalen? Uw chat profielen No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel. No comment provided by engineer. + + Your contact + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@). @@ -9049,6 +9458,10 @@ Verbindingsverzoek herhalen? Je huidige profiel No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences Jouw voorkeuren @@ -9134,6 +9547,11 @@ Verbindingsverzoek herhalen? hier boven, kies dan: No comment provided by engineer. + + accepted %@ + geaccepteerd %@ + rcv group event chat item + accepted call geaccepteerde oproep @@ -9144,6 +9562,11 @@ Verbindingsverzoek herhalen? geaccepteerde uitnodiging chat list item title + + accepted you + heb je geaccepteerd + rcv group event chat item + admin Beheerder @@ -9164,6 +9587,11 @@ Verbindingsverzoek herhalen? versleuteling overeenkomen… chat item text + + all + alle + member criteria value + all members alle leden @@ -9250,6 +9678,11 @@ marked deleted chat item preview text bellen… call status + + can't send messages + kan geen berichten versturen + No comment provided by engineer. + cancelled %@ geannuleerd %@ @@ -9300,11 +9733,6 @@ marked deleted chat item preview text verbonden No comment provided by engineer. - - connected directly - direct verbonden - rcv group event chat item - connecting Verbinden @@ -9355,6 +9783,16 @@ marked deleted chat item preview text contactpersoon %1$@ gewijzigd in %2$@ profile update event chat item + + contact deleted + contact verwijderd + No comment provided by engineer. + + + contact disabled + contact uitgeschakeld + No comment provided by engineer. + contact has e2e encryption contact heeft e2e-codering @@ -9365,6 +9803,15 @@ marked deleted chat item preview text contact heeft geen e2e versleuteling No comment provided by engineer. + + contact not ready + contact niet klaar + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator creator @@ -9531,11 +9978,20 @@ pref value doorgestuurd No comment provided by engineer. + + group + shown on group welcome message + group deleted groep verwijderd No comment provided by engineer. + + group is deleted + groep is verwijderd + No comment provided by engineer. + group profile updated groep profiel bijgewerkt @@ -9631,11 +10087,6 @@ pref value cursief No comment provided by engineer. - - join as %@ - deelnemen als %@ - No comment provided by engineer. - left is vertrokken @@ -9661,6 +10112,11 @@ pref value is toegetreden rcv group event chat item + + member has old version + lid heeft oude versie + No comment provided by engineer. + message bericht @@ -9726,6 +10182,11 @@ pref value geen tekst copied message info in history + + not synchronized + niet gesynchroniseerd + No comment provided by engineer. + observer Waarnemer @@ -9736,6 +10197,7 @@ pref value uit enabled status group pref value +member criteria value time to disappear @@ -9788,6 +10250,11 @@ time to disappear in afwachting van goedkeuring No comment provided by engineer. + + pending review + in afwachting van beoordeling + No comment provided by engineer. + quantum resistant e2e encryption quantum bestendige e2e-codering @@ -9828,6 +10295,11 @@ time to disappear contactadres verwijderd profile update event chat item + + removed from group + verwijderd uit de groep + No comment provided by engineer. + removed profile picture profielfoto verwijderd @@ -9838,11 +10310,38 @@ time to disappear heeft je verwijderd rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + verzoek tot toetreding afgewezen + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect verzocht om verbinding te maken chat list item title + + review + beoordeling + No comment provided by engineer. + + + reviewed by admins + beoordeeld door beheerders + No comment provided by engineer. + saved opgeslagen @@ -9878,11 +10377,6 @@ time to disappear beveiligingscode gewijzigd chat item text - - send direct message - stuur een direct bericht - No comment provided by engineer. - server queue info: %1$@ @@ -10032,10 +10526,10 @@ laatst ontvangen bericht: %2$@ jij No comment provided by engineer. - - you are invited to group - je bent uitgenodigd voor de groep - No comment provided by engineer. + + you accepted this member + je hebt dit lid geaccepteerd + snd group event chat item you are observer @@ -10175,6 +10669,7 @@ laatst ontvangen bericht: %2$@ From %d chat(s) + Van %d chat(s) notification body diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 175c8b4112..d08a7d86e5 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -563,8 +563,17 @@ time interval Akceptuj accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + alert action + + + Accept as observer + alert action + Accept conditions Zaakceptuj warunki @@ -575,6 +584,10 @@ swipe action Zaakceptować prośbę o połączenie? No comment provided by engineer. + + Accept contact request + alert title + Accept contact request from %@? Zaakceptuj prośbę o kontakt od %@? @@ -583,9 +596,13 @@ swipe action Accept incognito Akceptuj incognito - accept contact request via notification + alert action swipe action + + Accept member + alert title + Accepted conditions Zaakceptowano warunki @@ -626,6 +643,10 @@ swipe action Dodaj listę No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile Dodaj profil @@ -840,6 +861,10 @@ swipe action Zezwól na obniżenie wersji No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny) @@ -925,6 +950,10 @@ swipe action Zezwól swoim kontaktom na wysyłanie znikających wiadomości. No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. Zezwól swoim kontaktom na wysyłanie wiadomości głosowych. @@ -938,12 +967,12 @@ swipe action Already connecting! Już połączony! - No comment provided by engineer. + new chat sheet title Already joining the group! Już dołączono do grupy! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1150,11 +1179,6 @@ swipe action Automatyczne akceptowanie obrazów No comment provided by engineer. - - Auto-accept settings - Ustawienia automatycznej akceptacji - alert title - Back Wstecz @@ -1228,6 +1252,14 @@ swipe action Lepszy interfejs użytkownika No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black Czarny @@ -1278,6 +1310,10 @@ swipe action Rozmycie mediów No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Zarówno Ty, jak i Twój kontakt możecie dodawać reakcje wiadomości. @@ -1298,6 +1334,10 @@ swipe action Zarówno Ty, jak i Twój kontakt możecie wysyłać znikające wiadomości. No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. Zarówno Ty, jak i Twój kontakt możecie wysyłać wiadomości głosowe. @@ -1318,6 +1358,10 @@ swipe action Czaty biznesowe No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses Firmy @@ -1364,6 +1408,10 @@ swipe action Nie można zadzwonić do członka No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Nie można zaprosić kontaktu! @@ -1383,7 +1431,8 @@ swipe action Cancel Anuluj alert action -alert button +alert button +new chat action Cancel migration @@ -1488,7 +1537,7 @@ set passcode view Chat already exists! Czat już istnieje! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1575,11 +1624,27 @@ set passcode view Czat zostanie usunięty dla Ciebie – tej operacji nie można cofnąć! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats Czaty No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. Sprawdzaj wiadomości co 20 min. @@ -1797,9 +1862,8 @@ set passcode view Łącz automatycznie No comment provided by engineer. - - Connect incognito - Połącz incognito + + Connect faster! 🚀 No comment provided by engineer. @@ -1812,44 +1876,39 @@ set passcode view Szybciej łącz się ze znajomymi. No comment provided by engineer. - - Connect to yourself? - Połączyć się ze sobą? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Połączyć się ze sobą? To jest twój własny adres SimpleX! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Połączyć się ze sobą? To jest twój jednorazowy link! - No comment provided by engineer. + new chat sheet title Connect via contact address Połącz przez adres kontaktowy - No comment provided by engineer. + new chat sheet title Connect via link Połącz się przez link - No comment provided by engineer. + new chat sheet title Connect via one-time link Połącz przez jednorazowy link - No comment provided by engineer. + new chat sheet title Connect with %@ Połącz z %@ - No comment provided by engineer. + new chat action Connected @@ -1913,7 +1972,7 @@ To jest twój jednorazowy link! Connection error Błąd połączenia - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1955,7 +2014,7 @@ To jest twój jednorazowy link! Connection timeout Czas połączenia minął - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2007,6 +2066,10 @@ To jest twój jednorazowy link! Preferencje kontaktu No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Kontakt zostanie usunięty – nie można tego cofnąć! @@ -2119,9 +2182,8 @@ To jest twój jednorazowy link! Utwórz kolejkę server test step - - Create secret group - Utwórz tajną grupę + + Create your address No comment provided by engineer. @@ -2374,6 +2436,10 @@ swipe action Usunąć profil czatu? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? No comment provided by engineer. @@ -2564,11 +2630,19 @@ swipe action Potwierdzenia dostawy! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Opis No comment provided by engineer. + + Description too large + alert title + Desktop address Adres komputera @@ -2787,7 +2861,7 @@ swipe action Don't show again Nie pokazuj ponownie - No comment provided by engineer. + alert action Done @@ -2868,6 +2942,10 @@ chat item action Edytuj profil grupy No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable Włącz @@ -2902,6 +2980,10 @@ chat item action Włącz dostęp do kamery No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all Włącz dla wszystkich @@ -3100,6 +3182,10 @@ chat item action Błąd przyjmowania prośby o kontakt No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) Błąd dodawania członka(ów) @@ -3109,11 +3195,19 @@ chat item action Error adding server alert title + + Error adding short link + No comment provided by engineer. + Error changing address Błąd zmiany adresu No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile Błąd zmiany połączenia profilu @@ -3127,7 +3221,7 @@ chat item action Error changing setting Błąd zmiany ustawienia - No comment provided by engineer. + alert title Error changing to incognito! @@ -3141,7 +3235,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później. - No comment provided by engineer. + alert message Error creating address @@ -3186,15 +3280,19 @@ chat item action Błąd odszyfrowania pliku No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database Błąd usuwania bazy danych czatu - No comment provided by engineer. + alert title Error deleting chat! Błąd usuwania czatu! - No comment provided by engineer. + alert title Error deleting connection @@ -3204,12 +3302,12 @@ chat item action Error deleting database Błąd usuwania bazy danych - No comment provided by engineer. + alert title Error deleting old database Błąd usuwania starej bazy danych - No comment provided by engineer. + alert title Error deleting token @@ -3244,7 +3342,7 @@ chat item action Error exporting chat database Błąd eksportu bazy danych czatu - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3254,7 +3352,7 @@ chat item action Error importing chat database Błąd importu bazy danych czatu - No comment provided by engineer. + alert title Error joining group @@ -3275,6 +3373,10 @@ chat item action Błąd otwierania czatu No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file Błąd odbioru pliku @@ -3294,10 +3396,14 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + alert title + Error removing member Błąd usuwania członka - No comment provided by engineer. + alert title Error reordering lists @@ -3366,6 +3472,10 @@ chat item action Błąd wysyłania wiadomości No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Błąd ustawiania potwierdzeń dostawy! @@ -3384,7 +3494,7 @@ chat item action Error switching profile Błąd zmiany profilu - No comment provided by engineer. + alert title Error switching profile! @@ -3446,6 +3556,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Błąd: URL jest nieprawidłowy @@ -3618,6 +3732,10 @@ snd error text Pliki i media chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Pliki i media są zabronione w tej grupie. @@ -3658,6 +3776,23 @@ snd error text Szybciej znajduj czaty No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Napraw @@ -3762,9 +3897,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Serwer przekazujący %1$@ nie mógł połączyć się z serwerem docelowym %2$@. Spróbuj ponownie później. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3857,7 +3992,7 @@ Błąd: %2$@ Group already exists! Grupa już istnieje! - No comment provided by engineer. + new chat sheet title Group display name @@ -3924,6 +4059,10 @@ Błąd: %2$@ Profil grupy jest przechowywany na urządzeniach członków, a nie na serwerach. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message Wiadomość powitalna grupy @@ -4292,7 +4431,7 @@ More improvements are coming soon! Invalid link Nieprawidłowy link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4404,37 +4543,32 @@ More improvements are coming soon! Dołącz swipe action + + Join as %@ + dołącz jako %@ + No comment provided by engineer. + Join group Dołącz do grupy - No comment provided by engineer. + new chat sheet title Join group conversations Dołącz do grupowej rozmowy No comment provided by engineer. - - Join group? - Dołączyć do grupy? - No comment provided by engineer. - Join incognito Dołącz incognito No comment provided by engineer. - - Join with current profile - Dołącz z obecnym profilem - No comment provided by engineer. - Join your group? This is your link for group %@! Dołączyć do twojej grupy? To jest twój link do grupy %@! - No comment provided by engineer. + new chat action Joining group @@ -4461,6 +4595,10 @@ To jest twój link do grupy %@! Zachować nieużyte zaproszenie? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections Zachowaj swoje połączenia @@ -4514,6 +4652,10 @@ To jest twój link do grupy %@! Opuścić grupę? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat Porozmawiajmy w SimpleX Chat @@ -4566,6 +4708,10 @@ To jest twój link do grupy %@! Wiadomości na żywo No comment provided by engineer. + + Loading profile… + in progress text + Local name Nazwa lokalna @@ -4641,11 +4787,23 @@ To jest twój link do grupy %@! Członek No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive Członek nieaktywny item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4673,6 +4831,10 @@ To jest twój link do grupy %@! Członek zostanie usunięty z grupy - nie można tego cofnąć! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. Członkowie grupy mogą dodawać reakcje wiadomości. @@ -4746,6 +4908,10 @@ To jest twój link do grupy %@! Wiadomość przekazana item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. Wiadomość może zostać dostarczona później jeśli członek stanie się aktywny. @@ -4821,6 +4987,10 @@ To jest twój link do grupy %@! Wiadomości i pliki No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! Wiadomości od %@ zostaną pokazane! @@ -5068,6 +5238,10 @@ To jest twój link do grupy %@! New events notification + + New group role: Moderator + No comment provided by engineer. + New in %@ Nowość w %@ @@ -5083,6 +5257,10 @@ To jest twój link do grupy %@! Nowa rola członka No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message Nowa wiadomość @@ -5119,6 +5297,10 @@ To jest twój link do grupy %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected Nie wybrano kontaktów @@ -5196,6 +5378,10 @@ To jest twój link do grupy %@! Brak uprawnień do nagrywania wiadomości głosowej No comment provided by engineer. + + No private routing session + alert title + No push server Lokalnie @@ -5298,7 +5484,9 @@ To jest twój link do grupy %@! Ok Ok - alert button + alert action +alert button +new chat action Old database @@ -5386,6 +5574,10 @@ Wymaga włączenia VPN. Tylko Ty możesz wysyłać znikające wiadomości. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Tylko Ty możesz wysyłać wiadomości głosowe. @@ -5411,6 +5603,10 @@ Wymaga włączenia VPN. Tylko Twój kontakt może wysyłać znikające wiadomości. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Tylko Twój kontakt może wysyłać wiadomości głosowe. @@ -5433,21 +5629,29 @@ Wymaga włączenia VPN. Open chat Otwórz czat - No comment provided by engineer. + new chat action Open chat console Otwórz konsolę czatu authentication reason + + Open clean link + alert action + Open conditions No comment provided by engineer. + + Open full link + alert action + Open group Grupa otwarta - No comment provided by engineer. + new chat action Open link? @@ -5458,6 +5662,30 @@ Wymaga włączenia VPN. Otwórz migrację na innym urządzeniu authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… Otwieranie aplikacji… @@ -5560,11 +5788,6 @@ Wymaga włączenia VPN. Hasło do wyświetlenia No comment provided by engineer. - - Past member %@ - Były członek %@ - past/unknown group member - Paste desktop address Wklej adres komputera @@ -5635,7 +5858,7 @@ Proszę podzielić się innymi problemami z deweloperami. Please check your network connection with %@ and try again. Sprawdzić połączenie sieciowe z %@ i spróbować ponownie. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5698,6 +5921,10 @@ Błąd: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5716,11 +5943,6 @@ Błąd: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy - server test error - Preserve the last message draft, with attachments. Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami. @@ -5799,7 +6021,11 @@ Błąd: %@ Private routing error Błąd prywatnego trasowania - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5902,6 +6128,10 @@ Włącz w ustawianiach *Sieć i serwery* . Chroń swoje profile czatu hasłem! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout Limit czasu protokołu @@ -6127,7 +6357,8 @@ Włącz w ustawianiach *Sieć i serwery* . Reject Odrzuć - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6138,7 +6369,11 @@ swipe action Reject contact request Odrzuć prośbę kontaktu - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6165,6 +6400,10 @@ swipe action Usuń obraz No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Usuń członka @@ -6180,6 +6419,10 @@ swipe action Usunąć hasło z pęku kluczy? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate Renegocjuj @@ -6195,11 +6438,6 @@ swipe action Renegocjować szyfrowanie? No comment provided by engineer. - - Repeat connection request? - Powtórzyć prośbę połączenia? - No comment provided by engineer. - Repeat download Powtórz pobieranie @@ -6210,11 +6448,6 @@ swipe action Powtórz importowanie No comment provided by engineer. - - Repeat join request? - Powtórzyć prośbę dołączenia? - No comment provided by engineer. - Repeat upload Powtórz wgrywanie @@ -6245,6 +6478,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -6343,7 +6580,7 @@ swipe action Retry Ponów - No comment provided by engineer. + alert action Reveal @@ -6354,6 +6591,18 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke Odwołaj @@ -6410,6 +6659,14 @@ chat item action Zapisz (i powiadom kontakty) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact Zapisz i powiadom kontakt @@ -6435,6 +6692,10 @@ chat item action Zapisz profil grupy No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6629,6 +6890,10 @@ chat item action Wysyłaj wiadomości na żywo - będą one aktualizowane dla odbiorcy(ów) w trakcie ich wpisywania No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to Wyślij potwierdzenia dostawy do @@ -6693,6 +6958,14 @@ chat item action Wyślij potwierdzenia No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. Wyślij je z galerii lub niestandardowych klawiatur. @@ -6703,6 +6976,10 @@ chat item action Wysyłaj do 100 ostatnich wiadomości do nowych członków. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. Nadawca anulował transfer pliku. @@ -6839,13 +7116,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Serwer wymaga autoryzacji do przesłania, sprawdź hasło server test error @@ -6918,6 +7195,10 @@ chat item action Ustaw go zamiast uwierzytelniania systemowego. No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -6937,6 +7218,10 @@ chat item action Ustaw hasło do eksportu No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! Ustaw wiadomość wyświetlaną nowym członkom! @@ -7005,6 +7290,14 @@ chat item action Udostępnij link No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile Udostępnij profil @@ -7025,6 +7318,18 @@ chat item action Udostępnij kontaktom No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -7126,6 +7431,11 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + Ustawienia automatycznej akceptacji + alert title + SimpleX channel link simplex link type @@ -7169,6 +7479,10 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode Uproszczony tryb incognito @@ -7376,6 +7690,10 @@ report reason Połączenie TCP No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout Limit czasu połączenia TCP @@ -7410,10 +7728,26 @@ report reason Zrób zdjęcie No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button Naciśnij przycisk @@ -7500,6 +7834,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć. @@ -7558,6 +7896,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Hash poprzedniej wiadomości jest inny. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. Wiadomość zostanie usunięta dla wszystkich członków. @@ -7599,7 +7941,7 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom The sender will NOT be notified Nadawca NIE zostanie powiadomiony - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7688,16 +8030,6 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Ta grupa już nie istnieje. No comment provided by engineer. - - This is your own SimpleX address! - To jest twój własny adres SimpleX! - No comment provided by engineer. - - - This is your own one-time link! - To jest twój jednorazowy link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7716,6 +8048,14 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom To ustawienie dotyczy wiadomości Twojego bieżącego profilu czatu **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title Tytuł @@ -7795,11 +8135,19 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. Aby obsługiwać natychmiastowe powiadomienia push, należy zmigrować bazę danych czatu. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -8032,11 +8380,35 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Aktualizacja ustawień spowoduje ponowne połączenie klienta ze wszystkimi serwerami. No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat Zaktualizuj i otwórz czat No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors Błędy przesłania @@ -8102,7 +8474,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use current profile Użyj obecnego profilu - No comment provided by engineer. + new chat action Use for files @@ -8127,10 +8499,14 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj interfejsu połączeń iOS No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile Użyj nowego profilu incognito - No comment provided by engineer. + new chat action Use only local notifications? @@ -8156,10 +8532,6 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Używaj aplikacji podczas połączenia. @@ -8362,6 +8734,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wiadomość powitalna jest zbyt długa No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new Co nowego @@ -8483,12 +8859,12 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc You are already connecting to %@. Już się łączysz z %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Już jesteś połączony z tym jednorazowym linkiem! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8498,24 +8874,19 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc You are already joining the group %@. Już dołączasz do grupy %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Już dołączasz do grupy przez ten link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Już dołączasz do grupy przez ten link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Już dołączasz do grupy! Powtórzyć prośbę dołączenia? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8630,10 +9001,14 @@ Powtórzyć prośbę dołączenia? Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! Nie możesz wysyłać wiadomości! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8645,17 +9020,12 @@ Powtórzyć prośbę dołączenia? Ty decydujesz, kto może się połączyć. No comment provided by engineer. - - You have already requested connection via this address! - Już prosiłeś o połączenie na ten adres! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Już prosiłeś o połączenie! Powtórzyć prośbę połączenia? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8716,6 +9086,10 @@ Powtórzyć prośbę połączenia? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Zostaniesz połączony do grupy, gdy urządzenie gospodarza grupy będzie online, proszę czekać lub sprawdzić później! @@ -8741,11 +9115,6 @@ Powtórzyć prośbę połączenia? Uwierzytelnienie będzie wymagane przy uruchamianiu lub wznawianiu aplikacji po 30 sekundach w tle. No comment provided by engineer. - - You will connect to all group members. - Zostaniesz połączony ze wszystkimi członkami grupy. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Nadal będziesz otrzymywać połączenia i powiadomienia z wyciszonych profili, gdy są one aktywne. @@ -8785,6 +9154,10 @@ Powtórzyć prośbę połączenia? Twój adres SimpleX No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls Twoje połączenia @@ -8810,11 +9183,19 @@ Powtórzyć prośbę połączenia? Twoje profile czatu No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd. No comment provided by engineer. + + Your contact + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@). @@ -8845,6 +9226,10 @@ Powtórzyć prośbę połączenia? Twój obecny profil No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences Twoje preferencje @@ -8930,6 +9315,10 @@ Powtórzyć prośbę połączenia? powyżej, a następnie wybierz: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call zaakceptowane połączenie @@ -8939,6 +9328,10 @@ Powtórzyć prośbę połączenia? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin administrator @@ -8959,6 +9352,10 @@ Powtórzyć prośbę połączenia? uzgadnianie szyfrowania… chat item text + + all + member criteria value + all members wszyscy członkowie @@ -9044,6 +9441,10 @@ marked deleted chat item preview text dzwonie… call status + + can't send messages + No comment provided by engineer. + cancelled %@ anulowany %@ @@ -9094,11 +9495,6 @@ marked deleted chat item preview text połączony No comment provided by engineer. - - connected directly - połącz bezpośrednio - rcv group event chat item - connecting łączenie @@ -9149,6 +9545,14 @@ marked deleted chat item preview text kontakt %1$@ zmieniony na %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption kontakt posiada szyfrowanie e2e @@ -9159,6 +9563,14 @@ marked deleted chat item preview text kontakt nie posiada szyfrowania e2e No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator twórca @@ -9325,11 +9737,19 @@ pref value przekazane dalej No comment provided by engineer. + + group + shown on group welcome message + group deleted grupa usunięta No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated zaktualizowano profil grupy @@ -9425,11 +9845,6 @@ pref value kursywa No comment provided by engineer. - - join as %@ - dołącz jako %@ - No comment provided by engineer. - left opuścił @@ -9455,6 +9870,10 @@ pref value połączony rcv group event chat item + + member has old version + No comment provided by engineer. + message wiadomość @@ -9519,6 +9938,10 @@ pref value brak tekstu copied message info in history + + not synchronized + No comment provided by engineer. + observer obserwator @@ -9529,6 +9952,7 @@ pref value wyłączony enabled status group pref value +member criteria value time to disappear @@ -9579,6 +10003,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption kwantowo odporne szyfrowanie e2e @@ -9618,6 +10046,10 @@ time to disappear usunięto adres kontaktu profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture usunięto zdjęcie profilu @@ -9628,10 +10060,34 @@ time to disappear usunął cię rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved zapisane @@ -9667,11 +10123,6 @@ time to disappear kod bezpieczeństwa zmieniony chat item text - - send direct message - wyślij wiadomość bezpośrednią - No comment provided by engineer. - server queue info: %1$@ @@ -9821,10 +10272,9 @@ ostatnia otrzymana wiadomość: %2$@ Ty No comment provided by engineer. - - you are invited to group - jesteś zaproszony do grupy - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index bbb6c7d22a..d9af0624bf 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -2,7 +2,7 @@
- +
@@ -2352,8 +2352,8 @@ We will be adding server redundancy to prevent lost messages. Guarde a senha em um local seguro, você NÃO poderá alterá-la se a perder. No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. Possivelmente, a impressão digital do certificado no endereço do servidor está incorreta server test error @@ -2727,8 +2727,8 @@ We will be adding server redundancy to prevent lost messages. Mensagens enviadas serão excluídas depois do tempo definido. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. O servidor requer autorização para criar filas, verifique a senha server test error @@ -3934,7 +3934,7 @@ SimpleX servers cannot see your profile. itálico No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -4130,8 +4130,8 @@ SimpleX servers cannot see your profile. sim pref value - - you are invited to group + + You are invited to group você está convidado para o grupo No comment provided by engineer. @@ -4610,8 +4610,8 @@ Disponível na 5.1 Salvar e atualizar perfil do grupo No comment provided by engineer. - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. O servidor requer autorização para fazer upload, verifique a senha server test error @@ -5565,8 +5565,8 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Chat migrated! Conversa migrada! - - Auto-accept settings + + SimpleX address settings Aceitar automaticamente configurações @@ -5645,11 +5645,99 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Chat profile Perfil da conversa + + %d seconds(s) + %d segundo(s) + + + **Scan / Paste link**: to connect via a link you received. + **Escanear / Colar link**: para conectar através de um link que você recebeu. + + + 1 year + 1 ano + + + About operators + Sobre operadores + + + Accept as member + Aceitar como membro + + + Accept as observer + Aceitar como observador + + + Accept conditions + Aceitar condições + + + Accept contact request + Aceitar solicitação de contato + + + Accept member + Aceitar membro + + + Accepted conditions + Condições aceitas + + + Active + Ativo + + + Add friends + Adicionar amigos + + + Add list + Adicionar lista + + + Add message + Adicionar mensagem + + + Add team members + Adicionar membros da equipe + + + Add to list + Adicionar à lista + + + Add your team members to the conversations. + Adicione membros da sua equipe às conversas. + + + Added media & file servers + Servidores de mídia e arquivos adicionados + + + Added message servers + Servidores de mensagem adicionados + + + Address or 1-time link? + Endereço ou link de uso único? + + + Address settings + Configurações de endereço + + + All + Todos +
- +
@@ -5677,7 +5765,7 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi
- +
diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index bc8bf79da1..e4fac55bcb 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -39,7 +39,7 @@ Available in v5.1 (can be copied) - .(pode ser copiado) + (pode ser copiado) No comment provided by engineer. @@ -2453,8 +2453,8 @@ Available in v5.1 Polish interface No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. server test error @@ -2829,12 +2829,12 @@ Available in v5.1 Sent messages will be deleted after set time. No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. server test error @@ -4043,7 +4043,7 @@ SimpleX servers cannot see your profile. italic No comment provided by engineer. - + join as %@ No comment provided by engineer. @@ -4220,8 +4220,8 @@ SimpleX servers cannot see your profile. yes pref value - - you are invited to group + + You are invited to group No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 419fa75375..568ca53946 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -167,7 +167,7 @@ %d hours - %d ч. + %d час. time interval @@ -563,8 +563,19 @@ time interval Принять accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Принять в группу + alert action + + + Accept as observer + Принять как читателя + alert action + Accept conditions Принять условия @@ -575,6 +586,11 @@ swipe action Принять запрос? No comment provided by engineer. + + Accept contact request + Принять запрос на соединение + alert title + Accept contact request from %@? Принять запрос на соединение от %@? @@ -583,9 +599,14 @@ swipe action Accept incognito Принять инкогнито - accept contact request via notification + alert action swipe action + + Accept member + Принять члена + alert title + Accepted conditions Принятые условия @@ -626,6 +647,11 @@ swipe action Добавить список No comment provided by engineer. + + Add message + Добавить cообщение + placeholder for sending contact request + Add profile Добавить профиль @@ -798,6 +824,7 @@ swipe action All servers + Все серверы No comment provided by engineer. @@ -840,6 +867,11 @@ swipe action Разрешить прямую доставку No comment provided by engineer. + + Allow files and media only if your contact allows them. + Разрешить файлы и медиа, только если их разрешает Ваш контакт. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа) @@ -925,6 +957,11 @@ swipe action Разрешить Вашим контактам отправлять исчезающие сообщения. No comment provided by engineer. + + Allow your contacts to send files and media. + Разрешить Вашим контактам отправлять файлы и медиа. + No comment provided by engineer. + Allow your contacts to send voice messages. Разрешить Вашим контактам отправлять голосовые сообщения. @@ -938,12 +975,12 @@ swipe action Already connecting! Уже соединяется! - No comment provided by engineer. + new chat sheet title Already joining the group! Вступление в группу уже начато! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1150,11 +1187,6 @@ swipe action Автоприем изображений No comment provided by engineer. - - Auto-accept settings - Настройки автоприема - alert title - Back Назад @@ -1230,6 +1262,16 @@ swipe action Улучшенный интерфейс No comment provided by engineer. + + Bio + О себе + No comment provided by engineer. + + + Bio too large + Описание слишком длинное + alert title + Black Черная @@ -1280,6 +1322,11 @@ swipe action Размытие изображений No comment provided by engineer. + + Bot + Бот + No comment provided by engineer. + Both you and your contact can add message reactions. И Вы, и Ваш контакт можете добавлять реакции на сообщения. @@ -1300,6 +1347,11 @@ swipe action Вы и Ваш контакт можете отправлять исчезающие сообщения. No comment provided by engineer. + + Both you and your contact can send files and media. + Вы и Ваш контакт можете отправлять файлы и медиа. + No comment provided by engineer. + Both you and your contact can send voice messages. Вы и Ваш контакт можете отправлять голосовые сообщения. @@ -1320,6 +1372,11 @@ swipe action Бизнес разговоры No comment provided by engineer. + + Business connection + Бизнес контакт + No comment provided by engineer. + Businesses Бизнесы @@ -1369,6 +1426,11 @@ swipe action Не удаётся позвонить члену группы No comment provided by engineer. + + Can't change profile + Нельзя поменять профиль + alert title + Can't invite contact! Нельзя пригласить контакт! @@ -1388,7 +1450,8 @@ swipe action Cancel Отменить alert action -alert button +alert button +new chat action Cancel migration @@ -1494,7 +1557,7 @@ set passcode view Chat already exists! Разговор уже существует! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1581,11 +1644,31 @@ set passcode view Разговор будет удален для Вас - это действие нельзя отменить! No comment provided by engineer. + + Chat with admins + Чат с админами + chat toolbar + + + Chat with member + Чат с членом группы + No comment provided by engineer. + + + Chat with members before they join. + Общайтесь с членами до того как принять их. + No comment provided by engineer. + Chats Чаты No comment provided by engineer. + + Chats with members + Чаты с членами группы + No comment provided by engineer. + Check messages every 20 min. Проверять сообщения каждые 20 минут. @@ -1811,9 +1894,9 @@ set passcode view Соединяться автоматически No comment provided by engineer. - - Connect incognito - Соединиться Инкогнито + + Connect faster! 🚀 + Соединяйтесь быстрее! 🚀 No comment provided by engineer. @@ -1826,44 +1909,39 @@ set passcode view Соединяйтесь с друзьями быстрее. No comment provided by engineer. - - Connect to yourself? - Соединиться с самим собой? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Соединиться с самим собой? Это ваш собственный адрес SimpleX! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Соединиться с самим собой? Это ваша собственная одноразовая ссылка! - No comment provided by engineer. + new chat sheet title Connect via contact address Соединиться через адрес - No comment provided by engineer. + new chat sheet title Connect via link Соединиться через ссылку - No comment provided by engineer. + new chat sheet title Connect via one-time link Соединиться через одноразовую ссылку - No comment provided by engineer. + new chat sheet title Connect with %@ Соединиться с %@ - No comment provided by engineer. + new chat action Connected @@ -1928,7 +2006,7 @@ This is your own one-time link! Connection error Ошибка соединения - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1975,7 +2053,7 @@ This is your own one-time link! Connection timeout Превышено время соединения - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2027,6 +2105,11 @@ This is your own one-time link! Предпочтения контакта No comment provided by engineer. + + Contact requests from groups + Запросы на соединение из групп + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Контакт будет удален — это нельзя отменить! @@ -2142,9 +2225,9 @@ This is your own one-time link! Создание очереди server test step - - Create secret group - Создать скрытую группу + + Create your address + Создайте Ваш адрес No comment provided by engineer. @@ -2401,6 +2484,11 @@ swipe action Удалить профиль? No comment provided by engineer. + + Delete chat with member? + Удалить чат с членом группы? + alert title + Delete chat? Удалить разговор? @@ -2596,11 +2684,21 @@ swipe action Отчёты о доставке! No comment provided by engineer. + + Deprecated options + Удалённые настройки + No comment provided by engineer. + Description Описание No comment provided by engineer. + + Description too large + Описание слишком длинное + alert title + Desktop address Адрес компьютера @@ -2688,6 +2786,7 @@ swipe action Direct messages between members are prohibited. + Прямые сообщения между членами запрещены. No comment provided by engineer. @@ -2792,6 +2891,7 @@ swipe action Do not send history to new members. + Не отправлять историю новым членам. No comment provided by engineer. @@ -2822,7 +2922,7 @@ swipe action Don't show again Не показывать - No comment provided by engineer. + alert action Done @@ -2905,6 +3005,11 @@ chat item action Редактировать профиль группы No comment provided by engineer. + + Empty message! + Пустое сообщение! + No comment provided by engineer. + Enable Включить @@ -2917,6 +3022,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных. No comment provided by engineer. @@ -2939,6 +3045,11 @@ chat item action Включить доступ к камере No comment provided by engineer. + + Enable disappearing messages by default. + Включите исчезающие сообщения по умолчанию. + No comment provided by engineer. + Enable for all Включить для всех @@ -3139,8 +3250,14 @@ chat item action Ошибка при принятии запроса на соединение No comment provided by engineer. + + Error accepting member + Ошибка вступления члена группы + alert title + Error adding member(s) + Ошибка при добавлении членов группы No comment provided by engineer. @@ -3148,11 +3265,21 @@ chat item action Ошибка добавления сервера alert title + + Error adding short link + Ошибка создания короткой ссылки + No comment provided by engineer. + Error changing address Ошибка при изменении адреса No comment provided by engineer. + + Error changing chat profile + Ошибка изменения профиля + alert title + Error changing connection profile Ошибка при изменении профиля соединения @@ -3166,7 +3293,7 @@ chat item action Error changing setting Ошибка при изменении настройки - No comment provided by engineer. + alert title Error changing to incognito! @@ -3181,7 +3308,7 @@ chat item action Error connecting to forwarding server %@. Please try later. Ошибка подключения к пересылающему серверу %@. Попробуйте позже. - No comment provided by engineer. + alert message Error creating address @@ -3205,6 +3332,7 @@ chat item action Error creating member contact + Ошибка при создании контакта No comment provided by engineer. @@ -3227,15 +3355,20 @@ chat item action Ошибка расшифровки файла No comment provided by engineer. + + Error deleting chat + Ошибка при удалении чата с членом группы + alert title + Error deleting chat database Ошибка при удалении данных чата - No comment provided by engineer. + alert title Error deleting chat! Ошибка при удалении чата! - No comment provided by engineer. + alert title Error deleting connection @@ -3245,12 +3378,12 @@ chat item action Error deleting database Ошибка при удалении данных чата - No comment provided by engineer. + alert title Error deleting old database Ошибка при удалении предыдущей версии данных чата - No comment provided by engineer. + alert title Error deleting token @@ -3285,7 +3418,7 @@ chat item action Error exporting chat database Ошибка при экспорте архива чата - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3295,7 +3428,7 @@ chat item action Error importing chat database Ошибка при импорте архива чата - No comment provided by engineer. + alert title Error joining group @@ -3314,7 +3447,12 @@ chat item action Error opening chat - Ошибка доступа к чату + Ошибка при открытии чата + No comment provided by engineer. + + + Error opening group + Ошибка при открытии группы No comment provided by engineer. @@ -3337,9 +3475,15 @@ chat item action Ошибка регистрации для уведомлений alert title + + Error rejecting contact request + Ошибка отклонения запроса + alert title + Error removing member - No comment provided by engineer. + Ошибка при удалении члена группы + alert title Error reordering lists @@ -3403,6 +3547,7 @@ chat item action Error sending member contact invitation + Ошибка при отправке приглашения члену No comment provided by engineer. @@ -3410,6 +3555,11 @@ chat item action Ошибка при отправке сообщения No comment provided by engineer. + + Error setting auto-accept + Ошибка при установке автоприёма запросов + No comment provided by engineer. + Error setting delivery receipts! Ошибка настроек отчётов о доставке! @@ -3428,7 +3578,7 @@ chat item action Error switching profile Ошибка переключения профиля - No comment provided by engineer. + alert title Error switching profile! @@ -3492,6 +3642,11 @@ chat item action file error text snd error text + + Error: %@. + Ошибка: %@. + server test error + Error: URL is invalid Ошибка: неверная ссылка @@ -3671,6 +3826,11 @@ snd error text Файлы и медиа chat feature + + Files and media are prohibited in this chat. + Файлы и медиа запрещены в этом чате. + No comment provided by engineer. + Files and media are prohibited. Файлы и медиа запрещены в этой группе. @@ -3711,6 +3871,26 @@ snd error text Быстро найти чаты No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + Хэш в адресе сервера назначения не соответствует сертификату: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + Хэш в адресе пересылающего сервера не соответствует сертификату: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Возможно, хэш сертификата в адресе сервера неверный + server test error + + + Fingerprint in server address does not match certificate: %@. + Хэш в адресе сервера не соответствует сертификату: %@. + No comment provided by engineer. + Fix Починить @@ -3738,6 +3918,7 @@ snd error text Fix not supported by group member + Починка не поддерживается членом группы. No comment provided by engineer. @@ -3821,9 +4002,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Пересылающий сервер %@ не смог подключиться к серверу назначения %@. Попробуйте позже. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Пересылающий сервер %1$@ не смог подключиться к серверу назначения %2$@. Попробуйте позже. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3871,6 +4052,7 @@ Error: %2$@ Fully decentralized – visible only to members. + Группа полностью децентрализована – она видна только членам. No comment provided by engineer. @@ -3916,7 +4098,7 @@ Error: %2$@ Group already exists! Группа уже существует! - No comment provided by engineer. + new chat sheet title Group display name @@ -3980,8 +4162,14 @@ Error: %2$@ Group profile is stored on members' devices, not on the servers. + Профиль группы хранится на устройствах членов, а не на серверах. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Профиль группы изменен. Если Вы сохраните его, новый профиль будет отправлен членам группы. + alert message + Group welcome message Приветственное сообщение группы @@ -3989,6 +4177,7 @@ Error: %2$@ Group will be deleted for all members - this cannot be undone! + Группа будет удалена для всех членов - это действие нельзя отменить! No comment provided by engineer. @@ -4053,6 +4242,7 @@ Error: %2$@ History is not sent to new members. + История не отправляется новым членам. No comment provided by engineer. @@ -4361,7 +4551,7 @@ More improvements are coming soon! Invalid link Ошибка ссылки - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4400,6 +4590,7 @@ More improvements are coming soon! Invite members + Пригласить членов группы No comment provided by engineer. @@ -4473,37 +4664,32 @@ More improvements are coming soon! Вступить swipe action + + Join as %@ + вступить как %@ + No comment provided by engineer. + Join group Вступить в группу - No comment provided by engineer. + new chat sheet title Join group conversations Присоединяйтесь к разговорам в группах No comment provided by engineer. - - Join group? - Вступить в группу? - No comment provided by engineer. - Join incognito Вступить инкогнито No comment provided by engineer. - - Join with current profile - Вступить с активным профилем - No comment provided by engineer. - Join your group? This is your link for group %@! Вступить в вашу группу? Это ваша ссылка на группу %@! - No comment provided by engineer. + new chat action Joining group @@ -4530,6 +4716,11 @@ This is your link for group %@! Оставить неиспользованное приглашение? alert title + + Keep your chats clean + Очищайте Ваши чаты + No comment provided by engineer. + Keep your connections Сохраните Ваши соединения @@ -4585,6 +4776,11 @@ This is your link for group %@! Выйти из группы? No comment provided by engineer. + + Less traffic on mobile networks. + Меньше трафик в мобильных сетях. + No comment provided by engineer. + Let's talk in SimpleX Chat Давайте поговорим в SimpleX Chat @@ -4640,6 +4836,11 @@ This is your link for group %@! "Живые" сообщения No comment provided by engineer. + + Loading profile… + Загрузка профиля… + in progress text + Local name Локальное имя @@ -4712,12 +4913,29 @@ This is your link for group %@! Member + Член группы + No comment provided by engineer. + + + Member %@ + Член группы %@ + past/unknown group member + + + Member admission + Приём членов в группу No comment provided by engineer. Member inactive + Член неактивен item status text + + Member is deleted - can't accept request + Член группы удалён - невозможно принять запрос + No comment provided by engineer. + Member reports Сообщения о нарушениях @@ -4730,54 +4948,72 @@ This is your link for group %@! Member role will be changed to "%@". All group members will be notified. + Роль члена будет изменена на "%@". Все члены группы получат уведомление. No comment provided by engineer. Member role will be changed to "%@". The member will receive a new invitation. + Роль члена будет изменена на "%@". Будет отправлено новое приглашение. No comment provided by engineer. Member will be removed from chat - this cannot be undone! + Член будет удален из разговора - это действие нельзя отменить! No comment provided by engineer. Member will be removed from group - this cannot be undone! + Член группы будет удален - это действие нельзя отменить! No comment provided by engineer. + + Member will join the group, accept member? + Участник хочет присоединиться к группе. Принять? + alert message + Members can add message reactions. + Члены могут добавлять реакции на сообщения. No comment provided by engineer. Members can irreversibly delete sent messages. (24 hours) + Члены могут необратимо удалять отправленные сообщения. (24 часа) No comment provided by engineer. Members can report messsages to moderators. + Члены группы могут пожаловаться модераторам. No comment provided by engineer. Members can send SimpleX links. + Члены могут отправлять ссылки SimpleX. No comment provided by engineer. Members can send direct messages. + Члены могут посылать прямые сообщения. No comment provided by engineer. Members can send disappearing messages. + Члены могут посылать исчезающие сообщения. No comment provided by engineer. Members can send files and media. + Члены могут слать файлы и медиа. No comment provided by engineer. Members can send voice messages. + Члены могут отправлять голосовые сообщения. No comment provided by engineer. Mention members 👋 + Упоминайте участников 👋 No comment provided by engineer. @@ -4810,8 +5046,14 @@ This is your link for group %@! Сообщение переслано item status text + + Message instantly once you tap Connect. + Отправляйте сообщения сразу после соединения. + No comment provided by engineer. + Message may be delivered later if member becomes active. + Сообщение может быть доставлено позже, если член группы станет активным. item status description @@ -4884,6 +5126,11 @@ This is your link for group %@! Сообщения No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Сообщения защищены **end-to-end шифрованием**. + No comment provided by engineer. + Messages from %@ will be shown! Сообщения от %@ будут показаны! @@ -5139,6 +5386,11 @@ This is your link for group %@! Новые события notification + + New group role: Moderator + Новая роль в группах: Модератор + No comment provided by engineer. + New in %@ Новое в %@ @@ -5151,8 +5403,14 @@ This is your link for group %@! New member role + Роль члена группы No comment provided by engineer. + + New member wants to join the group. + Новый участник хочет присоединиться к группе. + rcv group event chat item + New message Новое сообщение @@ -5193,6 +5451,11 @@ This is your link for group %@! Нет чатов в списке %@ No comment provided by engineer. + + No chats with members + Нет чатов с членами группы + No comment provided by engineer. + No contacts selected Контакты не выбраны @@ -5273,6 +5536,11 @@ This is your link for group %@! Нет разрешения для записи голосового сообщения No comment provided by engineer. + + No private routing session + Нет сессии конфиденциальной доставки + alert title + No push server Без сервера нотификаций @@ -5367,6 +5635,9 @@ This is your link for group %@! Now admins can: - delete members' messages. - disable members ("observer" role) + Теперь админы могут: +- удалять сообщения членов. +- приостанавливать членов (роль наблюдатель) No comment provided by engineer. @@ -5382,7 +5653,9 @@ This is your link for group %@! Ok Ок - alert button + alert action +alert button +new chat action Old database @@ -5473,6 +5746,11 @@ Requires compatible VPN. Только Вы можете отправлять исчезающие сообщения. No comment provided by engineer. + + Only you can send files and media. + Только Вы можете отправлять файлы и медиа. + No comment provided by engineer. + Only you can send voice messages. Только Вы можете отправлять голосовые сообщения. @@ -5498,6 +5776,11 @@ Requires compatible VPN. Только Ваш контакт может отправлять исчезающие сообщения. No comment provided by engineer. + + Only your contact can send files and media. + Только Ваш контакт может отправлять файлы и медиа. + No comment provided by engineer. + Only your contact can send voice messages. Только Ваш контакт может отправлять голосовые сообщения. @@ -5521,25 +5804,36 @@ Requires compatible VPN. Open chat Открыть чат - No comment provided by engineer. + new chat action Open chat console Открыть консоль authentication reason + + Open clean link + Открыть очищенную ссылку + alert action + Open conditions Открыть условия No comment provided by engineer. + + Open full link + Открыть полную ссылку + alert action + Open group Открыть группу - No comment provided by engineer. + new chat action Open link? + Открыть ссылку? alert title @@ -5547,6 +5841,36 @@ Requires compatible VPN. Открытие миграции на другое устройство authentication reason + + Open new chat + Открыть новый чат + new chat action + + + Open new group + Открыть новую группу + new chat action + + + Open to accept + Откройте чтобы принять + No comment provided by engineer. + + + Open to connect + Откройте чтобы соединиться + No comment provided by engineer. + + + Open to join + Откройте чтобы вступить + No comment provided by engineer. + + + Open to use bot + Откройте чтобы использовать бот + No comment provided by engineer. + Opening app… Приложение отрывается… @@ -5654,10 +5978,6 @@ Requires compatible VPN. Пароль чтобы раскрыть No comment provided by engineer. - - Past member %@ - past/unknown group member - Paste desktop address Вставить адрес компьютера @@ -5728,7 +6048,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. Пожалуйста, проверьте Ваше соединение с %@ и попробуйте еще раз. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5792,6 +6112,11 @@ Error: %@ Попробуйте выключить и снова включить уведомления. token info + + Please wait for group moderators to review your request to join the group. + Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление. + snd group event chat item + Please wait for token activation to complete. Пожалуйста, дождитесь завершения активации токена. @@ -5812,11 +6137,6 @@ Error: %@ Порт No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Возможно, хэш сертификата в адресе сервера неверный - server test error - Preserve the last message draft, with attachments. Сохранить последний черновик, вместе с вложениями. @@ -5900,7 +6220,12 @@ Error: %@ Private routing error Ошибка конфиденциальной доставки - No comment provided by engineer. + alert title + + + Private routing timeout + Таймаут конфиденциальной доставки + alert title Profile and server connections @@ -5964,6 +6289,7 @@ Error: %@ Prohibit sending direct messages to members. + Запретить посылать прямые сообщения членам группы. No comment provided by engineer. @@ -6003,6 +6329,11 @@ Enable in *Network & servers* settings. Защитите Ваши профили чата паролем! No comment provided by engineer. + + Protocol background timeout + Фоновый таймаут протокола + No comment provided by engineer. + Protocol timeout Таймаут протокола @@ -6231,7 +6562,8 @@ Enable in *Network & servers* settings. Reject Отклонить - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6242,7 +6574,12 @@ swipe action Reject contact request Отклонить запрос - No comment provided by engineer. + alert title + + + Reject member? + Отклонить участника? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6269,12 +6606,19 @@ swipe action Удалить изображение No comment provided by engineer. + + Remove link tracking + Удалять параметры отслеживания + No comment provided by engineer. + Remove member + Удалить члена группы No comment provided by engineer. Remove member? + Удалить члена группы? No comment provided by engineer. @@ -6282,6 +6626,11 @@ swipe action Удалить пароль из Keychain? No comment provided by engineer. + + Removes messages and blocks members. + Может удалять сообщения и блокировать членов. + No comment provided by engineer. + Renegotiate Пересогласовать @@ -6297,11 +6646,6 @@ swipe action Пересогласовать шифрование? No comment provided by engineer. - - Repeat connection request? - Повторить запрос на соединение? - No comment provided by engineer. - Repeat download Повторить загрузку @@ -6312,11 +6656,6 @@ swipe action Повторить импорт No comment provided by engineer. - - Repeat join request? - Повторить запрос на вступление? - No comment provided by engineer. - Repeat upload Повторить загрузку @@ -6352,6 +6691,11 @@ swipe action Причина сообщения? No comment provided by engineer. + + Report sent to moderators + Жалоба отправлена модераторам + alert title + Report spam: only group moderators will see it. Пожаловаться на спам: увидят только модераторы группы. @@ -6455,7 +6799,7 @@ swipe action Retry Повторить - No comment provided by engineer. + alert action Reveal @@ -6467,6 +6811,21 @@ swipe action Посмотреть условия No comment provided by engineer. + + Review group members + Одобрять членов группы + No comment provided by engineer. + + + Review members + Одобрять членов + admission stage + + + Review members before admitting ("knocking"). + Одобрять членов для вступления в группу. + admission stage description + Revoke Отозвать @@ -6523,6 +6882,16 @@ chat item action Сохранить (и уведомить контакты) alert button + + Save (and notify members) + Сохранить (и уведомить членов) + alert button + + + Save admission settings? + Сохранить настройки вступления? + alert title + Save and notify contact Сохранить и уведомить контакт @@ -6530,6 +6899,7 @@ chat item action Save and notify group members + Сохранить и уведомить членов группы No comment provided by engineer. @@ -6547,6 +6917,11 @@ chat item action Сохранить профиль группы No comment provided by engineer. + + Save group profile? + Сохранить профиль группы? + alert title + Save list Сохранить список @@ -6742,6 +7117,11 @@ chat item action Отправить живое сообщение — оно будет обновляться для получателей по мере того, как Вы его вводите No comment provided by engineer. + + Send contact request? + Отправить запрос на соединение? + No comment provided by engineer. + Send delivery receipts to Отправка отчётов о доставке @@ -6804,7 +7184,17 @@ chat item action Send receipts - Отправлять отчёты о доставке + Отчёты о доставке + No comment provided by engineer. + + + Send request + Отправить запрос + No comment provided by engineer. + + + Send request without message + Отправить запрос без сообщения No comment provided by engineer. @@ -6814,6 +7204,12 @@ chat item action Send up to 100 last messages to new members. + Отправить до 100 последних сообщений новым членам. + No comment provided by engineer. + + + Send your private feedback to groups. + Отправляйте Ваши конфиденциальные предложения группе. No comment provided by engineer. @@ -6956,13 +7352,13 @@ chat item action Протокол сервера изменен. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Сервер требует авторизации для создания очередей, проверьте пароль server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Сервер требует авторизации для загрузки, проверьте пароль server test error @@ -7036,6 +7432,11 @@ chat item action Установите код вместо системной аутентификации. No comment provided by engineer. + + Set member admission + Приём членов в группу + No comment provided by engineer. + Set message expiration in chats. Установите срок хранения сообщений в чатах. @@ -7056,8 +7457,14 @@ chat item action Установите пароль No comment provided by engineer. + + Set profile bio and welcome message. + Добавьте описание и приветственное сообщение. + No comment provided by engineer. + Set the message shown to new members! + Установить сообщение для новых членов группы! No comment provided by engineer. @@ -7126,6 +7533,16 @@ chat item action Поделиться ссылкой No comment provided by engineer. + + Share old address + Поделиться старым адресом + alert button + + + Share old link + Поделиться старой ссылкой + alert button + Share profile Поделиться профилем @@ -7146,8 +7563,24 @@ chat item action Поделиться с контактами No comment provided by engineer. + + Share your address + Поделитесь Вашим адресом + No comment provided by engineer. + + + Short SimpleX address + Короткий адрес SimpleX + No comment provided by engineer. + + + Short description + Цель + No comment provided by engineer. + Short link + Короткая ссылка No comment provided by engineer. @@ -7250,8 +7683,14 @@ chat item action Адрес SimpleX или одноразовая ссылка? No comment provided by engineer. + + SimpleX address settings + Настройки автоприема + alert title + SimpleX channel link + SimpleX ссылка канала simplex link type @@ -7294,6 +7733,11 @@ chat item action Аудит SimpleX протоколов от Trail of Bits. No comment provided by engineer. + + SimpleX relay link + Ссылка SimpleX relay + simplex link type + Simplified incognito mode Упрощенный режим Инкогнито @@ -7507,6 +7951,11 @@ report reason TCP-соединение No comment provided by engineer. + + TCP connection bg timeout + Фоновый таймаут TCP-соединения + No comment provided by engineer. + TCP connection timeout Таймаут TCP соединения @@ -7542,11 +7991,31 @@ report reason Сделать фото No comment provided by engineer. + + Tap Connect to chat + Нажмите Соединиться + No comment provided by engineer. + + + Tap Connect to send request + Нажмите Соединиться, чтобы отправить запрос + No comment provided by engineer. + + + Tap Connect to use bot + Нажмите Соединиться, чтобы использовать бот. + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. No comment provided by engineer. + + Tap Join group + Нажмите Вступить в группу + No comment provided by engineer. + Tap button Нажмите кнопку @@ -7634,6 +8103,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + Адрес будет коротким, и Ваш профиль будет добавлен в адрес. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках. @@ -7694,20 +8168,29 @@ It can happen because of some bug or when the connection is compromised.Хэш предыдущего сообщения отличается. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + Ссылка будет короткой, и профиль группы будет добавлен в ссылку. + alert message + The message will be deleted for all members. + Сообщение будет удалено для всех членов группы. No comment provided by engineer. The message will be marked as moderated for all members. + Сообщение будет помечено как удаленное для всех членов группы. No comment provided by engineer. The messages will be deleted for all members. + Сообщения будут удалены для всех членов группы. No comment provided by engineer. The messages will be marked as moderated for all members. + Сообщения будут помечены как удаленные для всех членов группы. No comment provided by engineer. @@ -7733,7 +8216,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified Отправитель не будет уведомлён - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7817,6 +8300,7 @@ It can happen because of some bug or when the connection is compromised. This group has over %lld members, delivery receipts are not sent. + В этой группе более %lld членов, отчёты о доставке не отправляются. No comment provided by engineer. @@ -7824,18 +8308,9 @@ It can happen because of some bug or when the connection is compromised.Эта группа больше не существует. No comment provided by engineer. - - This is your own SimpleX address! - Это ваш собственный адрес SimpleX! - No comment provided by engineer. - - - This is your own one-time link! - Это ваша собственная одноразовая ссылка! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку. No comment provided by engineer. @@ -7853,6 +8328,16 @@ It can happen because of some bug or when the connection is compromised.Эта настройка применяется к сообщениям в Вашем текущем профиле чата **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + Эта настройка применяется к Вашему текущему профилю чата **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Время удаления устанавливается только для новых контактов. + No comment provided by engineer. + Title Заголовок @@ -7935,11 +8420,21 @@ You will be prompted to complete authentication before this feature is enabled.< Для оправки No comment provided by engineer. + + To send commands you must be connected. + Вы должны быть соединены, чтобы отправлять команды. + alert message + To support instant push notifications the chat database has to be migrated. Для поддержки мгновенный доставки уведомлений данные чата должны быть перемещены. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Чтобы использовать другой профиль после попытки соединения, удалите чат и используйте ссылку снова. + alert message + To use the servers of **%@**, accept conditions of use. Чтобы использовать серверы оператора **%@**, примите условия использования. @@ -8027,14 +8522,17 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member + Разблокировать члена группы No comment provided by engineer. Unblock member for all? + Разблокировать члена для всех? No comment provided by engineer. Unblock member? + Разблокировать члена группы? No comment provided by engineer. @@ -8136,10 +8634,12 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link + Ссылка не поддерживается No comment provided by engineer. Up to 100 last messages are sent to new members. + До 100 последних сообщений отправляются новым членам. No comment provided by engineer. @@ -8172,11 +8672,41 @@ To connect, please ask your contact to create another connection link and check Обновление настроек приведет к сбросу и установке нового соединения со всеми серверами. No comment provided by engineer. + + Upgrade + Обновить + alert button + + + Upgrade address + Обновить адрес + No comment provided by engineer. + + + Upgrade address? + Обновить адрес? + alert message + Upgrade and open chat Обновить и открыть чат No comment provided by engineer. + + Upgrade group link? + Обновить ссылку группы? + alert message + + + Upgrade link + Обновить ссылку + No comment provided by engineer. + + + Upgrade your address + Обновите Ваш адрес + No comment provided by engineer. + Upload errors Ошибки загрузки @@ -8234,6 +8764,7 @@ To connect, please ask your contact to create another connection link and check Use TCP port 443 for preset servers only. + Использовать TCP-порт 443 только для серверов по умолчанию. No comment provided by engineer. @@ -8244,7 +8775,7 @@ To connect, please ask your contact to create another connection link and check Use current profile Использовать активный профиль - No comment provided by engineer. + new chat action Use for files @@ -8271,10 +8802,15 @@ To connect, please ask your contact to create another connection link and check Использовать интерфейс iOS для звонков No comment provided by engineer. + + Use incognito profile + Использовать профиль инкогнито + No comment provided by engineer. + Use new incognito profile Использовать новый Инкогнито профиль - No comment provided by engineer. + new chat action Use only local notifications? @@ -8301,10 +8837,6 @@ To connect, please ask your contact to create another connection link and check Использовать серверы No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Используйте приложение во время звонка. @@ -8510,6 +9042,11 @@ To connect, please ask your contact to create another connection link and check Приветственное сообщение слишком длинное No comment provided by engineer. + + Welcome your contacts 👋 + Приветствуйте Ваши контакты 👋 + No comment provided by engineer. + What's new Новые функции @@ -8633,12 +9170,12 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. Вы уже соединяетесь с %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Вы уже соединяетесь по этой одноразовой ссылке! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8648,24 +9185,19 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. Вы уже вступаете в группу %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Вы уже вступаете в группу по этой ссылке! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Вы уже вступаете в группу по этой ссылке. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Вы уже вступаете в группу! Повторить запрос на вступление? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8749,6 +9281,7 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. + Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. No comment provided by engineer. @@ -8781,10 +9314,15 @@ Repeat join request? Вы можете увидеть ссылку-приглашение снова открыв соединение. alert message + + You can view your reports in Chat with admins. + Вы можете найти Ваши жалобы в Чате с админами. + alert message + You can't send messages! Вы не можете отправлять сообщения! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8796,17 +9334,12 @@ Repeat join request? Вы определяете, кто может соединиться. No comment provided by engineer. - - You have already requested connection via this address! - Вы уже запросили соединение через этот адрес! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Вы уже запросили соединение! Повторить запрос? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8825,6 +9358,7 @@ Repeat connection request? You joined this group. Connecting to inviting group member. + Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы. No comment provided by engineer. @@ -8867,6 +9401,11 @@ Repeat connection request? Вы должны получать уведомления. token info + + You will be able to send messages **only after your request is accepted**. + Вы сможете отправлять сообщения **только после того как Ваш запрос будет принят**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже! @@ -8892,10 +9431,6 @@ Repeat connection request? Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме. No comment provided by engineer. - - You will connect to all group members. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Вы все равно получите звонки и уведомления в профилях без звука, когда они активные. @@ -8936,6 +9471,11 @@ Repeat connection request? Ваш адрес SimpleX No comment provided by engineer. + + Your business contact + Ваш бизнес контакт + No comment provided by engineer. + Your calls Ваши звонки @@ -8961,11 +9501,21 @@ Repeat connection request? Ваши профили чата No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Соединение было перемещено в профиль %@, но при переключении профиля произошла ошибка. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Соединение было перемещено на %@, но при смене профиля произошла неожиданная ошибка. No comment provided by engineer. + + Your contact + Ваш контакт + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ваш контакт отправил файл, размер которого превышает максимальный размер (%@). @@ -8996,6 +9546,11 @@ Repeat connection request? Ваш активный профиль No comment provided by engineer. + + Your group + Ваша группа + No comment provided by engineer. + Your preferences Ваши предпочтения @@ -9018,7 +9573,7 @@ Repeat connection request? Your profile is stored on your device and only shared with your contacts. - Ваш профиль храниться на Вашем устройстве и отправляется только контактам. + Ваш профиль хранится на Вашем устройстве и отправляется только контактам. No comment provided by engineer. @@ -9081,6 +9636,11 @@ Repeat connection request? наверху, затем выберите: No comment provided by engineer. + + accepted %@ + принят %@ + rcv group event chat item + accepted call принятый звонок @@ -9091,6 +9651,11 @@ Repeat connection request? принятое приглашение chat list item title + + accepted you + Вы приняты + rcv group event chat item + admin админ @@ -9111,8 +9676,14 @@ Repeat connection request? шифрование согласовывается… chat item text + + all + все + member criteria value + all members + все члены feature role @@ -9196,6 +9767,11 @@ marked deleted chat item preview text входящий звонок… call status + + can't send messages + нельзя отправлять + No comment provided by engineer. + cancelled %@ отменил(a) %@ @@ -9246,11 +9822,6 @@ marked deleted chat item preview text соединение установлено No comment provided by engineer. - - connected directly - соединен(а) напрямую - rcv group event chat item - connecting соединяется @@ -9301,6 +9872,16 @@ marked deleted chat item preview text контакт %1$@ изменён на %2$@ profile update event chat item + + contact deleted + контакт удален + No comment provided by engineer. + + + contact disabled + контакт выключен + No comment provided by engineer. + contact has e2e encryption у контакта есть e2e шифрование @@ -9311,6 +9892,16 @@ marked deleted chat item preview text у контакта нет e2e шифрования No comment provided by engineer. + + contact not ready + контакт не готов + No comment provided by engineer. + + + contact should accept… + контакт должен принять… + No comment provided by engineer. + creator создатель @@ -9477,11 +10068,21 @@ pref value переслано No comment provided by engineer. + + group + группа + shown on group welcome message + group deleted группа удалена No comment provided by engineer. + + group is deleted + группа удалена + No comment provided by engineer. + group profile updated профиль группы обновлен @@ -9577,11 +10178,6 @@ pref value курсив No comment provided by engineer. - - join as %@ - вступить как %@ - No comment provided by engineer. - left покинул(а) группу @@ -9594,10 +10190,12 @@ pref value member + член группы member role member %1$@ changed to %2$@ + член %1$@ изменился на %2$@ profile update event chat item @@ -9605,6 +10203,11 @@ pref value соединен(а) rcv group event chat item + + member has old version + член имеет старую версию + No comment provided by engineer. + message написать @@ -9670,6 +10273,11 @@ pref value нет текста copied message info in history + + not synchronized + не синхронизирован + No comment provided by engineer. + observer читатель @@ -9680,6 +10288,7 @@ pref value нет enabled status group pref value +member criteria value time to disappear @@ -9732,6 +10341,11 @@ time to disappear ожидает утверждения No comment provided by engineer. + + pending review + ожидает одобрения + No comment provided by engineer. + quantum resistant e2e encryption квантово-устойчивое e2e шифрование @@ -9772,6 +10386,11 @@ time to disappear удалён адрес контакта profile update event chat item + + removed from group + удален из группы + No comment provided by engineer. + removed profile picture удалена картинка профиля @@ -9782,11 +10401,41 @@ time to disappear удалил(а) Вас из группы rcv group event chat item + + request is sent + запрос отправлен + No comment provided by engineer. + + + request to join rejected + запрос на вступление отклонён + No comment provided by engineer. + + + requested connection + запрос на соединение + rcv group event chat item + + + requested connection from group %@ + запрос на соединение из группы %@ + rcv direct event chat item + requested to connect запрошено соединение chat list item title + + review + рассмотрение + No comment provided by engineer. + + + reviewed by admins + одобрен админами + No comment provided by engineer. + saved сохранено @@ -9822,11 +10471,6 @@ time to disappear код безопасности изменился chat item text - - send direct message - отправьте сообщение - No comment provided by engineer. - server queue info: %1$@ @@ -9976,10 +10620,10 @@ last received msg: %2$@ Вы No comment provided by engineer. - - you are invited to group - Вы приглашены в группу - No comment provided by engineer. + + you accepted this member + Вы приняли этого члена + snd group event chat item you are observer @@ -10119,6 +10763,7 @@ last received msg: %2$@ From %d chat(s) + Из %d чатов notification body diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 671dd87d7d..a0ab509388 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -518,8 +518,17 @@ time interval รับ accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + alert action + + + Accept as observer + alert action + Accept conditions No comment provided by engineer. @@ -528,6 +537,10 @@ swipe action Accept connection request? No comment provided by engineer. + + Accept contact request + alert title + Accept contact request from %@? รับการขอติดต่อจาก %@? @@ -536,9 +549,13 @@ swipe action Accept incognito ยอมรับโหมดไม่ระบุตัวตน - accept contact request via notification + alert action swipe action + + Accept member + alert title + Accepted conditions No comment provided by engineer. @@ -572,6 +589,10 @@ swipe action Add list No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile เพิ่มโปรไฟล์ @@ -763,6 +784,10 @@ swipe action Allow downgrade No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) อนุญาตให้ลบข้อความแบบถาวรเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตให้คุณเท่านั้น @@ -845,6 +870,10 @@ swipe action อนุญาตให้ผู้ติดต่อของคุณส่งข้อความที่จะหายไปหลังปิดแชท (disappearing messages) No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. อนุญาตให้ผู้ติดต่อของคุณส่งข้อความเสียง @@ -857,11 +886,11 @@ swipe action Already connecting! - No comment provided by engineer. + new chat sheet title Already joining the group! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1050,10 +1079,6 @@ swipe action ยอมรับภาพอัตโนมัติ No comment provided by engineer. - - Auto-accept settings - alert title - Back กลับ @@ -1118,6 +1143,14 @@ swipe action Better user experience No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black No comment provided by engineer. @@ -1158,6 +1191,10 @@ swipe action Blur media No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. ทั้งคุณและผู้ติดต่อของคุณสามารถเพิ่มปฏิกิริยาของข้อความได้ @@ -1178,6 +1215,10 @@ swipe action ทั้งคุณและผู้ติดต่อของคุณสามารถส่งข้อความที่หายไปได้ No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. ทั้งคุณและผู้ติดต่อของคุณสามารถส่งข้อความเสียงได้ @@ -1195,6 +1236,10 @@ swipe action Business chats No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses No comment provided by engineer. @@ -1236,6 +1281,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! ไม่สามารถเชิญผู้ติดต่อได้! @@ -1254,7 +1303,8 @@ swipe action Cancel ยกเลิก alert action -alert button +alert button +new chat action Cancel migration @@ -1351,7 +1401,7 @@ set passcode view Chat already exists! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1429,11 +1479,27 @@ set passcode view Chat will be deleted for you - this cannot be undone! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats แชท No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -1631,8 +1697,8 @@ set passcode view Connect automatically No comment provided by engineer. - - Connect incognito + + Connect faster! 🚀 No comment provided by engineer. @@ -1643,36 +1709,32 @@ set passcode view Connect to your friends faster. No comment provided by engineer. - - Connect to yourself? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! - No comment provided by engineer. + new chat sheet title Connect via contact address - No comment provided by engineer. + new chat sheet title Connect via link เชื่อมต่อผ่านลิงก์ - No comment provided by engineer. + new chat sheet title Connect via one-time link - No comment provided by engineer. + new chat sheet title Connect with %@ - No comment provided by engineer. + new chat action Connected @@ -1728,7 +1790,7 @@ This is your own one-time link! Connection error การเชื่อมต่อผิดพลาด - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1768,7 +1830,7 @@ This is your own one-time link! Connection timeout หมดเวลาการเชื่อมต่อ - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1816,6 +1878,10 @@ This is your own one-time link! การกําหนดลักษณะการติดต่อ No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! No comment provided by engineer. @@ -1919,9 +1985,8 @@ This is your own one-time link! สร้างคิว server test step - - Create secret group - สร้างกลุ่มลับ + + Create your address No comment provided by engineer. @@ -2162,6 +2227,10 @@ swipe action ลบโปรไฟล์แชทไหม? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? No comment provided by engineer. @@ -2345,11 +2414,19 @@ swipe action ใบตอบรับการจัดส่ง! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description คำอธิบาย No comment provided by engineer. + + Description too large + alert title + Desktop address No comment provided by engineer. @@ -2551,7 +2628,7 @@ swipe action Don't show again ไม่ต้องแสดงอีก - No comment provided by engineer. + alert action Done @@ -2624,6 +2701,10 @@ chat item action แก้ไขโปรไฟล์กลุ่ม No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable เปิดใช้งาน @@ -2657,6 +2738,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all เปิดใช้งานสําหรับทุกคน @@ -2843,6 +2928,10 @@ chat item action เกิดข้อผิดพลาดในการรับคำขอติดต่อ No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) เกิดข้อผิดพลาดในการเพิ่มสมาชิก @@ -2852,11 +2941,19 @@ chat item action Error adding server alert title + + Error adding short link + No comment provided by engineer. + Error changing address เกิดข้อผิดพลาดในการเปลี่ยนที่อยู่ No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile No comment provided by engineer. @@ -2869,7 +2966,7 @@ chat item action Error changing setting เกิดข้อผิดพลาดในการเปลี่ยนการตั้งค่า - No comment provided by engineer. + alert title Error changing to incognito! @@ -2881,7 +2978,7 @@ chat item action Error connecting to forwarding server %@. Please try later. - No comment provided by engineer. + alert message Error creating address @@ -2923,15 +3020,19 @@ chat item action Error decrypting file No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database เกิดข้อผิดพลาดในการลบฐานข้อมูลแชท - No comment provided by engineer. + alert title Error deleting chat! เกิดข้อผิดพลาดในการลบแชท! - No comment provided by engineer. + alert title Error deleting connection @@ -2941,12 +3042,12 @@ chat item action Error deleting database เกิดข้อผิดพลาดในการลบฐานข้อมูล - No comment provided by engineer. + alert title Error deleting old database เกิดข้อผิดพลาดในการลบฐานข้อมูลเก่า - No comment provided by engineer. + alert title Error deleting token @@ -2980,7 +3081,7 @@ chat item action Error exporting chat database เกิดข้อผิดพลาดในการส่งออกฐานข้อมูลแชท - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -2989,7 +3090,7 @@ chat item action Error importing chat database เกิดข้อผิดพลาดในการนำเข้าฐานข้อมูลแชท - No comment provided by engineer. + alert title Error joining group @@ -3008,6 +3109,10 @@ chat item action Error opening chat No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file เกิดข้อผิดพลาดในการรับไฟล์ @@ -3025,10 +3130,14 @@ chat item action Error registering for notifications alert title + + Error rejecting contact request + alert title + Error removing member เกิดข้อผิดพลาดในการลบสมาชิก - No comment provided by engineer. + alert title Error reordering lists @@ -3093,6 +3202,10 @@ chat item action เกิดข้อผิดพลาดในการส่งข้อความ No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! เกิดข้อผิดพลาดในการตั้งค่าใบตอบรับการจัดส่ง! @@ -3110,7 +3223,7 @@ chat item action Error switching profile - No comment provided by engineer. + alert title Error switching profile! @@ -3170,6 +3283,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid เกิดข้อผิดพลาด: URL ไม่ถูกต้อง @@ -3329,6 +3446,10 @@ snd error text ไฟล์และสื่อ chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. ไฟล์และสื่อเป็นสิ่งต้องห้ามในกลุ่มนี้ @@ -3366,6 +3487,23 @@ snd error text ค้นหาแชทได้เร็วขึ้น No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix แก้ไข @@ -3462,8 +3600,8 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3544,7 +3682,7 @@ Error: %2$@ Group already exists! - No comment provided by engineer. + new chat sheet title Group display name @@ -3611,6 +3749,10 @@ Error: %2$@ โปรไฟล์กลุ่มถูกจัดเก็บไว้ในอุปกรณ์ของสมาชิก ไม่ใช่บนเซิร์ฟเวอร์ No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message ข้อความต้อนรับกลุ่ม @@ -3963,7 +4105,7 @@ More improvements are coming soon! Invalid link - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4070,32 +4212,29 @@ More improvements are coming soon! เข้าร่วม swipe action + + Join as %@ + เข้าร่วมเป็น %@ + No comment provided by engineer. + Join group เข้าร่วมกลุ่ม - No comment provided by engineer. + new chat sheet title Join group conversations No comment provided by engineer. - - Join group? - No comment provided by engineer. - Join incognito เข้าร่วมแบบไม่ระบุตัวตน No comment provided by engineer. - - Join with current profile - No comment provided by engineer. - Join your group? This is your link for group %@! - No comment provided by engineer. + new chat action Joining group @@ -4118,6 +4257,10 @@ This is your link for group %@! Keep unused invitation? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections รักษาการเชื่อมต่อของคุณ @@ -4171,6 +4314,10 @@ This is your link for group %@! ออกจากกลุ่ม? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat มาคุยกันใน SimpleX Chat @@ -4220,6 +4367,10 @@ This is your link for group %@! ข้อความสด No comment provided by engineer. + + Loading profile… + in progress text + Local name ชื่อภายในเครื่องเท่านั้น @@ -4293,10 +4444,22 @@ This is your link for group %@! สมาชิก No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports chat feature @@ -4324,6 +4487,10 @@ This is your link for group %@! สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ @@ -4393,6 +4560,10 @@ This is your link for group %@! Message forwarded item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. item status description @@ -4459,6 +4630,10 @@ This is your link for group %@! ข้อความและไฟล์ No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! No comment provided by engineer. @@ -4683,6 +4858,10 @@ This is your link for group %@! New events notification + + New group role: Moderator + No comment provided by engineer. + New in %@ ใหม่ใน %@ @@ -4697,6 +4876,10 @@ This is your link for group %@! บทบาทของสมาชิกใหม่ No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message ข้อความใหม่ @@ -4733,6 +4916,10 @@ This is your link for group %@! No chats in list %@ No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected ไม่ได้เลือกผู้ติดต่อ @@ -4804,6 +4991,10 @@ This is your link for group %@! ไม่อนุญาตให้บันทึกข้อความเสียง No comment provided by engineer. + + No private routing session + alert title + No push server ในเครื่อง @@ -4902,7 +5093,9 @@ This is your link for group %@! Ok ตกลง - alert button + alert action +alert button +new chat action Old database @@ -4987,6 +5180,10 @@ Requires compatible VPN. มีเพียงคุณเท่านั้นที่สามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. มีเพียงคุณเท่านั้นที่สามารถส่งข้อความเสียงได้ @@ -5012,6 +5209,10 @@ Requires compatible VPN. เฉพาะผู้ติดต่อของคุณเท่านั้นที่สามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. ผู้ติดต่อของคุณเท่านั้นที่สามารถส่งข้อความเสียงได้ @@ -5033,20 +5234,28 @@ Requires compatible VPN. Open chat เปิดแชท - No comment provided by engineer. + new chat action Open chat console เปิดคอนโซลการแชท authentication reason + + Open clean link + alert action + Open conditions No comment provided by engineer. + + Open full link + alert action + Open group - No comment provided by engineer. + new chat action Open link? @@ -5056,6 +5265,30 @@ Requires compatible VPN. Open migration to another device authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… No comment provided by engineer. @@ -5149,10 +5382,6 @@ Requires compatible VPN. รหัสผ่านที่จะแสดง No comment provided by engineer. - - Past member %@ - past/unknown group member - Paste desktop address No comment provided by engineer. @@ -5214,7 +5443,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. โปรดตรวจสอบการเชื่อมต่อเครือข่ายของคุณกับ %@ แล้วลองอีกครั้ง - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5274,6 +5503,10 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5291,11 +5524,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง - server test error - Preserve the last message draft, with attachments. เก็บข้อความที่ร่างไว้ล่าสุดพร้อมไฟล์แนบ @@ -5368,7 +5596,11 @@ Error: %@ Private routing error - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -5465,6 +5697,10 @@ Enable in *Network & servers* settings. ปกป้องโปรไฟล์การแชทของคุณด้วยรหัสผ่าน! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout หมดเวลาโปรโตคอล @@ -5671,7 +5907,8 @@ Enable in *Network & servers* settings. Reject ปฏิเสธ - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -5681,7 +5918,11 @@ swipe action Reject contact request ปฏิเสธคำขอติดต่อ - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -5706,6 +5947,10 @@ swipe action Remove image No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member ลบสมาชิกออก @@ -5721,6 +5966,10 @@ swipe action ลบรหัสผ่านออกจาก keychain หรือไม่? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate เจรจาใหม่ @@ -5736,10 +5985,6 @@ swipe action เจรจา enryption ใหม่หรือไม่? No comment provided by engineer. - - Repeat connection request? - No comment provided by engineer. - Repeat download No comment provided by engineer. @@ -5748,10 +5993,6 @@ swipe action Repeat import No comment provided by engineer. - - Repeat join request? - No comment provided by engineer. - Repeat upload No comment provided by engineer. @@ -5781,6 +6022,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -5873,7 +6118,7 @@ swipe action Retry - No comment provided by engineer. + alert action Reveal @@ -5884,6 +6129,18 @@ swipe action Review conditions No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke ถอน @@ -5936,6 +6193,14 @@ chat item action บันทึก (และแจ้งผู้ติดต่อ) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact บันทึกและแจ้งผู้ติดต่อ @@ -5960,6 +6225,10 @@ chat item action บันทึกโปรไฟล์กลุ่ม No comment provided by engineer. + + Save group profile? + alert title + Save list No comment provided by engineer. @@ -6139,6 +6408,10 @@ chat item action ส่งข้อความสด - มันจะอัปเดตสําหรับผู้รับในขณะที่คุณพิมพ์ No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to ส่งใบเสร็จรับการจัดส่งข้อความไปที่ @@ -6198,6 +6471,14 @@ chat item action ส่งใบเสร็จ No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. ส่งจากแกลเลอรีหรือแป้นพิมพ์แบบกำหนดเอง @@ -6207,6 +6488,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. ผู้ส่งยกเลิกการโอนไฟล์ @@ -6332,13 +6617,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. เซิร์ฟเวอร์ต้องการการอนุญาตในการอัปโหลด โปรดตรวจสอบรหัสผ่าน server test error @@ -6404,6 +6689,10 @@ chat item action ตั้งแทนการรับรองความถูกต้องของระบบ No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -6422,6 +6711,10 @@ chat item action ตั้งรหัสผ่านเพื่อส่งออก No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! ตั้งข้อความที่แสดงต่อสมาชิกใหม่! @@ -6487,6 +6780,14 @@ chat item action แชร์ลิงก์ No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile No comment provided by engineer. @@ -6504,6 +6805,18 @@ chat item action แชร์กับผู้ติดต่อ No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -6599,6 +6912,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX address settings + alert title + SimpleX channel link simplex link type @@ -6640,6 +6957,10 @@ chat item action SimpleX protocols reviewed by Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode No comment provided by engineer. @@ -6829,6 +7150,10 @@ report reason TCP connection No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout หมดเวลาการเชื่อมต่อ TCP @@ -6862,10 +7187,26 @@ report reason ถ่ายภาพ No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button แตะปุ่ม @@ -6949,6 +7290,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. แอปสามารถแจ้งให้คุณทราบเมื่อคุณได้รับข้อความหรือคำขอติดต่อ - โปรดเปิดการตั้งค่าเพื่อเปิดใช้งาน @@ -7005,6 +7350,10 @@ It can happen because of some bug or when the connection is compromised.แฮชของข้อความก่อนหน้านี้แตกต่างกัน No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. ข้อความจะถูกลบสำหรับสมาชิกทั้งหมด @@ -7044,7 +7393,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified ผู้ส่งจะไม่ได้รับแจ้ง - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7124,14 +7473,6 @@ It can happen because of some bug or when the connection is compromised.ไม่มีกลุ่มนี้แล้ว No comment provided by engineer. - - This is your own SimpleX address! - No comment provided by engineer. - - - This is your own one-time link! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7149,6 +7490,14 @@ It can happen because of some bug or when the connection is compromised.การตั้งค่านี้ใช้กับข้อความในโปรไฟล์แชทปัจจุบันของคุณ **%@** No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title No comment provided by engineer. @@ -7223,11 +7572,19 @@ You will be prompted to complete authentication before this feature is enabled.< To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. เพื่อรองรับการแจ้งเตือนแบบทันที ฐานข้อมูลการแชทจะต้องได้รับการโยกย้าย No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -7444,11 +7801,35 @@ To connect, please ask your contact to create another connection link and check การอัปเดตการตั้งค่าจะเชื่อมต่อไคลเอนต์กับเซิร์ฟเวอร์ทั้งหมดอีกครั้ง No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat อัปเกรดและเปิดการแชท No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors No comment provided by engineer. @@ -7507,7 +7888,7 @@ To connect, please ask your contact to create another connection link and check Use current profile - No comment provided by engineer. + new chat action Use for files @@ -7531,9 +7912,13 @@ To connect, please ask your contact to create another connection link and check ใช้อินเทอร์เฟซการโทร iOS No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile - No comment provided by engineer. + new chat action Use only local notifications? @@ -7556,10 +7941,6 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. No comment provided by engineer. @@ -7745,6 +8126,10 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new มีอะไรใหม่ @@ -7853,11 +8238,11 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -7865,20 +8250,16 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. - No comment provided by engineer. - - - You are already joining the group via this link! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -7986,10 +8367,14 @@ Repeat join request? You can view invitation link again in connection details. alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! คุณไม่สามารถส่งข้อความได้! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8001,14 +8386,10 @@ Repeat join request? ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น No comment provided by engineer. - - You have already requested connection via this address! - No comment provided by engineer. - You have already requested connection! Repeat connection request? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8065,6 +8446,10 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! คุณจะเชื่อมต่อกับกลุ่มเมื่ออุปกรณ์โฮสต์ของกลุ่มออนไลน์อยู่ โปรดรอหรือตรวจสอบภายหลัง! @@ -8089,10 +8474,6 @@ Repeat connection request? คุณจะต้องตรวจสอบสิทธิ์เมื่อคุณเริ่มหรือกลับมาใช้แอปพลิเคชันอีกครั้งหลังจากผ่านไป 30 วินาทีในพื้นหลัง No comment provided by engineer. - - You will connect to all group members. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. คุณจะยังได้รับสายเรียกเข้าและการแจ้งเตือนจากโปรไฟล์ที่ปิดเสียงเมื่อโปรไฟล์ของเขามีการใช้งาน @@ -8132,6 +8513,10 @@ Repeat connection request? ที่อยู่ SimpleX ของคุณ No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls การโทรของคุณ @@ -8156,8 +8541,16 @@ Repeat connection request? โปรไฟล์แชทของคุณ No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact No comment provided by engineer. @@ -8189,6 +8582,10 @@ Repeat connection request? โปรไฟล์ปัจจุบันของคุณ No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences การตั้งค่าของคุณ @@ -8270,6 +8667,10 @@ Repeat connection request? ด้านบน จากนั้นเลือก: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call รับสายแล้ว @@ -8279,6 +8680,10 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin ผู้ดูแลระบบ @@ -8298,6 +8703,10 @@ Repeat connection request? เห็นด้วยกับการ encryption… chat item text + + all + member criteria value + all members feature role @@ -8375,6 +8784,10 @@ marked deleted chat item preview text กำลังโทร… call status + + can't send messages + No comment provided by engineer. + cancelled %@ ยกเลิก %@ @@ -8425,10 +8838,6 @@ marked deleted chat item preview text เชื่อมต่อสำเร็จ No comment provided by engineer. - - connected directly - rcv group event chat item - connecting กำลังเชื่อมต่อ @@ -8478,6 +8887,14 @@ marked deleted chat item preview text contact %1$@ changed to %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption ผู้ติดต่อมีการ encrypt จากต้นจนจบ @@ -8488,6 +8905,14 @@ marked deleted chat item preview text ผู้ติดต่อไม่มีการ encrypt จากต้นจนจบ No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator ผู้สร้าง @@ -8648,11 +9073,19 @@ pref value forwarded No comment provided by engineer. + + group + shown on group welcome message + group deleted ลบกลุ่มแล้ว No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated อัปเดตโปรไฟล์กลุ่มแล้ว @@ -8746,11 +9179,6 @@ pref value ตัวเอียง No comment provided by engineer. - - join as %@ - เข้าร่วมเป็น %@ - No comment provided by engineer. - left ออกแล้ว @@ -8775,6 +9203,10 @@ pref value เชื่อมต่อสำเร็จ rcv group event chat item + + member has old version + No comment provided by engineer. + message No comment provided by engineer. @@ -8838,6 +9270,10 @@ pref value ไม่มีข้อความ copied message info in history + + not synchronized + No comment provided by engineer. + observer ผู้สังเกตการณ์ @@ -8848,6 +9284,7 @@ pref value ปิด enabled status group pref value +member criteria value time to disappear @@ -8895,6 +9332,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption chat item text @@ -8932,6 +9373,10 @@ time to disappear removed contact address profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture profile update event chat item @@ -8941,10 +9386,34 @@ time to disappear ลบคุณออกแล้ว rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved No comment provided by engineer. @@ -8977,10 +9446,6 @@ time to disappear เปลี่ยนรหัสความปลอดภัยแล้ว chat item text - - send direct message - No comment provided by engineer. - server queue info: %1$@ @@ -9115,10 +9580,9 @@ last received msg: %2$@ you No comment provided by engineer. - - you are invited to group - คุณได้รับเชิญให้เข้าร่วมกลุ่ม - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index bbee40c2b9..8fb8e4ac51 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -192,6 +192,7 @@ %d seconds(s) + %d saniye(ler) delete after time @@ -465,6 +466,7 @@ time interval 1 year + 1 yıl delete after time @@ -561,8 +563,19 @@ time interval Kabul et accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Üye olarak kabul et + alert action + + + Accept as observer + Gözlemci olarak kabul et + alert action + Accept conditions Koşulları kabul et @@ -573,6 +586,11 @@ swipe action Bağlantı isteği kabul edilsin mi? No comment provided by engineer. + + Accept contact request + Kişi isteğini kabul et + alert title + Accept contact request from %@? %@ 'den gelen iletişim isteği kabul edilsin mi? @@ -581,9 +599,14 @@ swipe action Accept incognito Takma adla kabul et - accept contact request via notification + alert action swipe action + + Accept member + Üyeyi kabul et + alert title + Accepted conditions Kabul edilmiş koşullar @@ -591,7 +614,7 @@ swipe action Acknowledged - Onaylandı + Onaylı No comment provided by engineer. @@ -601,6 +624,7 @@ swipe action Active + Aktif token status text @@ -620,8 +644,14 @@ swipe action Add list + Liste ekle No comment provided by engineer. + + Add message + Mesaj ekle + placeholder for sending contact request + Add profile Profil ekle @@ -649,6 +679,7 @@ swipe action Add to list + Listeye ekle No comment provided by engineer. @@ -728,6 +759,7 @@ swipe action All + Hepsi No comment provided by engineer. @@ -742,6 +774,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. + Tüm sohbetler %@ listesinden kaldırılacak ve liste silinecek. alert message @@ -786,10 +819,12 @@ swipe action All reports will be archived for you. + Tüm raporlar sizin için arşivlenecek. No comment provided by engineer. All servers + Tüm sunucular No comment provided by engineer. @@ -832,6 +867,11 @@ swipe action Sürüm düşürmeye izin ver No comment provided by engineer. + + Allow files and media only if your contact allows them. + Dosyalara ve medyaya yalnızca iletişiminiz izin verdiğinde izin verin. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde) @@ -869,6 +909,7 @@ swipe action Allow to report messsages to moderators. + Mesajları moderatörlere bildirmeye izin ver. No comment provided by engineer. @@ -916,6 +957,11 @@ swipe action Kişilerinizin kaybolan mesajlar göndermesine izin verin. No comment provided by engineer. + + Allow your contacts to send files and media. + Kişilerinizin dosya ve medya göndermesine izin verin. + No comment provided by engineer. + Allow your contacts to send voice messages. Kişilerinizin sesli mesajlar göndermesine izin verin. @@ -929,12 +975,12 @@ swipe action Already connecting! Zaten bağlanılıyor! - No comment provided by engineer. + new chat sheet title Already joining the group! Zaten gruba bağlanılıyor! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -953,6 +999,7 @@ swipe action Another reason + Başka bir sebep report reason @@ -982,6 +1029,7 @@ swipe action App group: + Uygulama grubu: No comment provided by engineer. @@ -1031,14 +1079,17 @@ swipe action Archive + Arşivle No comment provided by engineer. Archive %lld reports? + %lld raporu arşivle? No comment provided by engineer. Archive all reports? + Tüm raporlar arşivlensin mi? No comment provided by engineer. @@ -1053,14 +1104,17 @@ swipe action Archive report + Raporu arşivle No comment provided by engineer. Archive report? + Rapor arşivlensin mi? No comment provided by engineer. Archive reports + Raporları arşivle swipe action @@ -1133,11 +1187,6 @@ swipe action Fotoğrafları otomatik kabul et No comment provided by engineer. - - Auto-accept settings - Ayarları otomatik olarak kabul et - alert title - Back Geri @@ -1175,6 +1224,7 @@ swipe action Better groups performance + Daha iyi grup performansı No comment provided by engineer. @@ -1199,6 +1249,7 @@ swipe action Better privacy and security + Daha iyi gizlilik ve güvenlik No comment provided by engineer. @@ -1211,6 +1262,16 @@ swipe action Daha iyi kullanıcı deneyimi No comment provided by engineer. + + Bio + Biy + No comment provided by engineer. + + + Bio too large + Biyografi çok uzun + alert title + Black Siyah @@ -1261,6 +1322,11 @@ swipe action Medyayı bulanıklaştır No comment provided by engineer. + + Bot + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Sen ve konuştuğun kişi mesaj tepkileri ekleyebilir. @@ -1281,6 +1347,11 @@ swipe action Sen ve konuştuğun kişi kaybolan mesajlar gönderebilir. No comment provided by engineer. + + Both you and your contact can send files and media. + Sen de kişilerin de medya ve dosya gönderebilir. + No comment provided by engineer. + Both you and your contact can send voice messages. Sen ve konuştuğun kişi sesli mesaj gönderebilir. @@ -1301,8 +1372,14 @@ swipe action İş konuşmaları No comment provided by engineer. + + Business connection + İş bağlantısı + No comment provided by engineer. + Businesses + İşletmeler No comment provided by engineer. @@ -1314,6 +1391,9 @@ swipe action By using SimpleX Chat you agree to: - send only legal content in public groups. - respect other users – no spam. + SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz: +- herkese açık gruplarda yalnızca yasal içerik göndermek. +- diğer kullanıcılara saygı göstermek – spam yapmamak. No comment provided by engineer. @@ -1346,6 +1426,11 @@ swipe action Üye aranamaz No comment provided by engineer. + + Can't change profile + Profil değiştirilemiyor + alert title + Can't invite contact! Kişi davet edilemiyor! @@ -1365,7 +1450,8 @@ swipe action Cancel İptal et alert action -alert button +alert button +new chat action Cancel migration @@ -1404,6 +1490,7 @@ alert button Change automatic message deletion? + Otomatik mesaj silme değiştirilsin mi? alert title @@ -1470,7 +1557,7 @@ set passcode view Chat already exists! Sohbet zaten mevcut! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1557,11 +1644,31 @@ set passcode view Sohbet senden silinecek - bu geri alınamaz! No comment provided by engineer. + + Chat with admins + Yöneticilerle sohbet et + chat toolbar + + + Chat with member + Üye ile sohbet et + No comment provided by engineer. + + + Chat with members before they join. + Üyeler katılmadan önce onlarla sohbet edin. + No comment provided by engineer. + Chats Sohbetler No comment provided by engineer. + + Chats with members + Üyelerle sohbetler + No comment provided by engineer. + Check messages every 20 min. Her 20 dakikada mesajları kontrol et. @@ -1629,10 +1736,12 @@ set passcode view Clear group? + Grup temizlensin mi? No comment provided by engineer. Clear or delete group? + Grup temizlensin veya silinsin mi? No comment provided by engineer. @@ -1657,6 +1766,7 @@ set passcode view Community guidelines violation + Topluluk kurallarının ihlali report reason @@ -1716,6 +1826,7 @@ set passcode view Configure server operators + Sunucu operatörlerini yapılandır No comment provided by engineer. @@ -1770,6 +1881,7 @@ set passcode view Confirmed + Onaylandı token status text @@ -1782,9 +1894,9 @@ set passcode view Otomatik olarak bağlan No comment provided by engineer. - - Connect incognito - Gizli bağlan + + Connect faster! 🚀 + Daha hızlı bağlanın! 🚀 No comment provided by engineer. @@ -1797,44 +1909,39 @@ set passcode view Arkadaşlarınıza daha hızlı bağlanın. No comment provided by engineer. - - Connect to yourself? - Kendine mi bağlanacaksın? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! Kendine mi bağlanacaksın? Bu senin kendi SimpleX adresin! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! Kendine mi bağlanacaksın? Bu senin kendi tek kullanımlık bağlantın! - No comment provided by engineer. + new chat sheet title Connect via contact address Kişi adresi aracılığıyla bağlan - No comment provided by engineer. + new chat sheet title Connect via link Bağlantı aracılığıyla bağlan - No comment provided by engineer. + new chat sheet title Connect via one-time link Tek kullanımlık bağlantı aracılığıyla bağlan - No comment provided by engineer. + new chat sheet title Connect with %@ %@ ile bağlan - No comment provided by engineer. + new chat action Connected @@ -1893,12 +2000,13 @@ Bu senin kendi tek kullanımlık bağlantın! Connection blocked + Bağlantı engellendi No comment provided by engineer. Connection error Bağlantı hatası - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1908,10 +2016,13 @@ Bu senin kendi tek kullanımlık bağlantın! Connection is blocked by server operator: %@ + Bağlantı sunucu operatörü tarafından engellendi: +%@ No comment provided by engineer. Connection not ready. + Bağlantı hazır değil. No comment provided by engineer. @@ -1926,6 +2037,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connection requires encryption renegotiation. + Bağlantı için şifreleme yeniden görüşmesi gerekiyor. No comment provided by engineer. @@ -1941,7 +2053,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connection timeout Bağlantı süresi geçmiş - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1993,6 +2105,11 @@ Bu senin kendi tek kullanımlık bağlantın! Kişi tercihleri No comment provided by engineer. + + Contact requests from groups + Gruplardan gelen iletişim talepleri + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Kişiler silinecek - bu geri alınamaz ! @@ -2010,6 +2127,7 @@ Bu senin kendi tek kullanımlık bağlantın! Content violates conditions of use + İçerik kullanım koşullarını ihlal ediyor blocking reason @@ -2089,6 +2207,7 @@ Bu senin kendi tek kullanımlık bağlantın! Create list + Liste oluştur No comment provided by engineer. @@ -2106,9 +2225,9 @@ Bu senin kendi tek kullanımlık bağlantın! Sıra oluştur server test step - - Create secret group - Gizli grup oluştur + + Create your address + Adresinizi oluşturun No comment provided by engineer. @@ -2352,6 +2471,7 @@ swipe action Delete chat messages from your device. + Sohbet mesajlarını cihazınızdan silin. No comment provided by engineer. @@ -2364,6 +2484,11 @@ swipe action Sohbet profili silinsin mi? No comment provided by engineer. + + Delete chat with member? + Üye ile sohbet silinsin mi? + alert title + Delete chat? Sohbet silinsin mi? @@ -2446,6 +2571,7 @@ swipe action Delete list? + Liste silinsin mi? alert title @@ -2500,6 +2626,7 @@ swipe action Delete report + Raporu sil No comment provided by engineer. @@ -2539,6 +2666,7 @@ swipe action Delivered even when Apple drops them. + Apple tarafından düşürülse bile teslim edilir. No comment provided by engineer. @@ -2556,11 +2684,21 @@ swipe action Mesaj gönderildi bilgisi! No comment provided by engineer. + + Deprecated options + Kullanımdan kaldırılan seçenekler + No comment provided by engineer. + Description Açıklama No comment provided by engineer. + + Description too large + Açıklama çok büyük + alert title + Desktop address Bilgisayar adresi @@ -2663,10 +2801,12 @@ swipe action Disable automatic message deletion? + Otomatik mesaj silme devre dışı bırakılsın mı? alert title Disable delete messages + Mesaj silmeyi devre dışı bırak alert button @@ -2761,6 +2901,7 @@ swipe action Documents: + Belgeler: No comment provided by engineer. @@ -2775,15 +2916,17 @@ swipe action Don't miss important messages. + Önemli mesajları kaçırmayın. No comment provided by engineer. Don't show again Yeniden gösterme - No comment provided by engineer. + alert action Done + Tamam No comment provided by engineer. @@ -2862,6 +3005,11 @@ chat item action Grup profilini düzenle No comment provided by engineer. + + Empty message! + Boş mesaj! + No comment provided by engineer. + Enable Etkinleştir @@ -2874,6 +3022,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Daha iyi meta veri gizliliği için Ağ & sunucu ayarlarında Flux'u etkinleştirin. No comment provided by engineer. @@ -2896,6 +3045,11 @@ chat item action Kamera erişimini etkinleştir No comment provided by engineer. + + Enable disappearing messages by default. + Varsayılan olarak kaybolan mesajları etkinleştirin. + No comment provided by engineer. + Enable for all Herkes için etkinleştir @@ -3018,6 +3172,7 @@ chat item action Encryption renegotiation in progress. + Şifreleme yeniden görüşmesi devam ediyor. No comment provided by engineer. @@ -3095,6 +3250,11 @@ chat item action Bağlantı isteği kabul edilirken hata oluştu No comment provided by engineer. + + Error accepting member + Üyeyi kabul etme hatası + alert title + Error adding member(s) Üye(ler) eklenirken hata oluştu @@ -3105,11 +3265,21 @@ chat item action Sunucu eklenirken hata oluştu alert title + + Error adding short link + Kısa bağlantı ekleme hatası + No comment provided by engineer. + Error changing address Adres değiştirilirken hata oluştu No comment provided by engineer. + + Error changing chat profile + Sohbet profilini değiştirme hatası + alert title + Error changing connection profile Bağlantı profili değiştirilirken hata oluştu @@ -3123,7 +3293,7 @@ chat item action Error changing setting Ayar değiştirilirken hata oluştu - No comment provided by engineer. + alert title Error changing to incognito! @@ -3132,12 +3302,13 @@ chat item action Error checking token status + Jeton durumu kontrol hatası No comment provided by engineer. Error connecting to forwarding server %@. Please try later. Yönlendirme sunucusu %@'ya bağlanırken hata oluştu. Lütfen daha sonra deneyin. - No comment provided by engineer. + alert message Error creating address @@ -3156,6 +3327,7 @@ chat item action Error creating list + Liste oluşturma hatası alert title @@ -3175,6 +3347,7 @@ chat item action Error creating report + Rapor oluşturma hatası No comment provided by engineer. @@ -3182,15 +3355,20 @@ chat item action Dosya şifresi çözülürken hata oluştu No comment provided by engineer. + + Error deleting chat + Üye ile sohbet silme hatası + alert title + Error deleting chat database Sohbet veritabanı silinirken sorun oluştu - No comment provided by engineer. + alert title Error deleting chat! Sohbet silinirken hata oluştu! - No comment provided by engineer. + alert title Error deleting connection @@ -3200,12 +3378,12 @@ chat item action Error deleting database Veritabanı silinirken hata oluştu - No comment provided by engineer. + alert title Error deleting old database Eski veritabanı silinirken hata oluştu - No comment provided by engineer. + alert title Error deleting token @@ -3240,7 +3418,7 @@ chat item action Error exporting chat database Sohbet veritabanı dışa aktarılırken hata oluştu - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3250,7 +3428,7 @@ chat item action Error importing chat database Sohbet veritabanı içe aktarılırken hata oluştu - No comment provided by engineer. + alert title Error joining group @@ -3269,7 +3447,12 @@ chat item action Error opening chat - Sohbeti açarken sorun oluştu + Kişiyi hazırlama hatası + No comment provided by engineer. + + + Error opening group + Grubu hazırlama hatası No comment provided by engineer. @@ -3289,15 +3472,22 @@ chat item action Error registering for notifications + Bildirimler için kayıt hatası + alert title + + + Error rejecting contact request + Kişi isteğini reddetme hatası alert title Error removing member Kişiyi silerken sorun oluştu - No comment provided by engineer. + alert title Error reordering lists + Listeleri yeniden sıralama hatası alert title @@ -3312,6 +3502,7 @@ chat item action Error saving chat list + Sohbet listesini kaydetme hatası alert title @@ -3364,6 +3555,11 @@ chat item action Mesaj gönderilirken hata oluştu No comment provided by engineer. + + Error setting auto-accept + Otomatik kabul ayarında hata + No comment provided by engineer. + Error setting delivery receipts! Görüldü ayarlanırken hata oluştu! @@ -3382,7 +3578,7 @@ chat item action Error switching profile Profil değiştirme sırasında hata oluştu - No comment provided by engineer. + alert title Error switching profile! @@ -3396,6 +3592,7 @@ chat item action Error testing server connection + Sunucu bağlantısını test etme hatası No comment provided by engineer. @@ -3445,6 +3642,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Hata: URL geçersiz @@ -3482,6 +3683,7 @@ snd error text Expired + Süresi dolmuş token status text @@ -3526,6 +3728,7 @@ snd error text Faster deletion of groups. + Grupların daha hızlı silinmesi. No comment provided by engineer. @@ -3535,6 +3738,7 @@ snd error text Faster sending messages. + Mesajların daha hızlı gönderilmesi. No comment provided by engineer. @@ -3544,6 +3748,7 @@ snd error text Favorites + Favoriler No comment provided by engineer. @@ -3561,6 +3766,8 @@ snd error text File is blocked by server operator: %@. + Dosya sunucu operatörü tarafından engellendi: +%@. file error text @@ -3618,6 +3825,11 @@ snd error text Dosyalar ve medya chat feature + + Files and media are prohibited in this chat. + Bu sohbette dosyalar ve medya yasaktır. + No comment provided by engineer. + Files and media are prohibited. Dosyalar ve medya bu grupta yasaklandı. @@ -3658,6 +3870,23 @@ snd error text Sohbetleri daha hızlı bul No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Düzelt @@ -3690,6 +3919,7 @@ snd error text For all moderators + Tüm moderatörler için No comment provided by engineer. @@ -3709,6 +3939,7 @@ snd error text For me + Benim için No comment provided by engineer. @@ -3767,9 +3998,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Yönlendirme sunucusu %@, hedef sunucu %@'ya bağlanamadı. Lütfen daha sonra deneyin. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Yönlendirme sunucusu %1$@, hedef sunucu %2$@'ya bağlanamadı. Lütfen daha sonra deneyin. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3837,6 +4068,7 @@ Hata: %2$@ Get notified when mentioned. + Bahsedildiğinde bildirim alın. No comment provided by engineer. @@ -3862,7 +4094,7 @@ Hata: %2$@ Group already exists! Grup çoktan mevcut! - No comment provided by engineer. + new chat sheet title Group display name @@ -3929,6 +4161,11 @@ Hata: %2$@ Grup profili üyelerin cihazlarında saklanır, sunucularda değil. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Grup profili değiştirildi. Eğer kaydederseniz, güncellenmiş profil grup üyelerine gönderilecektir. + alert message + Group welcome message Grup hoşgeldin mesajı @@ -3946,6 +4183,7 @@ Hata: %2$@ Groups + Gruplar No comment provided by engineer. @@ -3955,6 +4193,7 @@ Hata: %2$@ Help admins moderating their groups. + Yöneticilere gruplarını yönetmelerinde yardımcı olun. No comment provided by engineer. @@ -4019,6 +4258,7 @@ Hata: %2$@ How it works + Nasıl çalışır alert button @@ -4165,10 +4405,12 @@ Daha fazla iyileştirme yakında geliyor! Inappropriate content + Uygunsuz içerik report reason Inappropriate profile + Uygunsuz profil report reason @@ -4265,22 +4507,27 @@ Daha fazla iyileştirme yakında geliyor! Invalid + Geçersiz token status text Invalid (bad token) + Geçersiz (kötü jeton) token status text Invalid (expired) + Geçersiz (süresi dolmuş) token status text Invalid (unregistered) + Geçersiz (kayıtlı değil) token status text Invalid (wrong topic) + Geçersiz (yanlış konu) token status text @@ -4301,7 +4548,7 @@ Daha fazla iyileştirme yakında geliyor! Invalid link Geçersiz bağlantı - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4414,37 +4661,32 @@ Daha fazla iyileştirme yakında geliyor! Katıl swipe action + + Join as %@ + %@ olarak katıl + No comment provided by engineer. + Join group Gruba katıl - No comment provided by engineer. + new chat sheet title Join group conversations Grup sohbetlerine katıl No comment provided by engineer. - - Join group? - Gruba katılınsın mı? - No comment provided by engineer. - Join incognito Gizli katıl No comment provided by engineer. - - Join with current profile - Şu anki profille katıl - No comment provided by engineer. - Join your group? This is your link for group %@! Bu gruba katılınsın mı? Bu senin grup için bağlantın %@! - No comment provided by engineer. + new chat action Joining group @@ -4471,6 +4713,11 @@ Bu senin grup için bağlantın %@! Kullanılmamış davet tutulsun mu? alert title + + Keep your chats clean + Sohbetlerinizi temiz tutun + No comment provided by engineer. + Keep your connections Bağlantılarınızı koruyun @@ -4526,6 +4773,11 @@ Bu senin grup için bağlantın %@! Gruptan çıkılsın mı? No comment provided by engineer. + + Less traffic on mobile networks. + Mobil ağlarda daha az trafik. + No comment provided by engineer. + Let's talk in SimpleX Chat Hadi SimpleX Chat'te konuşalım @@ -4558,14 +4810,17 @@ Bu senin grup için bağlantın %@! List + Liste swipe action List name and emoji should be different for all lists. + Liste adı ve emojisi tüm listeler için farklı olmalıdır. No comment provided by engineer. List name... + Liste adı... No comment provided by engineer. @@ -4578,6 +4833,11 @@ Bu senin grup için bağlantın %@! Canlı mesajlar No comment provided by engineer. + + Loading profile… + Profil yükleniyor… + in progress text + Local name Yerel isim @@ -4653,13 +4913,29 @@ Bu senin grup için bağlantın %@! Kişi No comment provided by engineer. + + Member %@ + Üye%@ + past/unknown group member + + + Member admission + Üye kabulü + No comment provided by engineer. + Member inactive Üye inaktif item status text + + Member is deleted - can't accept request + Üye silinmiş - istek kabul edilemez + No comment provided by engineer. + Member reports + Üye raporları chat feature @@ -4679,6 +4955,7 @@ Bu senin grup için bağlantın %@! Member will be removed from chat - this cannot be undone! + Üye sohbetten kaldırılacak - bu geri alınamaz! No comment provided by engineer. @@ -4686,6 +4963,11 @@ Bu senin grup için bağlantın %@! Üye gruptan çıkarılacaktır - bu geri alınamaz! No comment provided by engineer. + + Member will join the group, accept member? + Üye gruba katılacak, kabul edilsin mi? + alert message + Members can add message reactions. Grup üyeleri mesaj tepkileri ekleyebilir. @@ -4698,6 +4980,7 @@ Bu senin grup için bağlantın %@! Members can report messsages to moderators. + Üyeler mesajları moderatörlere bildirebilir. No comment provided by engineer. @@ -4727,6 +5010,7 @@ Bu senin grup için bağlantın %@! Mention members 👋 + Üyeleri belirtin 👋 No comment provided by engineer. @@ -4759,6 +5043,11 @@ Bu senin grup için bağlantın %@! Mesaj iletildi item status text + + Message instantly once you tap Connect. + Bağlan'a dokunduğunuzda hemen mesaj gönderin. + No comment provided by engineer. + Message may be delivered later if member becomes active. Kullanıcı aktif olursa mesaj iletilebilir. @@ -4834,6 +5123,11 @@ Bu senin grup için bağlantın %@! Mesajlar & dosyalar No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Mesajlar **uçtan uca şifreleme** ile korunmaktadır. + No comment provided by engineer. + Messages from %@ will be shown! %@ den gelen mesajlar gösterilecektir! @@ -4841,6 +5135,7 @@ Bu senin grup için bağlantın %@! Messages in this chat will never be deleted. + Bu sohbetteki mesajlar asla silinmeyecek. alert message @@ -4945,6 +5240,7 @@ Bu senin grup için bağlantın %@! More + Daha fazla swipe action @@ -4959,6 +5255,7 @@ Bu senin grup için bağlantın %@! More reliable notifications + Daha güvenilir bildirimler No comment provided by engineer. @@ -4978,6 +5275,7 @@ Bu senin grup için bağlantın %@! Mute all + Tümünü sessize al notification label action @@ -5002,6 +5300,7 @@ Bu senin grup için bağlantın %@! Network decentralization + Ağ merkeziyetsizliği No comment provided by engineer. @@ -5016,6 +5315,7 @@ Bu senin grup için bağlantın %@! Network operator + Ağ operatörü No comment provided by engineer. @@ -5030,6 +5330,7 @@ Bu senin grup için bağlantın %@! New + Yeni token status text @@ -5079,8 +5380,14 @@ Bu senin grup için bağlantın %@! New events + Yeni etkinlikler notification + + New group role: Moderator + Yeni grup rolü: Moderatör + No comment provided by engineer. + New in %@ %@ da yeni @@ -5096,6 +5403,11 @@ Bu senin grup için bağlantın %@! Yeni üye rolü No comment provided by engineer. + + New member wants to join the group. + Yeni üye gruba katılmak istiyor. + rcv group event chat item + New message Yeni mesaj @@ -5108,6 +5420,7 @@ Bu senin grup için bağlantın %@! New server + Yeni sunucu No comment provided by engineer. @@ -5122,14 +5435,22 @@ Bu senin grup için bağlantın %@! No chats + Hiç sohbet yok No comment provided by engineer. No chats found + Hiç sohbet bulunamadı No comment provided by engineer. No chats in list %@ + Listede hiç sohbet yok %@ + No comment provided by engineer. + + + No chats with members + Üyelerle hiç sohbet yok No comment provided by engineer. @@ -5179,14 +5500,17 @@ Bu senin grup için bağlantın %@! No media & file servers. + Hiç medya & dosya sunucusu yok. servers error No message + Mesaj yok No comment provided by engineer. No message servers. + Hiç mesaj sunucusu yok. servers error @@ -5209,6 +5533,11 @@ Bu senin grup için bağlantın %@! Sesli mesaj kaydetmek için izin yok No comment provided by engineer. + + No private routing session + Özel yönlendirme oturumu yok + alert title + No push server Yerel @@ -5221,26 +5550,32 @@ Bu senin grup için bağlantın %@! No servers for private message routing. + Özel mesaj yönlendirmesi için hiç sunucu yok. servers error No servers to receive files. + Dosya almak için hiç sunucu yok. servers error No servers to receive messages. + Mesaj almak için hiç sunucu yok. servers error No servers to send files. + Dosya göndermek için hiç sunucu yok. servers error No token! + Hiç jeton yok! alert title No unread chats + Okunmamış sohbet yok No comment provided by engineer. @@ -5255,6 +5590,7 @@ Bu senin grup için bağlantın %@! Notes + Notlar No comment provided by engineer. @@ -5279,14 +5615,17 @@ Bu senin grup için bağlantın %@! Notifications error + Bildirim hatası alert title Notifications privacy + Bildirim gizliliği No comment provided by engineer. Notifications status + Bildirim durumu alert title @@ -5311,7 +5650,9 @@ Bu senin grup için bağlantın %@! Ok Tamam - alert button + alert action +alert button +new chat action Old database @@ -5344,6 +5685,7 @@ VPN'nin etkinleştirilmesi gerekir. Only chat owners can change preferences. + Yalnızca sohbet sahipleri tercihleri değiştirebilir. No comment provided by engineer. @@ -5373,10 +5715,12 @@ VPN'nin etkinleştirilmesi gerekir. Only sender and moderators see it + Sadece gönderici ve moderatörler görür No comment provided by engineer. Only you and moderators see it + Sadece siz ve moderatörler görür No comment provided by engineer. @@ -5399,6 +5743,11 @@ VPN'nin etkinleştirilmesi gerekir. Sadece sen kaybolan mesajlar gönderebilirsin. No comment provided by engineer. + + Only you can send files and media. + Sadece sen dosya ve medya gönderebilirsin. + No comment provided by engineer. + Only you can send voice messages. Sadece sen sesli mesajlar gönderebilirsin. @@ -5424,6 +5773,11 @@ VPN'nin etkinleştirilmesi gerekir. Sadece karşıdaki kişi kaybolan mesajlar gönderebilir. No comment provided by engineer. + + Only your contact can send files and media. + Sadece kişilerin dosya ve medya gönderebilir. + No comment provided by engineer. + Only your contact can send voice messages. Sadece karşıdaki kişi sesli mesajlar gönderebilir. @@ -5441,29 +5795,42 @@ VPN'nin etkinleştirilmesi gerekir. Open changes + Açık değişiklikler No comment provided by engineer. Open chat Sohbeti aç - No comment provided by engineer. + new chat action Open chat console Sohbet konsolunu aç authentication reason + + Open clean link + Temiz linki aç + alert action + Open conditions + Açık koşullar No comment provided by engineer. + + Open full link + Tam linki aç + alert action + Open group Grubu aç - No comment provided by engineer. + new chat action Open link? + Bağlantıyı aç? alert title @@ -5471,6 +5838,36 @@ VPN'nin etkinleştirilmesi gerekir. Başka bir cihaza açık geçiş authentication reason + + Open new chat + Yeni sohbet aç + new chat action + + + Open new group + Yeni grup aç + new chat action + + + Open to accept + Kabul etmek için aç + No comment provided by engineer. + + + Open to connect + Bağlanmak için aç + No comment provided by engineer. + + + Open to join + Katılmak için aç + No comment provided by engineer. + + + Open to use bot + Botu kullanmak için aç + No comment provided by engineer. + Opening app… Uygulama açılıyor… @@ -5478,14 +5875,17 @@ VPN'nin etkinleştirilmesi gerekir. Operator + Operatör No comment provided by engineer. Operator server + Operatör sunucusu alert title Or import archive file + Veya arşiv dosyasını içe aktar No comment provided by engineer. @@ -5510,10 +5910,12 @@ VPN'nin etkinleştirilmesi gerekir. Or to share privately + Veya özel olarak paylaşmak için No comment provided by engineer. Organize chats into lists + Sohbetleri listelere ayır No comment provided by engineer. @@ -5573,11 +5975,6 @@ VPN'nin etkinleştirilmesi gerekir. Gösterilecek şifre No comment provided by engineer. - - Past member %@ - Geçmiş üye %@ - past/unknown group member - Paste desktop address Bilgisayar adresini yapıştır @@ -5648,7 +6045,7 @@ Lütfen diğer herhangi bir sorunu geliştiricilerle paylaşın. Please check your network connection with %@ and try again. Lütfen ağ bağlantınızı %@ ile kontrol edin ve tekrar deneyin. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5709,14 +6106,22 @@ Hata: %@ Please try to disable and re-enable notfications. + Lütfen bildirimleri devre dışı bırakmayı ve yeniden etkinleştirmeyi deneyin. token info + + Please wait for group moderators to review your request to join the group. + Lütfen grup moderatörlerinin gruba katılma isteğinizi incelemesini bekleyin. + snd group event chat item + Please wait for token activation to complete. + Lütfen jeton aktivasyonunun tamamlanmasını bekleyin. token info Please wait for token to be registered. + Lütfen jeton kaydedilene kadar bekleyin. token info @@ -5729,11 +6134,6 @@ Hata: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil - server test error - Preserve the last message draft, with attachments. Son mesaj taslağını ekleriyle birlikte koru. @@ -5746,6 +6146,7 @@ Hata: %@ Preset servers + Ön ayar sunucuları No comment provided by engineer. @@ -5765,10 +6166,12 @@ Hata: %@ Privacy for your customers. + Müşterileriniz için gizlilik. No comment provided by engineer. Privacy policy and conditions of use. + Gizlilik politikası ve kullanım koşulları. No comment provided by engineer. @@ -5778,6 +6181,7 @@ Hata: %@ Private chats, groups and your contacts are not accessible to server operators. + Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez. No comment provided by engineer. @@ -5787,6 +6191,7 @@ Hata: %@ Private media file names. + Özel medya dosyası adları. No comment provided by engineer. @@ -5812,7 +6217,12 @@ Hata: %@ Private routing error Gizli yönlendirme hatası - No comment provided by engineer. + alert title + + + Private routing timeout + Özel yönlendirme zaman aşımı + alert title Profile and server connections @@ -5866,6 +6276,7 @@ Hata: %@ Prohibit reporting messages to moderators. + Mesajları moderatörlere bildirmeyi yasakla. No comment provided by engineer. @@ -5915,6 +6326,11 @@ Enable in *Network & servers* settings. Bir parolayla birlikte sohbet profillerini koru! No comment provided by engineer. + + Protocol background timeout + Protokol arka plan zaman aşımı + No comment provided by engineer. + Protocol timeout Protokol zaman aşımı @@ -6002,7 +6418,7 @@ Enable in *Network & servers* settings. Receipts are disabled - Alıcılar devre dışı bırakıldı + Alındı onayları devre dışı bırakıldı No comment provided by engineer. @@ -6127,20 +6543,24 @@ Enable in *Network & servers* settings. Register + Kaydol No comment provided by engineer. Register notification token? + Bildirim jetonunu kaydet? token info Registered + Kayıtlı token status text Reject Reddet - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6151,7 +6571,12 @@ swipe action Reject contact request Bağlanma isteğini reddet - No comment provided by engineer. + alert title + + + Reject member? + Üyeyi reddet? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6178,6 +6603,11 @@ swipe action Resmi kaldır No comment provided by engineer. + + Remove link tracking + Bağlantı izlemeyi kaldır + No comment provided by engineer. + Remove member Kişiyi sil @@ -6193,6 +6623,11 @@ swipe action Anahtar Zinciri'ndeki parola silinsin mi? No comment provided by engineer. + + Removes messages and blocks members. + Mesajları kaldırır ve üyeleri engeller. + No comment provided by engineer. + Renegotiate Yeniden müzakere @@ -6208,11 +6643,6 @@ swipe action Şifreleme yeniden müzakere edilsin mi? No comment provided by engineer. - - Repeat connection request? - Bağlantı isteği tekrarlansın mı? - No comment provided by engineer. - Repeat download Tekrar indir @@ -6223,11 +6653,6 @@ swipe action İthalatı tekrarla No comment provided by engineer. - - Repeat join request? - Katılma isteği tekrarlansın mı? - No comment provided by engineer. - Repeat upload Yüklemeyi tekrarla @@ -6240,42 +6665,57 @@ swipe action Report + Rapor et chat item action Report content: only group moderators will see it. + İçeriği rapor et: sadece grup moderatörleri görecek. report reason Report member profile: only group moderators will see it. + Üye profilini rapor et: sadece grup moderatörleri görecek. report reason Report other: only group moderators will see it. + Diğerini rapor et: sadece grup moderatörleri görecek. report reason Report reason? + Rapor nedeni? No comment provided by engineer. + + Report sent to moderators + Rapor moderatörlere gönderildi + alert title + Report spam: only group moderators will see it. + Spam rapor et: sadece grup moderatörleri görecek. report reason Report violation: only group moderators will see it. + İhlali rapor et: sadece grup moderatörleri görecek. report reason Report: %@ + Rapor: %@ report in notification Reporting messages to moderators is prohibited. + Mesajları moderatörlere bildirmek yasaktır. No comment provided by engineer. Reports + Raporlar No comment provided by engineer. @@ -6356,7 +6796,7 @@ swipe action Retry Yeniden dene - No comment provided by engineer. + alert action Reveal @@ -6365,8 +6805,24 @@ swipe action Review conditions + Koşulları gözden geçir No comment provided by engineer. + + Review group members + Grup üyelerini gözden geçir + No comment provided by engineer. + + + Review members + Üyeleri gözden geçir + admission stage + + + Review members before admitting ("knocking"). + Üyeleri kabul etmeden önce gözden geçir ("kapı çalma"). + admission stage description + Revoke İptal et @@ -6423,6 +6879,16 @@ chat item action Kaydet (ve kişilere bildir) alert button + + Save (and notify members) + Kaydet (ve üyelere bildir) + alert button + + + Save admission settings? + Kabul ayarlarını kaydet? + alert title + Save and notify contact Kaydet ve kişilere bildir @@ -6448,8 +6914,14 @@ chat item action Grup profilini kaydet No comment provided by engineer. + + Save group profile? + Grup profilini kaydet? + alert title + Save list + Listeyi kaydet No comment provided by engineer. @@ -6642,6 +7114,11 @@ chat item action Bir canlı mesaj gönder - yazışına göre kişiye(lere) kendini günceller No comment provided by engineer. + + Send contact request? + Kişi isteği gönderilsin mi? + No comment provided by engineer. + Send delivery receipts to Görüldü bilgilerini şuraya gönder @@ -6694,6 +7171,7 @@ chat item action Send private reports + Özel raporlar gönder No comment provided by engineer. @@ -6706,6 +7184,16 @@ chat item action Mesajlar gönder No comment provided by engineer. + + Send request + İstek gönder + No comment provided by engineer. + + + Send request without message + Mesaj olmadan istek gönder + No comment provided by engineer. + Send them from gallery or custom keyboards. Bunları galeriden veya özel klavyelerden gönder. @@ -6716,6 +7204,11 @@ chat item action Yeni üyelere 100 adete kadar son mesajları gönderin. No comment provided by engineer. + + Send your private feedback to groups. + Özel geri bildiriminizi gruplara gönderin. + No comment provided by engineer. + Sender cancelled file transfer. Gönderici dosya gönderimini iptal etti. @@ -6733,7 +7226,7 @@ chat item action Sending delivery receipts will be enabled for all contacts. - Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek. + Tüm kişiler için alındı bilgisi gönderme özelliği etkinleştirilecek. No comment provided by engineer. @@ -6823,6 +7316,7 @@ chat item action Server added to operator %@. + Sunucu operatör %@'ya eklendi. alert message @@ -6842,23 +7336,26 @@ chat item action Server operator changed. + Sunucu operatörü değişti. alert title Server operators + Sunucu operatörleri No comment provided by engineer. Server protocol changed. + Sunucu protokolü değişti. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Sunucunun yükleme yapması için yetki gereklidir, şifreyi kontrol edin server test error @@ -6909,6 +7406,7 @@ chat item action Set chat name… + Sohbet adını belirle… No comment provided by engineer. @@ -6931,8 +7429,14 @@ chat item action Sistem kimlik doğrulaması yerine ayarla. No comment provided by engineer. + + Set member admission + Üye kabulünü ayarla + No comment provided by engineer. + Set message expiration in chats. + Sohbetlerde mesajın sonlanma süresini ayarla. No comment provided by engineer. @@ -6950,6 +7454,11 @@ chat item action Dışa aktarmak için parola ayarla No comment provided by engineer. + + Set profile bio and welcome message. + Profil biyografisi ve hoşgeldiniz mesajı düzenle. + No comment provided by engineer. + Set the message shown to new members! Yeni üyeler için gösterilen bir mesaj ayarla! @@ -6988,10 +7497,12 @@ chat item action Share 1-time link with a friend + Arkadaşınızla 1 defaya mahsus bağlantı paylaşın No comment provided by engineer. Share SimpleX address on social media. + SimpleX adresini sosyal medyada paylaşın. No comment provided by engineer. @@ -7001,6 +7512,7 @@ chat item action Share address publicly + Adresinizi herkese açık olarak paylaşın No comment provided by engineer. @@ -7018,6 +7530,16 @@ chat item action Bağlantıyı paylaş No comment provided by engineer. + + Share old address + Eski adresi paylaş + alert button + + + Share old link + Eski bağlantıyı paylaş + alert button + Share profile Profil paylaş @@ -7038,8 +7560,24 @@ chat item action Kişilerle paylaş No comment provided by engineer. + + Share your address + Adresini paylaş + No comment provided by engineer. + + + Short SimpleX address + Kısa SimpleX adresi + No comment provided by engineer. + + + Short description + Kısa açıklama + No comment provided by engineer. + Short link + Kısa bağlantı No comment provided by engineer. @@ -7099,6 +7637,7 @@ chat item action SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat ve Flux, Flux tarafından işletilen sunucuları uygulamaya dahil etmek için bir anlaşma yaptı. No comment provided by engineer. @@ -7133,14 +7672,22 @@ chat item action SimpleX address and 1-time links are safe to share via any messenger. + SimpleX adresi ve 1 defaya mahsus bağlantılar, herhangi bir mesajlaşma uygulaması aracılığıyla paylaşmak için güvenlidir. No comment provided by engineer. SimpleX address or 1-time link? + SimpleX adresi mi yoksa 1 defaya mahsus bağlantı mı? No comment provided by engineer. + + SimpleX address settings + Ayarları otomatik olarak kabul et + alert title + SimpleX channel link + SimpleX kanal bağlantısı simplex link type @@ -7183,6 +7730,11 @@ chat item action SimpleX protokolleri Trail of Bits tarafından incelenmiştir. No comment provided by engineer. + + SimpleX relay link + SimpleX aktarıcı bağlantısı + simplex link type + Simplified incognito mode Basitleştirilmiş gizli mod @@ -7236,6 +7788,8 @@ chat item action Some servers failed the test: %@ + Bazı sunucular testi geçemedi: +%@ alert message @@ -7245,6 +7799,7 @@ chat item action Spam + Spam blocking reason report reason @@ -7335,6 +7890,7 @@ report reason Storage + Depolama No comment provided by engineer. @@ -7392,6 +7948,11 @@ report reason TCP bağlantısı No comment provided by engineer. + + TCP connection bg timeout + TCP bağlantı arka plan zaman aşımı + No comment provided by engineer. + TCP connection timeout TCP bağlantı zaman aşımı @@ -7399,6 +7960,7 @@ report reason TCP port for messaging + Mesajlaşma için TCP portu No comment provided by engineer. @@ -7426,8 +7988,29 @@ report reason Fotoğraf çek No comment provided by engineer. + + Tap Connect to chat + Sohbet etmek için Bağlan'a dokunun + No comment provided by engineer. + + + Tap Connect to send request + Bağlan'a dokunarak isteği gönderin + No comment provided by engineer. + + + Tap Connect to use bot + Botu kullanmak için Bağlan tuşuna bas + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. + Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun. + No comment provided by engineer. + + + Tap Join group + Gruba katıl'a dokunun No comment provided by engineer. @@ -7477,6 +8060,7 @@ report reason Test notifications + Bildirimleri test et No comment provided by engineer. @@ -7516,6 +8100,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + Adres kısa olacak ve profiliniz bu adres üzerinden paylaşılacaktır. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın. @@ -7523,6 +8112,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The app protects your privacy by using different operators in each conversation. + Uygulama, her sohbette farklı operatörler kullanarak gizliliğinizi korur. No comment provided by engineer. @@ -7542,6 +8132,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The connection reached the limit of undelivered messages, your contact may be offline. + Bağlantı, teslim edilmemiş mesajlar limitine ulaştı, kişiniz çevrimdışı olabilir. No comment provided by engineer. @@ -7574,6 +8165,11 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Önceki mesajın hash'i farklı. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + Bağlantı kısa olacak ve grup profili bağlantı üzerinden paylaşılacaktır. + alert message + The message will be deleted for all members. Mesaj tüm üyeler için silinecektir. @@ -7601,10 +8197,12 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The same conditions will apply to operator **%@**. + Aynı koşullar operatör **%@** için de geçerli olacaktır. No comment provided by engineer. The second preset operator in the app! + Uygulamadaki ikinci ön ayar operatörü! No comment provided by engineer. @@ -7615,7 +8213,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The sender will NOT be notified Gönderene BİLDİRİLMEYECEKTİR - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7624,6 +8222,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The servers for new files of your current chat profile **%@**. + Mevcut sohbet profiliniz için yeni dosyaların sunucuları **%@**. No comment provided by engineer. @@ -7643,6 +8242,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. These conditions will also apply for: **%@**. + Bu koşullar ayrıca şunlar için de geçerli olacaktır: **%@**. No comment provided by engineer. @@ -7667,6 +8267,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Bu işlem geri alınamaz - daha önce seçilen tarihten önceki bu sohbette gönderilen ve alınan mesajlar silinecektir. alert message @@ -7704,18 +8305,9 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Bu grup artık mevcut değildir. No comment provided by engineer. - - This is your own SimpleX address! - Bu senin kendi SimpleX adresin! - No comment provided by engineer. - - - This is your own one-time link! - Bu senin kendi tek kullanımlık bağlantın! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Bu bağlantı daha yeni bir uygulama sürümü gerektiriyor. Lütfen uygulamayı güncelleyin veya kişinizden uyumlu bir bağlantı göndermesini isteyin. No comment provided by engineer. @@ -7725,6 +8317,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. This message was deleted or not received yet. + Bu mesaj silindi veya henüz alınmadı. No comment provided by engineer. @@ -7732,6 +8325,16 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Bu ayar, geçerli sohbet profiliniz **%@** deki mesajlara uygulanır. No comment provided by engineer. + + This setting is for your current profile **%@**. + Bu ayar, mevcut profiliniz içindir. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Kaybolma süresi yalnızca yeni kişiler için ayarlanır. + No comment provided by engineer. + Title Başlık @@ -7759,6 +8362,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. To protect against your link being replaced, you can compare contact security codes. + Bağlantınızın değiştirilmesine karşı korunmak için, kişi güvenlik kodlarını karşılaştırabilirsiniz. No comment provided by engineer. @@ -7785,6 +8389,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec To receive + Almak için No comment provided by engineer. @@ -7809,15 +8414,27 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec To send + Göndermek için No comment provided by engineer. + + To send commands you must be connected. + Komut göndermek için bağlı olmanız gerekir. + alert message + To support instant push notifications the chat database has to be migrated. Anlık anlık bildirimleri desteklemek için sohbet veritabanının taşınması gerekir. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Bağlantı denemesinden sonra başka bir profili kullanmak için, sohbeti silin ve bağlantıyı tekrar kullanın. + alert message + To use the servers of **%@**, accept conditions of use. + **%@**'nın sunucularını kullanmak için, kullanım koşullarını kabul edin. No comment provided by engineer. @@ -7837,6 +8454,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Token status: %@. + Jeton durumu: %@. token status @@ -7916,6 +8534,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Undelivered messages + Teslim edilmemiş mesajlar No comment provided by engineer. @@ -8012,6 +8631,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Unsupported connection link + Desteklenmeyen bağlantı bağlantısı No comment provided by engineer. @@ -8041,6 +8661,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Updated conditions + Güncellenmiş koşullar No comment provided by engineer. @@ -8048,11 +8669,41 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Ayarların güncellenmesi, istemciyi tüm sunuculara yeniden bağlayacaktır. No comment provided by engineer. + + Upgrade + Yükseltme + alert button + + + Upgrade address + Adres güncelleme + No comment provided by engineer. + + + Upgrade address? + Adres güncellensin mi? + alert message + Upgrade and open chat Yükselt ve sohbeti aç No comment provided by engineer. + + Upgrade group link? + Grub linki güncellensin mi? + alert message + + + Upgrade link + Linki güncelle + No comment provided by engineer. + + + Upgrade your address + Adresini yükselt + No comment provided by engineer. + Upload errors Yükleme hataları @@ -8085,6 +8736,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use %@ + %@ kullan No comment provided by engineer. @@ -8104,10 +8756,12 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use TCP port %@ when no port is specified. + Port belirtilmediğinde TCP port %@ kullanın. No comment provided by engineer. Use TCP port 443 for preset servers only. + Sadece ön ayar sunucuları için TCP port 443 kullanın. No comment provided by engineer. @@ -8118,14 +8772,16 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use current profile Şu anki profili kullan - No comment provided by engineer. + new chat action Use for files + Dosyalar için kullan No comment provided by engineer. Use for messages + Mesajlar için kullan No comment provided by engineer. @@ -8143,10 +8799,15 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste iOS arama arayüzünden kullan No comment provided by engineer. + + Use incognito profile + Gizli profil kullan + No comment provided by engineer. + Use new incognito profile Yeni gizli profilden kullan - No comment provided by engineer. + new chat action Use only local notifications? @@ -8170,10 +8831,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use servers - No comment provided by engineer. - - - Use short links (BETA) + Sunucuları kullan No comment provided by engineer. @@ -8188,6 +8846,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use web port + Web portunu kullan No comment provided by engineer. @@ -8272,6 +8931,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste View conditions + Koşulları görüntüle No comment provided by engineer. @@ -8281,6 +8941,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste View updated conditions + Güncellenmiş koşulları görüntüle No comment provided by engineer. @@ -8378,6 +9039,11 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Hoş geldiniz mesajı çok uzun No comment provided by engineer. + + Welcome your contacts 👋 + Kişilerine hoş geldin👋 + No comment provided by engineer. + What's new Neler yeni @@ -8395,6 +9061,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Birden fazla operatör etkinleştirildiğinde, hiçbiri kimin kiminle iletişim kurduğunu öğrenmek için meta veriye sahip değildir. No comment provided by engineer. @@ -8494,17 +9161,18 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste You are already connected with %@. + Zaten %@ ile bağlantıdasınız. No comment provided by engineer. You are already connecting to %@. Zaten %@'a bağlanıyorsunuz. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Bu tek seferlik bağlantı üzerinden zaten bağlanıyorsunuz! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8514,24 +9182,19 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste You are already joining the group %@. Zaten %@ grubuna katılıyorsunuz. - No comment provided by engineer. - - - You are already joining the group via this link! - Bu bağlantı üzerinden gruba zaten katılıyorsunuz! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Gruba zaten bu bağlantı üzerinden katılıyorsunuz. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Gruba zaten katılıyorsunuz! Katılma isteği tekrarlansın mı? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8560,6 +9223,7 @@ Katılma isteği tekrarlansın mı? You can configure servers via settings. + Sunucuları ayarlar aracılığıyla yapılandırabilirsiniz. No comment provided by engineer. @@ -8604,6 +9268,7 @@ Katılma isteği tekrarlansın mı? You can set connection name, to remember who the link was shared with. + Bağlantı adını ayarlayabilirsiniz, böylece bağlantının kiminle paylaşıldığını hatırlarsınız. No comment provided by engineer. @@ -8646,10 +9311,15 @@ Katılma isteği tekrarlansın mı? Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin. alert message + + You can view your reports in Chat with admins. + Raporlarınızı Yöneticilerle Sohbet bölümünde görüntüleyebilirsiniz. + alert message + You can't send messages! Mesajlar gönderemezsiniz! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8661,17 +9331,12 @@ Katılma isteği tekrarlansın mı? Kimin bağlanabileceğine siz karar verirsiniz. No comment provided by engineer. - - You have already requested connection via this address! - Bu adres üzerinden zaten bağlantı talebinde bulundunuz! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Zaten bağlantı isteğinde bulundunuz! Bağlantı isteği tekrarlansın mı? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8730,8 +9395,14 @@ Bağlantı isteği tekrarlansın mı? You should receive notifications. + Bildirim almanız gerekiyor. token info + + You will be able to send messages **only after your request is accepted**. + Mesaj gönderebilmek için **isteğinizin kabul edilmesini beklemelisiniz**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Grup sahibinin cihazı çevrimiçi olduğunda gruba bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin! @@ -8757,11 +9428,6 @@ Bağlantı isteği tekrarlansın mı? Arka planda 30 saniye kaldıktan sonra uygulamayı başlattığınızda veya devam ettirdiğinizde kimlik doğrulaması yapmanız gerekecektir. No comment provided by engineer. - - You will connect to all group members. - Bütün grup üyelerine bağlanacaksın. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz. @@ -8769,6 +9435,7 @@ Bağlantı isteği tekrarlansın mı? You will stop receiving messages from this chat. Chat history will be preserved. + Bu sohbetten mesaj almaya son vereceksiniz. Sohbet geçmişi korunacaktır. No comment provided by engineer. @@ -8801,6 +9468,11 @@ Bağlantı isteği tekrarlansın mı? SimpleX adresin No comment provided by engineer. + + Your business contact + İş bağlantınız + No comment provided by engineer. + Your calls Aramaların @@ -8826,11 +9498,21 @@ Bağlantı isteği tekrarlansın mı? Sohbet profillerin No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Sohbetiniz %@'ya taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Bağlantınız %@ adresine taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu. No comment provided by engineer. + + Your contact + İrtibat kişiniz + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kişiniz şu anda desteklenen maksimum boyuttan (%@) daha büyük bir dosya gönderdi. @@ -8861,6 +9543,11 @@ Bağlantı isteği tekrarlansın mı? Mevcut profiliniz No comment provided by engineer. + + Your group + Grubunuz + No comment provided by engineer. + Your preferences Tercihleriniz @@ -8908,6 +9595,7 @@ Bağlantı isteği tekrarlansın mı? Your servers + Sunucularınız No comment provided by engineer. @@ -8945,6 +9633,11 @@ Bağlantı isteği tekrarlansın mı? yukarı çıkın, ardından seçin: No comment provided by engineer. + + accepted %@ + kabul edildi %@ + rcv group event chat item + accepted call kabul edilen arama @@ -8952,8 +9645,14 @@ Bağlantı isteği tekrarlansın mı? accepted invitation + davetiye kabul edildi chat list item title + + accepted you + seni kabul etti + rcv group event chat item + admin yönetici @@ -8974,6 +9673,11 @@ Bağlantı isteği tekrarlansın mı? şifreleme kabul ediliyor… chat item text + + all + tümü + member criteria value + all members bütün üyeler @@ -8991,6 +9695,7 @@ Bağlantı isteği tekrarlansın mı? archived report + arşivlenmiş rapor No comment provided by engineer. @@ -9059,6 +9764,11 @@ marked deleted chat item preview text aranıyor… call status + + can't send messages + mesaj gönderilemiyor + No comment provided by engineer. + cancelled %@ %@ iptal edildi @@ -9109,11 +9819,6 @@ marked deleted chat item preview text bağlanıldı No comment provided by engineer. - - connected directly - doğrudan bağlandı - rcv group event chat item - connecting bağlanılıyor @@ -9164,6 +9869,16 @@ marked deleted chat item preview text %1$@ kişisi %2$@ olarak değişti profile update event chat item + + contact deleted + kişi silindi + No comment provided by engineer. + + + contact disabled + kişi devre dışı + No comment provided by engineer. + contact has e2e encryption kişi uçtan uca şifrelemeye sahiptir @@ -9174,6 +9889,16 @@ marked deleted chat item preview text kişi uçtan uca şifrelemeye sahip değildir No comment provided by engineer. + + contact not ready + kişi hazır değil + No comment provided by engineer. + + + contact should accept… + kişi kabul etmeli… + No comment provided by engineer. + creator oluşturan @@ -9332,7 +10057,7 @@ pref value expired - Süresi dolmuş + süresi dolmuş No comment provided by engineer. @@ -9340,11 +10065,21 @@ pref value iletildi No comment provided by engineer. + + group + grup + shown on group welcome message + group deleted grup silindi No comment provided by engineer. + + group is deleted + grup silindi + No comment provided by engineer. + group profile updated grup profili güncellendi @@ -9440,11 +10175,6 @@ pref value italik No comment provided by engineer. - - join as %@ - %@ olarak katıl - No comment provided by engineer. - left ayrıldı @@ -9470,6 +10200,11 @@ pref value bağlanıldı rcv group event chat item + + member has old version + üye eski sürümde + No comment provided by engineer. + message mesaj @@ -9502,6 +10237,7 @@ pref value moderator + moderatör member role @@ -9534,6 +10270,11 @@ pref value metin yok copied message info in history + + not synchronized + senkronize edilmedi + No comment provided by engineer. + observer gözlemci @@ -9544,6 +10285,7 @@ pref value kapalı enabled status group pref value +member criteria value time to disappear @@ -9588,10 +10330,17 @@ time to disappear pending + beklemede No comment provided by engineer. pending approval + onay bekliyor + No comment provided by engineer. + + + pending review + inceleme bekliyor No comment provided by engineer. @@ -9611,6 +10360,7 @@ time to disappear rejected + reddedildi No comment provided by engineer. @@ -9633,6 +10383,11 @@ time to disappear kişi adresi silindi profile update event chat item + + removed from group + gruptan çıkarıldı + No comment provided by engineer. + removed profile picture profil fotoğrafı silindi @@ -9643,10 +10398,41 @@ time to disappear sen kaldırıldın rcv group event chat item + + request is sent + istek gönderildi + No comment provided by engineer. + + + request to join rejected + katılma isteği reddedildi + No comment provided by engineer. + + + requested connection + istenilen bağlantı + rcv group event chat item + + + requested connection from group %@ + %@ grubundan bağlantı isteği + rcv direct event chat item + requested to connect + bağlanma isteği gönderildi chat list item title + + review + incele + No comment provided by engineer. + + + reviewed by admins + yöneticiler tarafından incelendi + No comment provided by engineer. + saved kaydedildi @@ -9682,11 +10468,6 @@ time to disappear güvenlik kodu değiştirildi chat item text - - send direct message - doğrudan mesaj gönder - No comment provided by engineer. - server queue info: %1$@ @@ -9836,10 +10617,10 @@ son alınan msj: %2$@ sen No comment provided by engineer. - - you are invited to group - gruba davet edildiniz - No comment provided by engineer. + + you accepted this member + bu üyeyi kabul ettiniz + snd group event chat item you are observer @@ -9974,22 +10755,27 @@ son alınan msj: %2$@ %d new events + %d yeni olaylar notification body From %d chat(s) + %d 'dan sohbetler notification body From: %@ + Şuradan: %@@ notification body New events + Yeni etkinlikler notification New messages + Yeni mesajlar notification diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index c0375e3b02..bfb565fd65 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -192,6 +192,7 @@ %d seconds(s) + %d секунд(и) delete after time @@ -465,6 +466,7 @@ time interval 1 year + 1 рік delete after time @@ -561,8 +563,19 @@ time interval Прийняти accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + Прийняти як учасника + alert action + + + Accept as observer + Прийняти як спостерігача + alert action + Accept conditions Прийняти умови @@ -573,6 +586,11 @@ swipe action Прийняти запит на підключення? No comment provided by engineer. + + Accept contact request + Прийняти запит на контакт + alert title + Accept contact request from %@? Прийняти запит на контакт від %@? @@ -581,9 +599,14 @@ swipe action Accept incognito Прийняти інкогніто - accept contact request via notification + alert action swipe action + + Accept member + Прийняти учасника + alert title + Accepted conditions Прийняті умови @@ -601,6 +624,7 @@ swipe action Active + Активний token status text @@ -620,8 +644,14 @@ swipe action Add list + Додати список No comment provided by engineer. + + Add message + Додати повідомлення + placeholder for sending contact request + Add profile Додати профіль @@ -649,6 +679,7 @@ swipe action Add to list + Додати до списку No comment provided by engineer. @@ -728,6 +759,7 @@ swipe action All + Всі No comment provided by engineer. @@ -742,6 +774,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. + Всі чати будуть видалені з списку %@, і список буде видалений. alert message @@ -786,10 +819,12 @@ swipe action All reports will be archived for you. + Всі скарги будуть заархівовані для вас. No comment provided by engineer. All servers + Всі сервери No comment provided by engineer. @@ -832,6 +867,10 @@ swipe action Дозволити пониження версії No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Дозволяйте безповоротне видалення повідомлень, тільки якщо контакт дозволяє вам це зробити. (24 години) @@ -869,6 +908,7 @@ swipe action Allow to report messsages to moderators. + Дозволити надсилати скаргу на повідомлення модераторам. No comment provided by engineer. @@ -916,6 +956,10 @@ swipe action Дозвольте своїм контактам надсилати зникаючі повідомлення. No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. Дозвольте своїм контактам надсилати голосові повідомлення. @@ -929,12 +973,12 @@ swipe action Already connecting! Вже підключаємось! - No comment provided by engineer. + new chat sheet title Already joining the group! Вже приєднуємося до групи! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -953,6 +997,7 @@ swipe action Another reason + Інша причина report reason @@ -982,6 +1027,7 @@ swipe action App group: + Група застосунків: No comment provided by engineer. @@ -1031,14 +1077,17 @@ swipe action Archive + Архівувати No comment provided by engineer. Archive %lld reports? + Архівувати %lld скарг? No comment provided by engineer. Archive all reports? + Архівувати всі скарги? No comment provided by engineer. @@ -1053,14 +1102,17 @@ swipe action Archive report + Архівувати скаргу No comment provided by engineer. Archive report? + Архівувати скаргу? No comment provided by engineer. Archive reports + Архівувати скарги swipe action @@ -1133,11 +1185,6 @@ swipe action Автоматичне прийняття зображень No comment provided by engineer. - - Auto-accept settings - Автоприйняття налаштувань - alert title - Back Назад @@ -1175,6 +1222,7 @@ swipe action Better groups performance + Краща продуктивність груп No comment provided by engineer. @@ -1199,6 +1247,7 @@ swipe action Better privacy and security + Краща конфіденційність і безпека No comment provided by engineer. @@ -1211,6 +1260,16 @@ swipe action Покращений користувацький досвід No comment provided by engineer. + + Bio + Біо + No comment provided by engineer. + + + Bio too large + Біографія занадто велика + alert title + Black Чорний @@ -1261,6 +1320,10 @@ swipe action Розмиття медіа No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. Реакції на повідомлення можете додавати як ви, так і ваш контакт. @@ -1281,6 +1344,10 @@ swipe action Ви і ваш контакт можете надсилати зникаючі повідомлення. No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. Надсилати голосові повідомлення можете як ви, так і ваш контакт. @@ -1301,8 +1368,14 @@ swipe action Ділові чати No comment provided by engineer. + + Business connection + Бізнес-зв'язок + No comment provided by engineer. + Businesses + Бізнеси No comment provided by engineer. @@ -1314,6 +1387,9 @@ swipe action By using SimpleX Chat you agree to: - send only legal content in public groups. - respect other users – no spam. + Використовуючи SimpleX Chat, ви погоджуєтеся: +- надсилати лише легальний контент у публічних групах. +- поважати інших користувачів - без спаму. No comment provided by engineer. @@ -1346,6 +1422,11 @@ swipe action Не вдається зателефонувати користувачеві No comment provided by engineer. + + Can't change profile + Не вдається змінити профіль + alert title + Can't invite contact! Не вдається запросити контакт! @@ -1365,7 +1446,8 @@ swipe action Cancel Скасувати alert action -alert button +alert button +new chat action Cancel migration @@ -1404,6 +1486,7 @@ alert button Change automatic message deletion? + Змінити автоматичне видалення повідомлень? alert title @@ -1428,7 +1511,7 @@ alert button Change passcode - Змінити пароль + Змінити код доступу authentication reason @@ -1470,7 +1553,7 @@ set passcode view Chat already exists! Чат вже існує! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1557,11 +1640,31 @@ set passcode view Чат буде видалено для вас - цю дію неможливо скасувати! No comment provided by engineer. + + Chat with admins + Чат з адміністраторами + chat toolbar + + + Chat with member + Чат з учасником + No comment provided by engineer. + + + Chat with members before they join. + Спілкуйтеся з учасниками до того, як вони приєднаються. + No comment provided by engineer. + Chats Чати No comment provided by engineer. + + Chats with members + Чати з учасниками + No comment provided by engineer. + Check messages every 20 min. Перевіряйте повідомлення кожні 20 хв. @@ -1629,10 +1732,12 @@ set passcode view Clear group? + Очистити групу? No comment provided by engineer. Clear or delete group? + Очистити чи видалити групу? No comment provided by engineer. @@ -1657,6 +1762,7 @@ set passcode view Community guidelines violation + Порушення правил спільноти report reason @@ -1716,6 +1822,7 @@ set passcode view Configure server operators + Налаштувати операторів сервера No comment provided by engineer. @@ -1770,6 +1877,7 @@ set passcode view Confirmed + Підтверджений token status text @@ -1782,9 +1890,9 @@ set passcode view Підключення автоматично No comment provided by engineer. - - Connect incognito - Підключайтеся інкогніто + + Connect faster! 🚀 + Підключайтеся швидше! 🚀 No comment provided by engineer. @@ -1797,44 +1905,39 @@ set passcode view Швидше спілкуйтеся з друзями. No comment provided by engineer. - - Connect to yourself? - З'єднатися з самим собою? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! - З'єднатися з самим собою? + З'єднатися з самим собою? Це ваша власна SimpleX-адреса! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! - Підключитися до себе? + Підключитися до себе? Це ваше власне одноразове посилання! - No comment provided by engineer. + new chat sheet title Connect via contact address Підключіться за контактною адресою - No comment provided by engineer. + new chat sheet title Connect via link Підключіться за посиланням - No comment provided by engineer. + new chat sheet title Connect via one-time link Під'єднатися за одноразовим посиланням - No comment provided by engineer. + new chat sheet title Connect with %@ Підключитися до %@ - No comment provided by engineer. + new chat action Connected @@ -1893,12 +1996,13 @@ This is your own one-time link! Connection blocked + Підключення заблоковано No comment provided by engineer. Connection error Помилка підключення - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1908,10 +2012,13 @@ This is your own one-time link! Connection is blocked by server operator: %@ + Підключення заблоковано оператором сервера: +%@ No comment provided by engineer. Connection not ready. + Підключення не готове. No comment provided by engineer. @@ -1926,6 +2033,7 @@ This is your own one-time link! Connection requires encryption renegotiation. + Підключення вимагає повторного узгодження шифрування. No comment provided by engineer. @@ -1941,7 +2049,7 @@ This is your own one-time link! Connection timeout Тайм-аут з'єднання - No comment provided by engineer. + alert title Connection with desktop stopped @@ -1993,6 +2101,10 @@ This is your own one-time link! Налаштування контактів No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! Контакт буде видалено - це неможливо скасувати! @@ -2010,6 +2122,7 @@ This is your own one-time link! Content violates conditions of use + Вміст порушує умови використання blocking reason @@ -2089,6 +2202,7 @@ This is your own one-time link! Create list + Створити список No comment provided by engineer. @@ -2106,9 +2220,9 @@ This is your own one-time link! Створити чергу server test step - - Create secret group - Створити секретну групу + + Create your address + Створіть свою адресу No comment provided by engineer. @@ -2352,6 +2466,7 @@ swipe action Delete chat messages from your device. + Видалити повідомлення чату з вашого пристрою. No comment provided by engineer. @@ -2364,6 +2479,11 @@ swipe action Видалити профіль чату? No comment provided by engineer. + + Delete chat with member? + Видалити чат з учасником? + alert title + Delete chat? Видалити чат? @@ -2446,6 +2566,7 @@ swipe action Delete list? + Видалити список? alert title @@ -2500,6 +2621,7 @@ swipe action Delete report + Видалити скаргу No comment provided by engineer. @@ -2557,11 +2679,20 @@ swipe action Квитанції про доставку! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description Опис No comment provided by engineer. + + Description too large + Опис занадто великий + alert title + Desktop address Адреса робочого столу @@ -2664,10 +2795,12 @@ swipe action Disable automatic message deletion? + Вимкнути автоматичне видалення повідомлень? alert title Disable delete messages + Вимкнути видалення повідомлень alert button @@ -2762,6 +2895,7 @@ swipe action Documents: + Документи: No comment provided by engineer. @@ -2776,15 +2910,17 @@ swipe action Don't miss important messages. + Не пропускайте важливі повідомлення. No comment provided by engineer. Don't show again Більше не показувати - No comment provided by engineer. + alert action Done + Готово No comment provided by engineer. @@ -2863,6 +2999,11 @@ chat item action Редагування профілю групи No comment provided by engineer. + + Empty message! + Порожнє повідомлення! + No comment provided by engineer. + Enable Увімкнути @@ -2875,6 +3016,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Увімкніть Flux у налаштуваннях мережі та серверів для кращої конфіденційності метаданих. No comment provided by engineer. @@ -2897,6 +3039,11 @@ chat item action Увімкніть доступ до камери No comment provided by engineer. + + Enable disappearing messages by default. + Увімкнути зникаючі повідомлення за замовчуванням. + No comment provided by engineer. + Enable for all Увімкнути для всіх @@ -3019,6 +3166,7 @@ chat item action Encryption renegotiation in progress. + Виконується повторне узгодження шифрування. No comment provided by engineer. @@ -3096,6 +3244,11 @@ chat item action Помилка при прийнятті запиту на контакт No comment provided by engineer. + + Error accepting member + Помилка при прийомі учасника + alert title + Error adding member(s) Помилка додавання користувача(ів) @@ -3106,11 +3259,21 @@ chat item action Помилка додавання сервера alert title + + Error adding short link + Помилка додавання короткого посилання + No comment provided by engineer. + Error changing address Помилка зміни адреси No comment provided by engineer. + + Error changing chat profile + Помилка зміни профілю чату + alert title + Error changing connection profile Помилка при зміні профілю з'єднання @@ -3124,7 +3287,7 @@ chat item action Error changing setting Помилка зміни налаштування - No comment provided by engineer. + alert title Error changing to incognito! @@ -3133,12 +3296,13 @@ chat item action Error checking token status + Помилка перевірки статусу токена No comment provided by engineer. Error connecting to forwarding server %@. Please try later. Помилка підключення до сервера переадресації %@. Спробуйте пізніше. - No comment provided by engineer. + alert message Error creating address @@ -3157,6 +3321,7 @@ chat item action Error creating list + Помилка при створенні списку alert title @@ -3176,6 +3341,7 @@ chat item action Error creating report + Помилка при створенні скарги No comment provided by engineer. @@ -3183,15 +3349,20 @@ chat item action Помилка розшифрування файлу No comment provided by engineer. + + Error deleting chat + Помилка при видаленні чату з учасником + alert title + Error deleting chat database Помилка видалення бази даних чату - No comment provided by engineer. + alert title Error deleting chat! Помилка видалення чату! - No comment provided by engineer. + alert title Error deleting connection @@ -3201,12 +3372,12 @@ chat item action Error deleting database Помилка видалення бази даних - No comment provided by engineer. + alert title Error deleting old database Помилка видалення старої бази даних - No comment provided by engineer. + alert title Error deleting token @@ -3241,7 +3412,7 @@ chat item action Error exporting chat database Помилка експорту бази даних чату - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3251,7 +3422,7 @@ chat item action Error importing chat database Помилка імпорту бази даних чату - No comment provided by engineer. + alert title Error joining group @@ -3273,6 +3444,11 @@ chat item action Помилка відкриття чату No comment provided by engineer. + + Error opening group + Помилка відкриття групи + No comment provided by engineer. + Error receiving file Помилка отримання файлу @@ -3290,15 +3466,22 @@ chat item action Error registering for notifications + Помилка під час реєстрації для отримання сповіщень + alert title + + + Error rejecting contact request + Помилка відхилення запиту на контакт alert title Error removing member Помилка видалення учасника - No comment provided by engineer. + alert title Error reordering lists + Помилка при переупорядкуванні списків alert title @@ -3313,6 +3496,7 @@ chat item action Error saving chat list + Помилка під час збереження списку чатів alert title @@ -3365,6 +3549,10 @@ chat item action Помилка надсилання повідомлення No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! Помилка встановлення підтвердження доставлення! @@ -3383,7 +3571,7 @@ chat item action Error switching profile Помилка перемикання профілю - No comment provided by engineer. + alert title Error switching profile! @@ -3397,6 +3585,7 @@ chat item action Error testing server connection + Помилка під час перевірки з'єднання з сервером No comment provided by engineer. @@ -3446,6 +3635,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid Помилка: URL-адреса невірна @@ -3483,6 +3676,7 @@ snd error text Expired + Термін дії закінчився token status text @@ -3527,6 +3721,7 @@ snd error text Faster deletion of groups. + Швидше видалення груп. No comment provided by engineer. @@ -3536,6 +3731,7 @@ snd error text Faster sending messages. + Швидше надсилання повідомлень. No comment provided by engineer. @@ -3545,6 +3741,7 @@ snd error text Favorites + Вибране No comment provided by engineer. @@ -3562,6 +3759,8 @@ snd error text File is blocked by server operator: %@. + Файл заблоковано оператором сервера: +%@. file error text @@ -3619,6 +3818,10 @@ snd error text Файли і медіа chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. Файли та медіа в цій групі заборонені. @@ -3659,6 +3862,23 @@ snd error text Швидше знаходьте чати No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + Можливо, в адресі сервера неправильно вказано відбиток сертифіката + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix Виправити @@ -3691,6 +3911,7 @@ snd error text For all moderators + Для всіх модераторів No comment provided by engineer. @@ -3710,6 +3931,7 @@ snd error text For me + Для мене No comment provided by engineer. @@ -3768,9 +3990,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше. - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + Серверу переадресації %1$@ не вдалося з'єднатися з сервером призначення %2$@. Спробуйте пізніше. + alert message Forwarding server address is incompatible with network settings: %@. @@ -3838,6 +4060,7 @@ Error: %2$@ Get notified when mentioned. + Отримуйте сповіщення, коли вас згадують. No comment provided by engineer. @@ -3863,7 +4086,7 @@ Error: %2$@ Group already exists! Група вже існує! - No comment provided by engineer. + new chat sheet title Group display name @@ -3930,6 +4153,11 @@ Error: %2$@ Профіль групи зберігається на пристроях учасників, а не на серверах. No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + Профіль групи було змінено. Якщо ви збережете його, оновлений профіль буде надіслано учасникам групи. + alert message + Group welcome message Привітальне повідомлення групи @@ -3947,6 +4175,7 @@ Error: %2$@ Groups + Групи No comment provided by engineer. @@ -3956,6 +4185,7 @@ Error: %2$@ Help admins moderating their groups. + Допоможіть адміністраторам модерувати їхні групи. No comment provided by engineer. @@ -4020,6 +4250,7 @@ Error: %2$@ How it works + Як це працює alert button @@ -4166,10 +4397,12 @@ More improvements are coming soon! Inappropriate content + Невідповідний вміст report reason Inappropriate profile + Невідповідний профіль report reason @@ -4266,22 +4499,27 @@ More improvements are coming soon! Invalid + Недійсний token status text Invalid (bad token) + Недійсний (неправильний токен) token status text Invalid (expired) + Недійсний (термін дії закінчився) token status text Invalid (unregistered) + Недійсний (незареєстрований) token status text Invalid (wrong topic) + Недійсний (неправильна тема) token status text @@ -4302,7 +4540,7 @@ More improvements are coming soon! Invalid link Невірне посилання - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4415,37 +4653,32 @@ More improvements are coming soon! Приєднуйтесь swipe action + + Join as %@ + приєднатися як %@ + No comment provided by engineer. + Join group Приєднуйтесь до групи - No comment provided by engineer. + new chat sheet title Join group conversations Приєднуйтесь до групових розмов No comment provided by engineer. - - Join group? - Приєднатися до групи? - No comment provided by engineer. - Join incognito Приєднуйтесь інкогніто No comment provided by engineer. - - Join with current profile - Приєднатися з поточним профілем - No comment provided by engineer. - Join your group? This is your link for group %@! Приєднатися до групи? Це ваше посилання на групу %@! - No comment provided by engineer. + new chat action Joining group @@ -4472,6 +4705,11 @@ This is your link for group %@! Зберігати невикористані запрошення? alert title + + Keep your chats clean + Підтримуйте чистоту в чатах + No comment provided by engineer. + Keep your connections Зберігайте свої зв'язки @@ -4527,6 +4765,11 @@ This is your link for group %@! Покинути групу? No comment provided by engineer. + + Less traffic on mobile networks. + Менше трафіку в мобільних мережах. + No comment provided by engineer. + Let's talk in SimpleX Chat Поговоримо в чаті SimpleX @@ -4559,14 +4802,17 @@ This is your link for group %@! List + Список swipe action List name and emoji should be different for all lists. + Назва списку та емодзі повинні бути різними для всіх списків. No comment provided by engineer. List name... + Ім'я в списку... No comment provided by engineer. @@ -4579,6 +4825,11 @@ This is your link for group %@! Живі повідомлення No comment provided by engineer. + + Loading profile… + Завантаження профілю… + in progress text + Local name Місцева назва @@ -4654,13 +4905,27 @@ This is your link for group %@! Учасник No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + Прийом членів + No comment provided by engineer. + Member inactive Користувач неактивний item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports + Повідомлення учасників chat feature @@ -4688,6 +4953,11 @@ This is your link for group %@! Учасник буде видалений з групи - це неможливо скасувати! No comment provided by engineer. + + Member will join the group, accept member? + Учасник приєднається до групи, прийняти учасника? + alert message + Members can add message reactions. Учасники групи можуть додавати реакції на повідомлення. @@ -4700,6 +4970,7 @@ This is your link for group %@! Members can report messsages to moderators. + Учасники можуть повідомляти повідомлення модераторам. No comment provided by engineer. @@ -4729,6 +5000,7 @@ This is your link for group %@! Mention members 👋 + Згадуйте учасників 👋 No comment provided by engineer. @@ -4761,6 +5033,11 @@ This is your link for group %@! Повідомлення переслано item status text + + Message instantly once you tap Connect. + Миттєве повідомлення, щойно ви натиснете "Підключитися". + No comment provided by engineer. + Message may be delivered later if member becomes active. Повідомлення може бути доставлене пізніше, якщо користувач стане активним. @@ -4836,6 +5113,11 @@ This is your link for group %@! Повідомлення та файли No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + Повідомлення захищені **наскрізним шифруванням**. + No comment provided by engineer. + Messages from %@ will be shown! Повідомлення від %@ будуть показані! @@ -4843,6 +5125,7 @@ This is your link for group %@! Messages in this chat will never be deleted. + Повідомлення в цьому чаті ніколи не будуть видалені. alert message @@ -4947,6 +5230,7 @@ This is your link for group %@! More + Більше swipe action @@ -4981,6 +5265,7 @@ This is your link for group %@! Mute all + Вимкнути звук для всіх notification label action @@ -5035,6 +5320,7 @@ This is your link for group %@! New + Новий token status text @@ -5087,6 +5373,11 @@ This is your link for group %@! Нові події notification + + New group role: Moderator + Нова роль у групі: Модератор + No comment provided by engineer. + New in %@ Нове в %@ @@ -5102,6 +5393,11 @@ This is your link for group %@! Нова роль учасника No comment provided by engineer. + + New member wants to join the group. + Новий учасник хоче приєднатися до групи. + rcv group event chat item + New message Нове повідомлення @@ -5129,14 +5425,22 @@ This is your link for group %@! No chats + Без чатів No comment provided by engineer. No chats found + Чати не знайдено No comment provided by engineer. No chats in list %@ + Немає чатів у списку %@ + No comment provided by engineer. + + + No chats with members + Ніяких чатів з учасниками No comment provided by engineer. @@ -5191,6 +5495,7 @@ This is your link for group %@! No message + Немає повідомлення No comment provided by engineer. @@ -5218,6 +5523,11 @@ This is your link for group %@! Немає дозволу на запис голосового повідомлення No comment provided by engineer. + + No private routing session + Немає приватного сеансу маршрутизації + alert title + No push server Локально @@ -5250,10 +5560,12 @@ This is your link for group %@! No token! + Немає токена! alert title No unread chats + Немає непрочитаних чатів No comment provided by engineer. @@ -5268,6 +5580,7 @@ This is your link for group %@! Notes + Нотатки No comment provided by engineer. @@ -5292,6 +5605,7 @@ This is your link for group %@! Notifications error + Помилка сповіщень alert title @@ -5301,6 +5615,7 @@ This is your link for group %@! Notifications status + Статус сповіщень alert title @@ -5325,7 +5640,9 @@ This is your link for group %@! Ok Гаразд - alert button + alert action +alert button +new chat action Old database @@ -5388,10 +5705,12 @@ Requires compatible VPN. Only sender and moderators see it + Тільки відправник і модератори бачать це No comment provided by engineer. Only you and moderators see it + Тільки ви та модератори бачать це No comment provided by engineer. @@ -5414,6 +5733,10 @@ Requires compatible VPN. Тільки ви можете надсилати зникаючі повідомлення. No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. Тільки ви можете надсилати голосові повідомлення. @@ -5439,6 +5762,10 @@ Requires compatible VPN. Тільки ваш контакт може надсилати зникаючі повідомлення. No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. Тільки ваш контакт може надсилати голосові повідомлення. @@ -5462,25 +5789,34 @@ Requires compatible VPN. Open chat Відкритий чат - No comment provided by engineer. + new chat action Open chat console Відкрийте консоль чату authentication reason + + Open clean link + alert action + Open conditions Відкриті умови No comment provided by engineer. + + Open full link + alert action + Open group Відкрита група - No comment provided by engineer. + new chat action Open link? + Відкрите посилання? alert title @@ -5488,6 +5824,35 @@ Requires compatible VPN. Відкрита міграція на інший пристрій authentication reason + + Open new chat + Відкрити новий чат + new chat action + + + Open new group + Відкрити нову групу + new chat action + + + Open to accept + Відкрити для прийняття + No comment provided by engineer. + + + Open to connect + Відкрито для підключення + No comment provided by engineer. + + + Open to join + Відкрито для приєднання + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… Відкриваємо програму… @@ -5535,6 +5900,7 @@ Requires compatible VPN. Organize chats into lists + Організовуйте чати в списки No comment provided by engineer. @@ -5594,11 +5960,6 @@ Requires compatible VPN. Показати пароль No comment provided by engineer. - - Past member %@ - Колишній учасник %@ - past/unknown group member - Paste desktop address Вставте адресу робочого столу @@ -5669,7 +6030,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. Будь ласка, перевірте підключення до мережі за допомогою %@ і спробуйте ще раз. - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5730,14 +6091,22 @@ Error: %@ Please try to disable and re-enable notfications. + Будь ласка, спробуйте вимкнути та знову увімкнути сповіщення. token info + + Please wait for group moderators to review your request to join the group. + Будь ласка, зачекайте, поки модератори групи розглянуть ваш запит на приєднання до групи. + snd group event chat item + Please wait for token activation to complete. + Будь ласка, дочекайтеся завершення активації токену. token info Please wait for token to be registered. + Будь ласка, зачекайте, поки токен буде зареєстровано. token info @@ -5750,11 +6119,6 @@ Error: %@ Порт No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - Можливо, в адресі сервера неправильно вказано відбиток сертифіката - server test error - Preserve the last message draft, with attachments. Зберегти чернетку останнього повідомлення з вкладеннями. @@ -5792,6 +6156,7 @@ Error: %@ Privacy policy and conditions of use. + Політика конфіденційності та умови використання. No comment provided by engineer. @@ -5801,6 +6166,7 @@ Error: %@ Private chats, groups and your contacts are not accessible to server operators. + Приватні чати, групи та ваші контакти недоступні для операторів сервера. No comment provided by engineer. @@ -5810,6 +6176,7 @@ Error: %@ Private media file names. + Приватні імена медіа-файлів. No comment provided by engineer. @@ -5835,7 +6202,12 @@ Error: %@ Private routing error Помилка приватної маршрутизації - No comment provided by engineer. + alert title + + + Private routing timeout + Тайм-аут приватної маршрутизації + alert title Profile and server connections @@ -5889,6 +6261,7 @@ Error: %@ Prohibit reporting messages to moderators. + Заборонити повідомлення модераторам. No comment provided by engineer. @@ -5938,6 +6311,11 @@ Enable in *Network & servers* settings. Захистіть свої профілі чату паролем! No comment provided by engineer. + + Protocol background timeout + Фоновий тайм-аут протоколу + No comment provided by engineer. + Protocol timeout Тайм-аут протоколу @@ -6150,20 +6528,24 @@ Enable in *Network & servers* settings. Register + Зареєструватися No comment provided by engineer. Register notification token? + Зареєструвати токен сповіщення? token info Registered + Зареєстровано token status text Reject Відхилити - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6174,7 +6556,12 @@ swipe action Reject contact request Відхилити запит на контакт - No comment provided by engineer. + alert title + + + Reject member? + Відхилити учасника? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6201,6 +6588,10 @@ swipe action Видалити зображення No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member Видалити учасника @@ -6216,6 +6607,11 @@ swipe action Видалити парольну фразу з брелока? No comment provided by engineer. + + Removes messages and blocks members. + Видаляє повідомлення та блокує користувачів. + No comment provided by engineer. + Renegotiate Переузгодьте @@ -6231,11 +6627,6 @@ swipe action Переузгодьте шифрування? No comment provided by engineer. - - Repeat connection request? - Повторити запит на підключення? - No comment provided by engineer. - Repeat download Повторити завантаження @@ -6246,11 +6637,6 @@ swipe action Повторний імпорт No comment provided by engineer. - - Repeat join request? - Повторити запит на приєднання? - No comment provided by engineer. - Repeat upload Повторне завантаження @@ -6263,42 +6649,57 @@ swipe action Report + Повідомити chat item action Report content: only group moderators will see it. + Повідомити про контент: тільки модератори групи побачать це. report reason Report member profile: only group moderators will see it. + Повідомити про профіль учасника: тільки модератори групи побачать це. report reason Report other: only group moderators will see it. + Повідомити інше: тільки модератори групи побачать це. report reason Report reason? + Причина повідомлення? No comment provided by engineer. + + Report sent to moderators + Повідомлення надіслано модераторам + alert title + Report spam: only group moderators will see it. + Повідомити про спам: тільки модератори групи побачать це. report reason Report violation: only group moderators will see it. + Повідомити про порушення: тільки модератори групи побачать це. report reason Report: %@ + Повідомити: %@ report in notification Reporting messages to moderators is prohibited. + Повідомляти про повідомлення модераторам заборонено. No comment provided by engineer. Reports + Звіти No comment provided by engineer. @@ -6379,7 +6780,7 @@ swipe action Retry Спробуйте ще раз - No comment provided by engineer. + alert action Reveal @@ -6391,6 +6792,21 @@ swipe action Умови перегляду No comment provided by engineer. + + Review group members + Учасники групи оглядів + No comment provided by engineer. + + + Review members + Схвалювати учасників + admission stage + + + Review members before admitting ("knocking"). + Перевірка учасників перед тим, як їх прийняти («стукіт»). + admission stage description + Revoke Відкликати @@ -6447,6 +6863,16 @@ chat item action Зберегти (і повідомити контактам) alert button + + Save (and notify members) + Зберегти (і повідомити учасникам) + alert button + + + Save admission settings? + Зберегти налаштування входу? + alert title + Save and notify contact Зберегти та повідомити контакт @@ -6472,8 +6898,14 @@ chat item action Зберегти профіль групи No comment provided by engineer. + + Save group profile? + Зберегти профіль групи? + alert title + Save list + Зберегти список No comment provided by engineer. @@ -6666,6 +7098,11 @@ chat item action Надішліть повідомлення в реальному часі - воно буде оновлюватися для одержувача (одержувачів), поки ви його вводите No comment provided by engineer. + + Send contact request? + Надіслати запит на контакт? + No comment provided by engineer. + Send delivery receipts to Надсилання звітів про доставку @@ -6718,6 +7155,7 @@ chat item action Send private reports + Надсилайте приватні звіти No comment provided by engineer. @@ -6730,6 +7168,16 @@ chat item action Надіслати підтвердження No comment provided by engineer. + + Send request + Надіслати запит + No comment provided by engineer. + + + Send request without message + Надіслати запит без повідомлення + No comment provided by engineer. + Send them from gallery or custom keyboards. Надсилайте їх із галереї чи власних клавіатур. @@ -6740,6 +7188,11 @@ chat item action Надішліть до 100 останніх повідомлень новим користувачам. No comment provided by engineer. + + Send your private feedback to groups. + Надсилайте свої приватні відгуки до груп. + No comment provided by engineer. + Sender cancelled file transfer. Відправник скасував передачу файлу. @@ -6880,13 +7333,13 @@ chat item action Протокол сервера змінено. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. Сервер вимагає авторизації для створення черг, перевірте пароль server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. Сервер вимагає авторизації для завантаження, перевірте пароль server test error @@ -6937,6 +7390,7 @@ chat item action Set chat name… + Назвати чат… No comment provided by engineer. @@ -6959,8 +7413,14 @@ chat item action Встановіть його замість аутентифікації системи. No comment provided by engineer. + + Set member admission + Встановити прийом учасників + No comment provided by engineer. + Set message expiration in chats. + Встановлюйте термін придатності повідомлень у чатах. No comment provided by engineer. @@ -6978,6 +7438,11 @@ chat item action Встановити ключову фразу для експорту No comment provided by engineer. + + Set profile bio and welcome message. + Налаштуйте біографію профілю та вітальне повідомлення. + No comment provided by engineer. + Set the message shown to new members! Налаштуйте повідомлення, яке показуватиметься новим користувачам! @@ -7049,6 +7514,16 @@ chat item action Поділіться посиланням No comment provided by engineer. + + Share old address + Поділіться старою адресою + alert button + + + Share old link + Поділіться старим посиланням + alert button + Share profile Поділіться профілем @@ -7069,8 +7544,24 @@ chat item action Поділіться з контактами No comment provided by engineer. + + Share your address + Поділіться своєю адресою + No comment provided by engineer. + + + Short SimpleX address + Коротка адреса SimpleX + No comment provided by engineer. + + + Short description + Короткий опис + No comment provided by engineer. + Short link + Коротке посилання No comment provided by engineer. @@ -7173,8 +7664,14 @@ chat item action SimpleX адреса або одноразове посилання? No comment provided by engineer. + + SimpleX address settings + Автоприйняття налаштувань + alert title + SimpleX channel link + Посилання на канал SimpleX simplex link type @@ -7217,6 +7714,10 @@ chat item action Протоколи SimpleX, розглянуті Trail of Bits. No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode Спрощений режим інкогніто @@ -7281,6 +7782,7 @@ chat item action Spam + Спам blocking reason report reason @@ -7371,6 +7873,7 @@ report reason Storage + Зберігання No comment provided by engineer. @@ -7428,6 +7931,11 @@ report reason TCP-з'єднання No comment provided by engineer. + + TCP connection bg timeout + Таймаут TCP-з'єднання bg + No comment provided by engineer. + TCP connection timeout Тайм-аут TCP-з'єднання @@ -7435,6 +7943,7 @@ report reason TCP port for messaging + TCP-порт для повідомлень No comment provided by engineer. @@ -7462,11 +7971,30 @@ report reason Сфотографуйте No comment provided by engineer. + + Tap Connect to chat + Натисніть Підключитися до чату + No comment provided by engineer. + + + Tap Connect to send request + Натисніть Підключитися, щоб відправити запит + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше. No comment provided by engineer. + + Tap Join group + Натисніть Приєднатися до групи + No comment provided by engineer. + Tap button Натисніть кнопку @@ -7514,6 +8042,7 @@ report reason Test notifications + Тестові сповіщення No comment provided by engineer. @@ -7553,6 +8082,11 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + Адреса буде короткою, і ваш профіль буде доступний за цією адресою. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію. @@ -7613,6 +8147,11 @@ It can happen because of some bug or when the connection is compromised.Хеш попереднього повідомлення відрізняється. No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + Посилання буде коротким, а профіль групи буде поширюватися за посиланням. + alert message + The message will be deleted for all members. Повідомлення буде видалено для всіх учасників. @@ -7656,7 +8195,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified Відправник НЕ буде повідомлений - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7710,6 +8249,7 @@ It can happen because of some bug or when the connection is compromised. This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + Цю дію не можна скасувати — повідомлення, надіслані та отримані в цьому чаті раніше за обраний час, будуть видалені. alert message @@ -7747,18 +8287,9 @@ It can happen because of some bug or when the connection is compromised.Цієї групи більше не існує. No comment provided by engineer. - - This is your own SimpleX address! - Це ваша власна SimpleX-адреса! - No comment provided by engineer. - - - This is your own one-time link! - Це ваше власне одноразове посилання! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Це посилання вимагає новішої версії додатку. Будь ласка, оновіть додаток або попросіть вашого контакту надіслати сумісне посилання. No comment provided by engineer. @@ -7768,6 +8299,7 @@ It can happen because of some bug or when the connection is compromised. This message was deleted or not received yet. + Це повідомлення було видалено або ще не отримано. No comment provided by engineer. @@ -7775,6 +8307,15 @@ It can happen because of some bug or when the connection is compromised.Це налаштування застосовується до повідомлень у вашому поточному профілі чату **%@**. No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + Час зникнення встановлюється тільки для нових контактів. + No comment provided by engineer. + Title Заголовок @@ -7857,11 +8398,20 @@ You will be prompted to complete authentication before this feature is enabled.< Щоб відправити No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. Для підтримки миттєвих push-повідомлень необхідно перенести базу даних чату. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + Щоб використовувати інший профіль після спроби з'єднання, видаліть чат і скористайтеся посиланням знову. + alert message + To use the servers of **%@**, accept conditions of use. Щоб користуватися серверами **%@**, прийміть умови використання. @@ -7884,6 +8434,7 @@ You will be prompted to complete authentication before this feature is enabled.< Token status: %@. + Статус токена: %@. token status @@ -8060,6 +8611,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link + Несумісне посилання для підключення No comment provided by engineer. @@ -8089,6 +8641,7 @@ To connect, please ask your contact to create another connection link and check Updated conditions + Оновлені умови No comment provided by engineer. @@ -8096,11 +8649,41 @@ To connect, please ask your contact to create another connection link and check Оновлення налаштувань призведе до перепідключення клієнта до всіх серверів. No comment provided by engineer. + + Upgrade + Оновлення + alert button + + + Upgrade address + Адреса оновлення + No comment provided by engineer. + + + Upgrade address? + Змінити адресу? + alert message + Upgrade and open chat Оновлення та відкритий чат No comment provided by engineer. + + Upgrade group link? + Оновити посилання на групу? + alert message + + + Upgrade link + Посилання для оновлення + No comment provided by engineer. + + + Upgrade your address + Поновіть свою адресу + No comment provided by engineer. + Upload errors Помилки завантаження @@ -8153,10 +8736,12 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. + Використовуйте TCP-порт %@, якщо порт не вказано. No comment provided by engineer. Use TCP port 443 for preset servers only. + Використовуйте TCP порт 443 лише для попередньо налаштованих серверів. No comment provided by engineer. @@ -8167,7 +8752,7 @@ To connect, please ask your contact to create another connection link and check Use current profile Використовувати поточний профіль - No comment provided by engineer. + new chat action Use for files @@ -8194,10 +8779,15 @@ To connect, please ask your contact to create another connection link and check Використовуйте інтерфейс виклику iOS No comment provided by engineer. + + Use incognito profile + Використовуйте профіль інкогніто + No comment provided by engineer. + Use new incognito profile Використовуйте новий профіль інкогніто - No comment provided by engineer. + new chat action Use only local notifications? @@ -8224,10 +8814,6 @@ To connect, please ask your contact to create another connection link and check Використовуйте сервери No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. Використовуйте додаток під час розмови. @@ -8240,6 +8826,7 @@ To connect, please ask your contact to create another connection link and check Use web port + Використовувати веб-порт No comment provided by engineer. @@ -8432,6 +9019,11 @@ To connect, please ask your contact to create another connection link and check Привітальне повідомлення занадто довге No comment provided by engineer. + + Welcome your contacts 👋 + Вітаємо ваші контакти 👋 + No comment provided by engineer. + What's new Що нового @@ -8555,12 +9147,12 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. Ви вже з'єднані з %@. - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! Ви вже підключаєтеся до %@.Ви вже підключаєтеся за цим одноразовим посиланням! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8570,24 +9162,19 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. Ви вже приєдналися до групи %@. - No comment provided by engineer. - - - You are already joining the group via this link! - Ви вже приєдналися до групи за цим посиланням! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. Ви вже приєдналися до групи за цим посиланням. - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? Ви вже приєдналися до групи! Повторити запит на приєднання? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8704,10 +9291,15 @@ Repeat join request? Ви можете переглянути посилання на запрошення ще раз у деталях підключення. alert message + + You can view your reports in Chat with admins. + Ви можете переглянути свої звіти у чаті з адміністраторами. + alert message + You can't send messages! Ви не можете надсилати повідомлення! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8719,17 +9311,12 @@ Repeat join request? Ви вирішуєте, хто може під'єднатися. No comment provided by engineer. - - You have already requested connection via this address! - Ви вже надсилали запит на підключення за цією адресою! - No comment provided by engineer. - You have already requested connection! Repeat connection request? Ви вже надіслали запит на підключення! Повторити запит на підключення? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8788,8 +9375,14 @@ Repeat connection request? You should receive notifications. + Ви повинні отримувати сповіщення. token info + + You will be able to send messages **only after your request is accepted**. + Ви зможете надсилати повідомлення **тільки після того, як ваш запит буде прийнято**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! Ви будете підключені до групи, коли пристрій господаря групи буде в мережі, будь ласка, зачекайте або перевірте пізніше! @@ -8815,11 +9408,6 @@ Repeat connection request? Вам потрібно буде пройти автентифікацію при запуску або відновленні програми після 30 секунд роботи у фоновому режимі. No comment provided by engineer. - - You will connect to all group members. - Ви з'єднаєтеся з усіма учасниками групи. - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. Ви все одно отримуватимете дзвінки та сповіщення від вимкнених профілів, якщо вони активні. @@ -8860,6 +9448,11 @@ Repeat connection request? Ваша адреса SimpleX No comment provided by engineer. + + Your business contact + Ваш діловий контакт + No comment provided by engineer. + Your calls Твої дзвінки @@ -8885,11 +9478,21 @@ Repeat connection request? Ваші профілі чату No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ваш чат було переміщено на %@, але при перенаправленні на профіль сталася несподівана помилка. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. Ваше з'єднання було переміщено на %@, але під час перенаправлення на профіль сталася несподівана помилка. No comment provided by engineer. + + Your contact + Ваш контакт + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ваш контакт надіслав файл, розмір якого перевищує підтримуваний на цей момент максимальний розмір (%@). @@ -8920,6 +9523,11 @@ Repeat connection request? Ваш поточний профіль No comment provided by engineer. + + Your group + Ваша група + No comment provided by engineer. + Your preferences Ваші уподобання @@ -9005,6 +9613,11 @@ Repeat connection request? вище, а потім обирайте: No comment provided by engineer. + + accepted %@ + прийнято %@ + rcv group event chat item + accepted call прийнято виклик @@ -9015,6 +9628,11 @@ Repeat connection request? прийняте запрошення chat list item title + + accepted you + прийняв(ла) вас + rcv group event chat item + admin адмін @@ -9035,6 +9653,11 @@ Repeat connection request? узгодження шифрування… chat item text + + all + усі + member criteria value + all members всі учасники @@ -9052,6 +9675,7 @@ Repeat connection request? archived report + архівование повідомлення No comment provided by engineer. @@ -9120,6 +9744,11 @@ marked deleted chat item preview text дзвоніть… call status + + can't send messages + не можна надсилати + No comment provided by engineer. + cancelled %@ скасовано %@ @@ -9170,11 +9799,6 @@ marked deleted chat item preview text з'єднаний No comment provided by engineer. - - connected directly - з'єднані безпосередньо - rcv group event chat item - connecting з'єднання @@ -9225,6 +9849,16 @@ marked deleted chat item preview text контакт %1$@ змінено на %2$@ profile update event chat item + + contact deleted + контакт видалено + No comment provided by engineer. + + + contact disabled + контакт вимкнено + No comment provided by engineer. + contact has e2e encryption контакт має шифрування e2e @@ -9235,6 +9869,16 @@ marked deleted chat item preview text контакт не має шифрування e2e No comment provided by engineer. + + contact not ready + контакт не готовий + No comment provided by engineer. + + + contact should accept… + контакт повинен прийняти… + No comment provided by engineer. + creator творець @@ -9401,11 +10045,21 @@ pref value переслано No comment provided by engineer. + + group + група + shown on group welcome message + group deleted групу видалено No comment provided by engineer. + + group is deleted + групу видалено + No comment provided by engineer. + group profile updated оновлено профіль групи @@ -9501,11 +10155,6 @@ pref value курсив No comment provided by engineer. - - join as %@ - приєднатися як %@ - No comment provided by engineer. - left ліворуч @@ -9531,6 +10180,11 @@ pref value з'єднаний rcv group event chat item + + member has old version + учасник використовує застарілу версію + No comment provided by engineer. + message повідомлення @@ -9563,6 +10217,7 @@ pref value moderator + модератор member role @@ -9595,6 +10250,11 @@ pref value без тексту copied message info in history + + not synchronized + не синхронізовано + No comment provided by engineer. + observer спостерігач @@ -9605,6 +10265,7 @@ pref value вимкнено enabled status group pref value +member criteria value time to disappear @@ -9649,10 +10310,17 @@ time to disappear pending + очікує No comment provided by engineer. pending approval + очікує на схвалення + No comment provided by engineer. + + + pending review + очікує на схвалення No comment provided by engineer. @@ -9672,6 +10340,7 @@ time to disappear rejected + відхилено No comment provided by engineer. @@ -9694,6 +10363,11 @@ time to disappear видалено контактну адресу profile update event chat item + + removed from group + видалено з групи + No comment provided by engineer. + removed profile picture видалено зображення профілю @@ -9704,11 +10378,39 @@ time to disappear прибрали вас rcv group event chat item + + request is sent + запит відправлено + No comment provided by engineer. + + + request to join rejected + запит на приєднання відхилено + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect запит на підключення chat list item title + + review + перегляд + No comment provided by engineer. + + + reviewed by admins + схвалено адміністраторами + No comment provided by engineer. + saved збережено @@ -9744,11 +10446,6 @@ time to disappear змінено код безпеки chat item text - - send direct message - надіслати пряме повідомлення - No comment provided by engineer. - server queue info: %1$@ @@ -9898,10 +10595,10 @@ last received msg: %2$@ ти No comment provided by engineer. - - you are invited to group - вас запрошують до групи - No comment provided by engineer. + + you accepted this member + ви прийняли цього учасника + snd group event chat item you are observer @@ -10041,6 +10738,7 @@ last received msg: %2$@ From %d chat(s) + З %d чату(ів) notification body diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d5411f86e3..0a2a252826 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -563,8 +563,17 @@ time interval 接受 accept contact request via notification accept incoming call via notification +alert action swipe action + + Accept as member + alert action + + + Accept as observer + alert action + Accept conditions 接受条款 @@ -575,6 +584,10 @@ swipe action 接受联系人? No comment provided by engineer. + + Accept contact request + alert title + Accept contact request from %@? 接受来自 %@ 的联系人请求? @@ -583,9 +596,13 @@ swipe action Accept incognito 接受隐身聊天 - accept contact request via notification + alert action swipe action + + Accept member + alert title + Accepted conditions 已接受的条款 @@ -626,6 +643,10 @@ swipe action 添加列表 No comment provided by engineer. + + Add message + placeholder for sending contact request + Add profile 添加个人资料 @@ -841,6 +862,10 @@ swipe action 允许降级 No comment provided by engineer. + + Allow files and media only if your contact allows them. + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) 仅有您的联系人许可后才允许不可撤回消息移除 @@ -926,6 +951,10 @@ swipe action 允许您的联系人发送限时消息。 No comment provided by engineer. + + Allow your contacts to send files and media. + No comment provided by engineer. + Allow your contacts to send voice messages. 允许您的联系人发送语音消息。 @@ -939,12 +968,12 @@ swipe action Already connecting! 已经在连接了! - No comment provided by engineer. + new chat sheet title Already joining the group! 已经加入了该群组! - No comment provided by engineer. + new chat sheet title Always use private routing. @@ -1151,11 +1180,6 @@ swipe action 自动接受图片 No comment provided by engineer. - - Auto-accept settings - 自动接受设置 - alert title - Back 返回 @@ -1231,6 +1255,14 @@ swipe action 更佳的使用体验 No comment provided by engineer. + + Bio + No comment provided by engineer. + + + Bio too large + alert title + Black 黑色 @@ -1281,6 +1313,10 @@ swipe action 模糊媒体 No comment provided by engineer. + + Bot + No comment provided by engineer. + Both you and your contact can add message reactions. 您和您的联系人都可以添加消息回应。 @@ -1301,6 +1337,10 @@ swipe action 您和您的联系人都可以发送限时消息。 No comment provided by engineer. + + Both you and your contact can send files and media. + No comment provided by engineer. + Both you and your contact can send voice messages. 您和您的联系人都可以发送语音消息。 @@ -1321,6 +1361,10 @@ swipe action 企业聊天 No comment provided by engineer. + + Business connection + No comment provided by engineer. + Businesses 企业 @@ -1370,6 +1414,10 @@ swipe action 无法呼叫成员 No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! 无法邀请联系人! @@ -1389,7 +1437,8 @@ swipe action Cancel 取消 alert action -alert button +alert button +new chat action Cancel migration @@ -1495,7 +1544,7 @@ set passcode view Chat already exists! 聊天已存在! - No comment provided by engineer. + new chat sheet title Chat colors @@ -1582,11 +1631,27 @@ set passcode view 将为你删除聊天 - 此操作无法撤销! No comment provided by engineer. + + Chat with admins + chat toolbar + + + Chat with member + No comment provided by engineer. + + + Chat with members before they join. + No comment provided by engineer. + Chats 聊天 No comment provided by engineer. + + Chats with members + No comment provided by engineer. + Check messages every 20 min. 每 20 分钟检查消息。 @@ -1812,9 +1877,8 @@ set passcode view 自动连接 No comment provided by engineer. - - Connect incognito - 在隐身状态下连接 + + Connect faster! 🚀 No comment provided by engineer. @@ -1827,44 +1891,39 @@ set passcode view 更快地与您的朋友联系。 No comment provided by engineer. - - Connect to yourself? - 连接到你自己? - No comment provided by engineer. - Connect to yourself? This is your own SimpleX address! 与自己建立联系? 这是您自己的 SimpleX 地址! - No comment provided by engineer. + new chat sheet title Connect to yourself? This is your own one-time link! 与自己建立联系? 这是您自己的一次性链接! - No comment provided by engineer. + new chat sheet title Connect via contact address 通过联系地址连接 - No comment provided by engineer. + new chat sheet title Connect via link 通过链接连接 - No comment provided by engineer. + new chat sheet title Connect via one-time link 通过一次性链接连接 - No comment provided by engineer. + new chat sheet title Connect with %@ 与 %@连接 - No comment provided by engineer. + new chat action Connected @@ -1929,7 +1988,7 @@ This is your own one-time link! Connection error 连接错误 - No comment provided by engineer. + alert title Connection error (AUTH) @@ -1975,7 +2034,7 @@ This is your own one-time link! Connection timeout 连接超时 - No comment provided by engineer. + alert title Connection with desktop stopped @@ -2027,6 +2086,10 @@ This is your own one-time link! 联系人偏好设置 No comment provided by engineer. + + Contact requests from groups + No comment provided by engineer. + Contact will be deleted - this cannot be undone! 联系人将被删除-这是无法撤消的! @@ -2142,9 +2205,8 @@ This is your own one-time link! 创建队列 server test step - - Create secret group - 创建私密群组 + + Create your address No comment provided by engineer. @@ -2401,6 +2463,10 @@ swipe action 删除聊天资料? No comment provided by engineer. + + Delete chat with member? + alert title + Delete chat? 删除聊天? @@ -2596,11 +2662,19 @@ swipe action 送达回执! No comment provided by engineer. + + Deprecated options + No comment provided by engineer. + Description 描述 No comment provided by engineer. + + Description too large + alert title + Desktop address 桌面地址 @@ -2824,7 +2898,7 @@ swipe action Don't show again 不再显示 - No comment provided by engineer. + alert action Done @@ -2907,6 +2981,10 @@ chat item action 编辑群组资料 No comment provided by engineer. + + Empty message! + No comment provided by engineer. + Enable 启用 @@ -2942,6 +3020,10 @@ chat item action 启用相机访问 No comment provided by engineer. + + Enable disappearing messages by default. + No comment provided by engineer. + Enable for all 全部启用 @@ -3142,6 +3224,10 @@ chat item action 接受联系人请求错误 No comment provided by engineer. + + Error accepting member + alert title + Error adding member(s) 添加成员错误 @@ -3152,11 +3238,19 @@ chat item action 添加服务器出错 alert title + + Error adding short link + No comment provided by engineer. + Error changing address 更改地址错误 No comment provided by engineer. + + Error changing chat profile + alert title + Error changing connection profile 更改连接资料出错 @@ -3170,7 +3264,7 @@ chat item action Error changing setting 更改设置错误 - No comment provided by engineer. + alert title Error changing to incognito! @@ -3184,7 +3278,7 @@ chat item action Error connecting to forwarding server %@. Please try later. 连接到转发服务器 %@ 时出错。请稍后尝试。 - No comment provided by engineer. + alert message Error creating address @@ -3231,15 +3325,19 @@ chat item action 解密文件时出错 No comment provided by engineer. + + Error deleting chat + alert title + Error deleting chat database 删除聊天数据库错误 - No comment provided by engineer. + alert title Error deleting chat! 删除聊天错误! - No comment provided by engineer. + alert title Error deleting connection @@ -3249,12 +3347,12 @@ chat item action Error deleting database 删除数据库错误 - No comment provided by engineer. + alert title Error deleting old database 删除旧数据库错误 - No comment provided by engineer. + alert title Error deleting token @@ -3289,7 +3387,7 @@ chat item action Error exporting chat database 导出聊天数据库错误 - No comment provided by engineer. + alert title Error exporting theme: %@ @@ -3299,7 +3397,7 @@ chat item action Error importing chat database 导入聊天数据库错误 - No comment provided by engineer. + alert title Error joining group @@ -3321,6 +3419,10 @@ chat item action 打开聊天时出错 No comment provided by engineer. + + Error opening group + No comment provided by engineer. + Error receiving file 接收文件错误 @@ -3341,10 +3443,14 @@ chat item action 注册消息推送出错 alert title + + Error rejecting contact request + alert title + Error removing member 删除成员错误 - No comment provided by engineer. + alert title Error reordering lists @@ -3416,6 +3522,10 @@ chat item action 发送消息错误 No comment provided by engineer. + + Error setting auto-accept + No comment provided by engineer. + Error setting delivery receipts! 设置送达回执出错! @@ -3434,7 +3544,7 @@ chat item action Error switching profile 切换配置文件出错 - No comment provided by engineer. + alert title Error switching profile! @@ -3498,6 +3608,10 @@ chat item action file error text snd error text + + Error: %@. + server test error + Error: URL is invalid 错误:URL 无效 @@ -3677,6 +3791,10 @@ snd error text 文件和媒体 chat feature + + Files and media are prohibited in this chat. + No comment provided by engineer. + Files and media are prohibited. 此群组中禁止文件和媒体。 @@ -3717,6 +3835,23 @@ snd error text 更快地查找聊天记录 No comment provided by engineer. + + Fingerprint in destination server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in forwarding server address does not match certificate: %@. + No comment provided by engineer. + + + Fingerprint in server address does not match certificate. + 服务器地址中的证书指纹可能不正确 + server test error + + + Fingerprint in server address does not match certificate: %@. + No comment provided by engineer. + Fix 修复 @@ -3828,9 +3963,9 @@ snd error text No comment provided by engineer. - Forwarding server %@ failed to connect to destination server %@. Please try later. - 转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。 - No comment provided by engineer. + Forwarding server %1$@ failed to connect to destination server %2$@. Please try later. + 转发服务器 %1$@ 无法连接到目标服务器 %2$@。请稍后尝试。 + alert message Forwarding server address is incompatible with network settings: %@. @@ -3924,7 +4059,7 @@ Error: %2$@ Group already exists! 群已存在! - No comment provided by engineer. + new chat sheet title Group display name @@ -3991,6 +4126,10 @@ Error: %2$@ 群组资料存储在成员的设备上,而不是服务器上。 No comment provided by engineer. + + Group profile was changed. If you save it, the updated profile will be sent to group members. + alert message + Group welcome message 群欢迎词 @@ -4373,7 +4512,7 @@ More improvements are coming soon! Invalid link 无效链接 - No comment provided by engineer. + alert title Invalid migration confirmation @@ -4486,37 +4625,32 @@ More improvements are coming soon! 加入 swipe action + + Join as %@ + 以 %@ 身份加入 + No comment provided by engineer. + Join group 加入群组 - No comment provided by engineer. + new chat sheet title Join group conversations 加入群对话 No comment provided by engineer. - - Join group? - 加入群组? - No comment provided by engineer. - Join incognito 加入隐身聊天 No comment provided by engineer. - - Join with current profile - 使用当前档案加入 - No comment provided by engineer. - Join your group? This is your link for group %@! 加入您的群组? 这是您组 %@ 的链接! - No comment provided by engineer. + new chat action Joining group @@ -4543,6 +4677,10 @@ This is your link for group %@! 保留未使用的邀请吗? alert title + + Keep your chats clean + No comment provided by engineer. + Keep your connections 保持连接 @@ -4598,6 +4736,10 @@ This is your link for group %@! 离开群组? No comment provided by engineer. + + Less traffic on mobile networks. + No comment provided by engineer. + Let's talk in SimpleX Chat 让我们一起在 SimpleX Chat 里聊天 @@ -4653,6 +4795,10 @@ This is your link for group %@! 实时消息 No comment provided by engineer. + + Loading profile… + in progress text + Local name 本地名称 @@ -4728,11 +4874,23 @@ This is your link for group %@! 成员 No comment provided by engineer. + + Member %@ + past/unknown group member + + + Member admission + No comment provided by engineer. + Member inactive 成员不活跃 item status text + + Member is deleted - can't accept request + No comment provided by engineer. + Member reports 成员举报 @@ -4763,6 +4921,10 @@ This is your link for group %@! 成员将被移出群组——此操作无法撤消! No comment provided by engineer. + + Member will join the group, accept member? + alert message + Members can add message reactions. 群组成员可以添加信息回应。 @@ -4838,6 +5000,10 @@ This is your link for group %@! 消息已转发 item status text + + Message instantly once you tap Connect. + No comment provided by engineer. + Message may be delivered later if member becomes active. 如果 member 变为活动状态,则稍后可能会发送消息。 @@ -4913,6 +5079,10 @@ This is your link for group %@! 消息 No comment provided by engineer. + + Messages are protected by **end-to-end encryption**. + No comment provided by engineer. + Messages from %@ will be shown! 将显示来自 %@ 的消息! @@ -5168,6 +5338,10 @@ This is your link for group %@! 新事件 notification + + New group role: Moderator + No comment provided by engineer. + New in %@ %@ 的新内容 @@ -5183,6 +5357,10 @@ This is your link for group %@! 新成员角色 No comment provided by engineer. + + New member wants to join the group. + rcv group event chat item + New message 新消息 @@ -5223,6 +5401,10 @@ This is your link for group %@! 列表 %@ 中无聊天 No comment provided by engineer. + + No chats with members + No comment provided by engineer. + No contacts selected 未选择联系人 @@ -5303,6 +5485,10 @@ This is your link for group %@! 没有录制语音消息的权限 No comment provided by engineer. + + No private routing session + alert title + No push server 本地 @@ -5415,7 +5601,9 @@ This is your link for group %@! Ok 好的 - alert button + alert action +alert button +new chat action Old database @@ -5506,6 +5694,10 @@ Requires compatible VPN. 只有您可以发送限时消息。 No comment provided by engineer. + + Only you can send files and media. + No comment provided by engineer. + Only you can send voice messages. 只有您可以发送语音消息。 @@ -5531,6 +5723,10 @@ Requires compatible VPN. 只有您的联系人才可以发送限时消息。 No comment provided by engineer. + + Only your contact can send files and media. + No comment provided by engineer. + Only your contact can send voice messages. 只有您的联系人可以发送语音消息。 @@ -5554,22 +5750,30 @@ Requires compatible VPN. Open chat 打开聊天 - No comment provided by engineer. + new chat action Open chat console 打开聊天控制台 authentication reason + + Open clean link + alert action + Open conditions 打开条款 No comment provided by engineer. + + Open full link + alert action + Open group 打开群 - No comment provided by engineer. + new chat action Open link? @@ -5580,6 +5784,30 @@ Requires compatible VPN. 打开迁移到另一台设备 authentication reason + + Open new chat + new chat action + + + Open new group + new chat action + + + Open to accept + No comment provided by engineer. + + + Open to connect + No comment provided by engineer. + + + Open to join + No comment provided by engineer. + + + Open to use bot + No comment provided by engineer. + Opening app… 正在打开应用程序… @@ -5685,11 +5913,6 @@ Requires compatible VPN. 显示密码 No comment provided by engineer. - - Past member %@ - 前任成员 %@ - past/unknown group member - Paste desktop address 粘贴桌面地址 @@ -5760,7 +5983,7 @@ Please share any other issues with the developers. Please check your network connection with %@ and try again. 请检查您与%@的网络连接,然后重试。 - No comment provided by engineer. + alert message Please check yours and your contact preferences. @@ -5823,6 +6046,10 @@ Error: %@ Please try to disable and re-enable notfications. token info + + Please wait for group moderators to review your request to join the group. + snd group event chat item + Please wait for token activation to complete. token info @@ -5840,11 +6067,6 @@ Error: %@ Port No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect - 服务器地址中的证书指纹可能不正确 - server test error - Preserve the last message draft, with attachments. 保留最后的消息草稿及其附件。 @@ -5925,7 +6147,11 @@ Error: %@ Private routing error 专用路由错误 - No comment provided by engineer. + alert title + + + Private routing timeout + alert title Profile and server connections @@ -6028,6 +6254,10 @@ Enable in *Network & servers* settings. 使用密码保护您的聊天资料! No comment provided by engineer. + + Protocol background timeout + No comment provided by engineer. + Protocol timeout 协议超时 @@ -6252,7 +6482,8 @@ Enable in *Network & servers* settings. Reject 拒绝 - reject incoming call via notification + alert action +reject incoming call via notification swipe action @@ -6263,7 +6494,11 @@ swipe action Reject contact request 拒绝联系人请求 - No comment provided by engineer. + alert title + + + Reject member? + alert title Relay server is only used if necessary. Another party can observe your IP address. @@ -6289,6 +6524,10 @@ swipe action 移除图片 No comment provided by engineer. + + Remove link tracking + No comment provided by engineer. + Remove member 删除成员 @@ -6304,6 +6543,10 @@ swipe action 从钥匙串中删除密码? No comment provided by engineer. + + Removes messages and blocks members. + No comment provided by engineer. + Renegotiate 重新协商 @@ -6319,11 +6562,6 @@ swipe action 重新协商加密? No comment provided by engineer. - - Repeat connection request? - 重复连接请求吗? - No comment provided by engineer. - Repeat download 重复下载 @@ -6334,11 +6572,6 @@ swipe action 重复导入 No comment provided by engineer. - - Repeat join request? - 重复加入请求吗? - No comment provided by engineer. - Repeat upload 重复上传 @@ -6369,6 +6602,10 @@ swipe action Report reason? No comment provided by engineer. + + Report sent to moderators + alert title + Report spam: only group moderators will see it. report reason @@ -6467,7 +6704,7 @@ swipe action Retry 重试 - No comment provided by engineer. + alert action Reveal @@ -6479,6 +6716,18 @@ swipe action 审阅条款 No comment provided by engineer. + + Review group members + No comment provided by engineer. + + + Review members + admission stage + + + Review members before admitting ("knocking"). + admission stage description + Revoke 吊销 @@ -6534,6 +6783,14 @@ chat item action 保存(并通知联系人) alert button + + Save (and notify members) + alert button + + + Save admission settings? + alert title + Save and notify contact 保存并通知联系人 @@ -6559,6 +6816,10 @@ chat item action 保存群组资料 No comment provided by engineer. + + Save group profile? + alert title + Save list 保存列表 @@ -6753,6 +7014,10 @@ chat item action 发送实时消息——它会在您键入时为收件人更新 No comment provided by engineer. + + Send contact request? + No comment provided by engineer. + Send delivery receipts to 将送达回执发送给 @@ -6817,6 +7082,14 @@ chat item action 发送回执 No comment provided by engineer. + + Send request + No comment provided by engineer. + + + Send request without message + No comment provided by engineer. + Send them from gallery or custom keyboards. 发送它们来自图库或自定义键盘。 @@ -6827,6 +7100,10 @@ chat item action 给新成员发送最多 100 条历史消息。 No comment provided by engineer. + + Send your private feedback to groups. + No comment provided by engineer. + Sender cancelled file transfer. 发送人已取消文件传输。 @@ -6962,13 +7239,13 @@ chat item action Server protocol changed. alert title - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. 服务器需要授权才能创建队列,检查密码 server test error - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. 服务器需要授权来上传,检查密码 server test error @@ -7041,6 +7318,10 @@ chat item action 设置它以代替系统身份验证。 No comment provided by engineer. + + Set member admission + No comment provided by engineer. + Set message expiration in chats. No comment provided by engineer. @@ -7060,6 +7341,10 @@ chat item action 设置密码来导出 No comment provided by engineer. + + Set profile bio and welcome message. + No comment provided by engineer. + Set the message shown to new members! 设置向新成员显示的消息! @@ -7127,6 +7412,14 @@ chat item action 分享链接 No comment provided by engineer. + + Share old address + alert button + + + Share old link + alert button + Share profile No comment provided by engineer. @@ -7146,6 +7439,18 @@ chat item action 与联系人分享 No comment provided by engineer. + + Share your address + No comment provided by engineer. + + + Short SimpleX address + No comment provided by engineer. + + + Short description + No comment provided by engineer. + Short link No comment provided by engineer. @@ -7250,6 +7555,11 @@ chat item action SimpleX 地址或一次性链接? No comment provided by engineer. + + SimpleX address settings + 自动接受设置 + alert title + SimpleX channel link SimpleX 频道链接 @@ -7295,6 +7605,10 @@ chat item action SimpleX 协议由 Trail of Bits 审阅。 No comment provided by engineer. + + SimpleX relay link + simplex link type + Simplified incognito mode 简化的隐身模式 @@ -7502,6 +7816,10 @@ report reason TCP 连接 No comment provided by engineer. + + TCP connection bg timeout + No comment provided by engineer. + TCP connection timeout TCP 连接超时 @@ -7535,10 +7853,26 @@ report reason 拍照 No comment provided by engineer. + + Tap Connect to chat + No comment provided by engineer. + + + Tap Connect to send request + No comment provided by engineer. + + + Tap Connect to use bot + No comment provided by engineer. + Tap Create SimpleX address in the menu to create it later. No comment provided by engineer. + + Tap Join group + No comment provided by engineer. + Tap button 点击按钮 @@ -7625,6 +7959,10 @@ It can happen because of some bug or when the connection is compromised. No comment provided by engineer. + + The address will be short, and your profile will be shared via the address. + alert message + The app can notify you when you receive messages or contact requests - please open settings to enable. 该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。 @@ -7683,6 +8021,10 @@ It can happen because of some bug or when the connection is compromised.上一条消息的散列不同。 No comment provided by engineer. + + The link will be short, and group profile will be shared via the link. + alert message + The message will be deleted for all members. 将为所有成员删除该消息。 @@ -7724,7 +8066,7 @@ It can happen because of some bug or when the connection is compromised. The sender will NOT be notified 发送者将不会收到通知 - No comment provided by engineer. + alert message The servers for new connections of your current chat profile **%@**. @@ -7812,16 +8154,6 @@ It can happen because of some bug or when the connection is compromised.该群组已不存在。 No comment provided by engineer. - - This is your own SimpleX address! - 这是你自己的 SimpleX 地址! - No comment provided by engineer. - - - This is your own one-time link! - 这是你自己的一次性链接! - No comment provided by engineer. - This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7840,6 +8172,14 @@ It can happen because of some bug or when the connection is compromised.此设置适用于您当前聊天资料 **%@** 中的消息。 No comment provided by engineer. + + This setting is for your current profile **%@**. + No comment provided by engineer. + + + Time to disappear is set only for new contacts. + No comment provided by engineer. + Title 标题 @@ -7917,11 +8257,19 @@ You will be prompted to complete authentication before this feature is enabled.< To send No comment provided by engineer. + + To send commands you must be connected. + alert message + To support instant push notifications the chat database has to be migrated. 为了支持即时推送通知,聊天数据库必须被迁移。 No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. @@ -8154,11 +8502,35 @@ To connect, please ask your contact to create another connection link and check 更新设置会将客户端重新连接到所有服务器。 No comment provided by engineer. + + Upgrade + alert button + + + Upgrade address + No comment provided by engineer. + + + Upgrade address? + alert message + Upgrade and open chat 升级并打开聊天 No comment provided by engineer. + + Upgrade group link? + alert message + + + Upgrade link + No comment provided by engineer. + + + Upgrade your address + No comment provided by engineer. + Upload errors 上传错误 @@ -8223,7 +8595,7 @@ To connect, please ask your contact to create another connection link and check Use current profile 使用当前配置文件 - No comment provided by engineer. + new chat action Use for files @@ -8248,10 +8620,14 @@ To connect, please ask your contact to create another connection link and check 使用 iOS 通话界面 No comment provided by engineer. + + Use incognito profile + No comment provided by engineer. + Use new incognito profile 使用新的隐身配置文件 - No comment provided by engineer. + new chat action Use only local notifications? @@ -8277,10 +8653,6 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. - - Use short links (BETA) - No comment provided by engineer. - Use the app while in the call. 通话时使用本应用. @@ -8482,6 +8854,10 @@ To connect, please ask your contact to create another connection link and check 欢迎消息太大了 No comment provided by engineer. + + Welcome your contacts 👋 + No comment provided by engineer. + What's new 更新内容 @@ -8603,12 +8979,12 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. 您已连接到 %@。 - No comment provided by engineer. + new chat sheet message You are already connecting via this one-time link! 你已经在通过这个一次性链接进行连接! - No comment provided by engineer. + new chat sheet message You are already in group %@. @@ -8618,24 +8994,19 @@ To connect, please ask your contact to create another connection link and check You are already joining the group %@. 您已加入组 %@。 - No comment provided by engineer. - - - You are already joining the group via this link! - 您已经通过此链接加入群组! - No comment provided by engineer. + new chat sheet message You are already joining the group via this link. 你已经在通过此链接加入该群。 - No comment provided by engineer. + new chat sheet message You are already joining the group! Repeat join request? 您已经加入了这个群组! 重复加入请求? - No comment provided by engineer. + new chat sheet title You are connected to the server used to receive messages from this contact. @@ -8750,10 +9121,14 @@ Repeat join request? 您可以在连接详情中再次查看邀请链接。 alert message + + You can view your reports in Chat with admins. + alert message + You can't send messages! 您无法发送消息! - No comment provided by engineer. + alert title You could not be verified; please try again. @@ -8765,17 +9140,12 @@ Repeat join request? 你决定谁可以连接。 No comment provided by engineer. - - You have already requested connection via this address! - 你已经请求通过此地址进行连接! - No comment provided by engineer. - You have already requested connection! Repeat connection request? 您已经请求连接了! 重复连接请求? - No comment provided by engineer. + new chat sheet title You have to enter passphrase every time the app starts - it is not stored on the device. @@ -8836,6 +9206,10 @@ Repeat connection request? You should receive notifications. token info + + You will be able to send messages **only after your request is accepted**. + No comment provided by engineer. + You will be connected to group when the group host's device is online, please wait or check later! 您将在组主设备上线时连接到该群组,请稍等或稍后再检查! @@ -8861,11 +9235,6 @@ Repeat connection request? 当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。 No comment provided by engineer. - - You will connect to all group members. - 你将连接到所有群成员。 - No comment provided by engineer. - You will still receive calls and notifications from muted profiles when they are active. 当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。 @@ -8905,6 +9274,10 @@ Repeat connection request? 您的 SimpleX 地址 No comment provided by engineer. + + Your business contact + No comment provided by engineer. + Your calls 您的通话 @@ -8929,8 +9302,16 @@ Repeat connection request? 您的聊天资料 No comment provided by engineer. - - Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + + Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile. + alert message + + + Your connection was moved to %@ but an error happened when switching profile. + No comment provided by engineer. + + + Your contact No comment provided by engineer. @@ -8962,6 +9343,10 @@ Repeat connection request? 您当前的资料 No comment provided by engineer. + + Your group + No comment provided by engineer. + Your preferences 您的偏好设置 @@ -9045,6 +9430,10 @@ Repeat connection request? 上面,然后选择: No comment provided by engineer. + + accepted %@ + rcv group event chat item + accepted call 已接受通话 @@ -9054,6 +9443,10 @@ Repeat connection request? accepted invitation chat list item title + + accepted you + rcv group event chat item + admin 管理员 @@ -9074,6 +9467,10 @@ Repeat connection request? 同意加密… chat item text + + all + member criteria value + all members 所有成员 @@ -9159,6 +9556,10 @@ marked deleted chat item preview text 呼叫中…… call status + + can't send messages + No comment provided by engineer. + cancelled %@ 已取消 %@ @@ -9209,11 +9610,6 @@ marked deleted chat item preview text 已连接 No comment provided by engineer. - - connected directly - 已直连 - rcv group event chat item - connecting 连接中 @@ -9264,6 +9660,14 @@ marked deleted chat item preview text 联系人 %1$@ 已更改为 %2$@ profile update event chat item + + contact deleted + No comment provided by engineer. + + + contact disabled + No comment provided by engineer. + contact has e2e encryption 联系人具有端到端加密 @@ -9274,6 +9678,14 @@ marked deleted chat item preview text 联系人没有端到端加密 No comment provided by engineer. + + contact not ready + No comment provided by engineer. + + + contact should accept… + No comment provided by engineer. + creator 创建者 @@ -9440,11 +9852,19 @@ pref value 已转发 No comment provided by engineer. + + group + shown on group welcome message + group deleted 群组已删除 No comment provided by engineer. + + group is deleted + No comment provided by engineer. + group profile updated 群组资料已更新 @@ -9540,11 +9960,6 @@ pref value 斜体 No comment provided by engineer. - - join as %@ - 以 %@ 身份加入 - No comment provided by engineer. - left 已离开 @@ -9570,6 +9985,10 @@ pref value 已连接 rcv group event chat item + + member has old version + No comment provided by engineer. + message 消息 @@ -9634,6 +10053,10 @@ pref value 无文本 copied message info in history + + not synchronized + No comment provided by engineer. + observer 观察者 @@ -9644,6 +10067,7 @@ pref value 关闭 enabled status group pref value +member criteria value time to disappear @@ -9694,6 +10118,10 @@ time to disappear pending approval No comment provided by engineer. + + pending review + No comment provided by engineer. + quantum resistant e2e encryption 抗量子端到端加密 @@ -9733,6 +10161,10 @@ time to disappear 删除了联系地址 profile update event chat item + + removed from group + No comment provided by engineer. + removed profile picture 删除了资料图片 @@ -9743,10 +10175,34 @@ time to disappear 已将您移除 rcv group event chat item + + request is sent + No comment provided by engineer. + + + request to join rejected + No comment provided by engineer. + + + requested connection + rcv group event chat item + + + requested connection from group %@ + rcv direct event chat item + requested to connect chat list item title + + review + No comment provided by engineer. + + + reviewed by admins + No comment provided by engineer. + saved 已保存 @@ -9782,11 +10238,6 @@ time to disappear 安全密码已更改 chat item text - - send direct message - 发送私信 - No comment provided by engineer. - server queue info: %1$@ @@ -9936,10 +10387,9 @@ last received msg: %2$@ No comment provided by engineer. - - you are invited to group - 您被邀请加入群组 - No comment provided by engineer. + + you accepted this member + snd group event chat item you are observer diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 3ea46ee364..0e4e383b52 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -2367,8 +2367,8 @@ We will be adding server redundancy to prevent lost messages. 請放置你的密碼於安全的地方,如果你遺失了密碼,將不可能修改你的密碼。 No comment provided by engineer. - - Possibly, certificate fingerprint in server address is incorrect + + Fingerprint in server address does not match certificate. 伺服器地址的憑證指紋可能不正確 server test error @@ -2757,8 +2757,8 @@ We will be adding server redundancy to prevent lost messages. 已傳送的訊息將在設定的時間後被刪除。 No comment provided by engineer. - - Server requires authorization to create queues, check password + + Server requires authorization to create queues, check password. 伺服器需要授權才能建立佇列,請檢查密碼 server test error @@ -4000,7 +4000,7 @@ SimpleX 伺服器並不會看到你的個人檔案。 斜體 No comment provided by engineer. - + join as %@ 以 %@ 身份加入 No comment provided by engineer. @@ -4206,8 +4206,8 @@ SimpleX 伺服器並不會看到你的個人檔案。 pref value - - you are invited to group + + You are invited to group 你被邀請加入至群組 No comment provided by engineer. @@ -4783,7 +4783,7 @@ Available in v5.1 Migrations: - 遷移:%@ + 遷移: No comment provided by engineer. @@ -5130,8 +5130,8 @@ Available in v5.1 儲存歡迎訊息? No comment provided by engineer. - - Server requires authorization to upload, check password + + Server requires authorization to upload, check password. 伺服器需要認證後才能上傳,檢查密碼 server test error @@ -5225,7 +5225,7 @@ SimpleX Lock must be enabled. <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> - <p>你好!</p> + <p>你好!</p> <p><a href="%@">來連接我透過SimpleX Chat</a></p> email text diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 176da2481e..efed487739 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -996,7 +996,11 @@ func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? // case let .contactConnecting(contact): // TODO profile update case let .receivedContactRequest(user, contactRequest): - return (UserContact(contactRequest: contactRequest).id, .contactRequest(user, contactRequest)) + if let userContactLinkId = contactRequest.userContactLinkId_ { + return (UserContact(userContactLinkId: userContactLinkId).id, .contactRequest(user, contactRequest)) + } else { + return nil + } case let .newChatItems(user, chatItems): // Received items are created one at a time if let chatItem = chatItems.first { diff --git a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings index 12d1e01f1d..83d42926d1 100644 --- a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "‐%d nieuwe gebeurtenissen"; +/* notification body */ +"From %d chat(s)" = "Van %d chat(s)"; + /* notification body */ "From: %@" = "Van: %@"; diff --git a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings index 7205b37e7f..cf082a166d 100644 --- a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d новых сообщений"; +/* notification body */ +"From %d chat(s)" = "Из %d чатов"; + /* notification body */ "From: %@" = "От: %@"; diff --git a/apps/ios/SimpleX NSE/tr.lproj/Localizable.strings b/apps/ios/SimpleX NSE/tr.lproj/Localizable.strings index 5ef592ec70..e43538b856 100644 --- a/apps/ios/SimpleX NSE/tr.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/tr.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d yeni olaylar"; + +/* notification body */ +"From %d chat(s)" = "%d 'dan sohbetler"; + +/* notification body */ +"From: %@" = "Şuradan: %@@"; + +/* notification */ +"New events" = "Yeni etkinlikler"; + +/* notification */ +"New messages" = "Yeni mesajlar"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings index ceace71e34..6dd5248aeb 100644 --- a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d нових подій"; +/* notification body */ +"From %d chat(s)" = "З %d чату(ів)"; + /* notification body */ "From: %@" = "Від: %@"; diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 3e901c73eb..4dad9d5d15 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -67,6 +67,7 @@ func apiSendMessages( : SEChatCommand.apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, + scope: chatInfo.groupChatScope(), live: false, ttl: nil, composedMessages: composedMessages @@ -123,7 +124,7 @@ enum SEChatCommand: ChatCmdProtocol { case apiSetEncryptLocalFiles(enable: Bool) case apiGetChats(userId: Int64) case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) - case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) var cmdString: String { switch self { @@ -139,15 +140,29 @@ enum SEChatCommand: ChatCmdProtocol { case let .apiCreateChatItems(noteFolderId, composedMessages): let msgs = encodeJSON(composedMessages) return "/_create *\(noteFolderId) json \(msgs)" - case let .apiSendMessages(type, id, live, ttl, composedMessages): + case let .apiSendMessages(type, id, scope, live, ttl, composedMessages): let msgs = encodeJSON(composedMessages) let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + return "/_send \(ref(type, id, scope: scope)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" } } - func ref(_ type: ChatType, _ id: Int64) -> String { - "\(type.rawValue)\(id)" + func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String { + "\(type.rawValue)\(id)\(scopeRef(scope: scope))" + } + + func scopeRef(scope: GroupChatScope?) -> String { + switch (scope) { + case .none: "" + case let .memberSupport(groupMemberId_): + if let groupMemberId = groupMemberId_ { + "(_support:\(groupMemberId))" + } else { + "(_support)" + } + case .reports: + "(reports, prohibited)" // can't use surrogate Reports scope + } } } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 12a775f85c..5080cf2040 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -390,7 +390,7 @@ enum SharedContent { switch self { case let .image(preview, _): .image(text: comment, image: preview) case let .movie(preview, duration, _): .video(text: comment, image: preview, duration: duration) - case let .url(preview): .link(text: preview.uri.absoluteString + (comment == "" ? "" : "\n" + comment), preview: preview) + case let .url(preview): .link(text: preview.uri + (comment == "" ? "" : "\n" + comment), preview: preview) case .text: .text(comment) case .data: .file(comment) } @@ -464,12 +464,13 @@ fileprivate func getSharedContent(_ ip: NSItemProvider) async -> Result(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil) -> APIResult { - if let d = sendSimpleXCmdStr(cmd.cmdString, ctrl) { +public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil, retryNum: Int32 = 0) -> APIResult { + if let d = sendSimpleXCmdStr(cmd.cmdString, ctrl, retryNum: retryNum) { decodeAPIResult(d) } else { APIResult.error(.invalidJSON(json: nil)) @@ -120,9 +120,9 @@ public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: cha } @inline(__always) -public func sendSimpleXCmdStr(_ cmd: String, _ ctrl: chat_ctrl? = nil) -> Data? { +public func sendSimpleXCmdStr(_ cmd: String, _ ctrl: chat_ctrl? = nil, retryNum: Int32) -> Data? { var c = cmd.cString(using: .utf8)! - return if let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c) { + return if let cjson = chat_send_cmd_retry(ctrl ?? getChatCtrl(), &c, retryNum) { dataFromCString(cjson) } else { nil @@ -186,6 +186,30 @@ struct ParsedServerAddress: Decodable { var parseError: String } +public func parseSanitizeUri(_ s: String, safe: Bool) -> ParsedUri? { + var c = s.cString(using: .utf8)! + if let cjson = chat_parse_uri(&c, safe ? 1 : 0) { + if let d = dataFromCString(cjson) { + do { + return try jsonDecoder.decode(ParsedUri.self, from: d) + } catch { + logger.error("parseSanitizeUri jsonDecoder.decode error: \(error.localizedDescription)") + } + } + } + return nil +} + +public struct ParsedUri: Decodable { + public var uriInfo: UriInfo? + public var parseError: String +} + +public struct UriInfo: Decodable { + public var scheme: String + public var sanitized: String? +} + @inline(__always) public func fromCString(_ c: UnsafeMutablePointer) -> String { let s = String.init(cString: c) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index b8d2361ac8..34ce1cf84f 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -241,8 +241,8 @@ public struct NetCfg: Codable, Equatable { public var smpProxyMode: SMPProxyMode = .always public var smpProxyFallback: SMPProxyFallback = .allowProtected public var smpWebPortServers: SMPWebPortServers = .preset - public var tcpConnectTimeout: Int // microseconds - public var tcpTimeout: Int // microseconds + public var tcpConnectTimeout: NetworkTimeout + public var tcpTimeout: NetworkTimeout public var tcpTimeoutPerKb: Int // microseconds public var rcvConcurrency: Int // pool size public var tcpKeepAlive: KeepAliveOpts? = KeepAliveOpts.defaults @@ -251,16 +251,16 @@ public struct NetCfg: Codable, Equatable { public var logTLSErrors: Bool = false public static let defaults: NetCfg = NetCfg( - tcpConnectTimeout: 25_000_000, - tcpTimeout: 15_000_000, + tcpConnectTimeout: NetworkTimeout(backgroundTimeout: 45_000_000, interactiveTimeout: 15_000_000), + tcpTimeout: NetworkTimeout(backgroundTimeout: 30_000_000, interactiveTimeout: 10_000_000), tcpTimeoutPerKb: 10_000, rcvConcurrency: 12, smpPingInterval: 1200_000_000 ) static let proxyDefaults: NetCfg = NetCfg( - tcpConnectTimeout: 35_000_000, - tcpTimeout: 20_000_000, + tcpConnectTimeout: NetworkTimeout(backgroundTimeout: 60_000_000, interactiveTimeout: 30_000_000), + tcpTimeout: NetworkTimeout(backgroundTimeout: 40_000_000, interactiveTimeout: 20_000_000), tcpTimeoutPerKb: 15_000, rcvConcurrency: 8, smpPingInterval: 1200_000_000 @@ -287,6 +287,11 @@ public struct NetCfg: Codable, Equatable { public var enableKeepAlive: Bool { tcpKeepAlive != nil } } +public struct NetworkTimeout: Codable, Equatable { + public var backgroundTimeout: Int // microseconds + public var interactiveTimeout: Int // microseconds +} + public enum HostMode: String, Codable { case onionViaSocks case onionHost = "onion" @@ -685,6 +690,7 @@ public enum ChatErrorType: Decodable, Hashable { case invalidConnReq case unsupportedConnReq case invalidChatMessage(connection: Connection, message: String) + case connReqMessageProhibited case contactNotReady(contact: Contact) case contactNotActive(contact: Contact) case contactDisabled(contact: Contact) @@ -757,10 +763,13 @@ public enum StoreError: Decodable, Hashable { case userContactLinkNotFound case contactRequestNotFound(contactRequestId: Int64) case contactRequestNotFoundByName(contactName: ContactName) + case invalidContactRequestEntity(contactRequestId: Int64) + case invalidBusinessChatContactRequest case groupNotFound(groupId: Int64) case groupNotFoundByName(groupName: GroupName) case groupMemberNameNotFound(groupId: Int64, groupMemberName: ContactName) case groupMemberNotFound(groupMemberId: Int64) + case groupHostMemberNotFound(groupId: Int64) case groupMemberNotFoundByMemberId(memberId: String) case memberContactGroupMemberNotFound(contactId: Int64) case groupWithoutUser @@ -817,7 +826,7 @@ public enum SQLiteError: Decodable, Hashable { public enum AgentErrorType: Decodable, Hashable { case CMD(cmdErr: CommandErrorType, errContext: String) - case CONN(connErr: ConnectionErrorType) + case CONN(connErr: ConnectionErrorType, errContext: String) case SMP(serverAddress: String, smpErr: ProtocolErrorType) case NTF(ntfErr: ProtocolErrorType) case XFTP(xftpErr: XFTPErrorType) @@ -849,7 +858,7 @@ public enum ConnectionErrorType: Decodable, Hashable { public enum BrokerErrorType: Decodable, Hashable { case RESPONSE(smpErr: String) case UNEXPECTED - case NETWORK + case NETWORK(networkError: NetworkError) case HOST case TRANSPORT(transportErr: ProtocolTransportError) case TIMEOUT @@ -945,6 +954,15 @@ public enum ProtocolCommandError: Decodable, Hashable { case NO_ENTITY } +public enum NetworkError: Decodable, Hashable { + case connectError(connectError: String) + case tLSError(tlsError: String) + case unknownCAError + case failedError + case timeoutError + case subscribeError(subscribeError: String) +} + public enum ProtocolTransportError: Decodable, Hashable { case badBlock case version diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 29ccab7357..77fff873ea 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -27,6 +27,8 @@ let GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED = "appLocalAuthEnabled" public let GROUP_DEFAULT_ALLOW_SHARE_EXTENSION = "allowShareExtension" // replaces DEFAULT_PRIVACY_LINK_PREVIEWS let GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" +public let GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS_SHOW_ALERT = "privacyLinkPreviewsShowAlert" +public let GROUP_DEFAULT_PRIVACY_SANITIZE_LINKS = "privacySanitizeLinks" // This setting is a main one, while having an unused duplicate from the past: DEFAULT_PRIVACY_ACCEPT_IMAGES let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used @@ -41,8 +43,12 @@ let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK = "networkSMPProxyFallback" let GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS = "networkSMPWebPortServers" -let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout" -let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout" +//let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout" +//let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout" +let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND = "networkTCPConnectTimeoutBackground" +let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE = "networkTCPConnectTimeoutInteractive" +let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_BACKGROUND = "networkTCPTimeoutInteractive" +let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_INTERACTIVE = "networkTCPTimeoutBackground" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb" let GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY = "networkRcvConcurrency" let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval" @@ -73,8 +79,10 @@ public func registerGroupDefaults() { GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE: SMPProxyMode.unknown.rawValue, GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK: SMPProxyFallback.allowProtected.rawValue, GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS: SMPWebPortServers.preset.rawValue, - GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout, - GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout, + GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND: NetCfg.defaults.tcpConnectTimeout.backgroundTimeout, + GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE: NetCfg.defaults.tcpConnectTimeout.interactiveTimeout, + GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_BACKGROUND: NetCfg.defaults.tcpTimeout.backgroundTimeout, + GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_INTERACTIVE: NetCfg.defaults.tcpTimeout.interactiveTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb, GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY: NetCfg.defaults.rcvConcurrency, GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL: NetCfg.defaults.smpPingInterval, @@ -89,6 +97,8 @@ public func registerGroupDefaults() { GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED: true, GROUP_DEFAULT_ALLOW_SHARE_EXTENSION: false, GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: true, + GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS_SHOW_ALERT: true, + GROUP_DEFAULT_PRIVACY_SANITIZE_LINKS: false, GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true, GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false, GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, @@ -216,6 +226,8 @@ public let allowShareExtensionGroupDefault = BoolDefault(defaults: groupDefaults public let privacyLinkPreviewsGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS) +public let privacyLinkPreviewsShowAlertGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS_SHOW_ALERT) + // This setting is a main one, while having an unused duplicate from the past: DEFAULT_PRIVACY_ACCEPT_IMAGES public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES) @@ -349,8 +361,14 @@ public func getNetCfg() -> NetCfg { let smpProxyMode = networkSMPProxyModeGroupDefault.get() let smpProxyFallback = networkSMPProxyFallbackGroupDefault.get() let smpWebPortServers = networkSMPWebPortServersDefault.get() - let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) - let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) + let tcpConnectTimeout = NetworkTimeout( + backgroundTimeout: groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND), + interactiveTimeout: groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE) + ) + let tcpTimeout = NetworkTimeout( + backgroundTimeout: groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_BACKGROUND), + interactiveTimeout: groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_INTERACTIVE) + ) let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) let rcvConcurrency = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY) let smpPingInterval = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL) @@ -392,8 +410,10 @@ public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { let socksProxy = networkProxy?.toProxyString() groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) networkSMPWebPortServersDefault.set(cfg.smpWebPortServers) - groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) - groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) + groupDefaults.set(cfg.tcpConnectTimeout.backgroundTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND) + groupDefaults.set(cfg.tcpConnectTimeout.interactiveTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE) + groupDefaults.set(cfg.tcpTimeout.backgroundTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_BACKGROUND) + groupDefaults.set(cfg.tcpTimeout.interactiveTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_INTERACTIVE) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) groupDefaults.set(cfg.rcvConcurrency, forKey: GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY) groupDefaults.set(cfg.smpPingInterval, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 88246465e1..9695e8e911 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -15,6 +15,9 @@ public let CREATE_MEMBER_CONTACT_VERSION = 2 // version to receive reports (MCReport) public let REPORTS_VERSION = 12 +// support group knocking (MsgScope) +public let GROUP_KNOCKING_VERSION = 15 + public let contentModerationPostLink = URL(string: "https://simplex.chat/blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.html#preventing-server-abuse-without-compromising-e2e-encryption")! public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { @@ -29,12 +32,14 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } + public var shortDescr: String? { profile.shortDescr } public var image: String? { get { profile.image } } public var localAlias: String { get { "" } } public var showNtfs: Bool public var sendRcptsContacts: Bool public var sendRcptsSmallGroups: Bool + public var autoAcceptMemberContacts: Bool public var viewPwdHash: UserPwdHash? public var uiThemes: ThemeModeOverrides? @@ -61,7 +66,8 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { activeOrder: 0, showNtfs: true, sendRcptsContacts: true, - sendRcptsSmallGroups: false + sendRcptsSmallGroups: false, + autoAcceptMemberContacts: false ) } @@ -106,12 +112,15 @@ public struct Profile: Codable, NamedChat, Hashable { public init( displayName: String, fullName: String, + shortDescr: String? = nil, image: String? = nil, contactLink: String? = nil, - preferences: Preferences? = nil + preferences: Preferences? = nil, + peerType: ChatPeerType? = nil ) { self.displayName = displayName self.fullName = fullName + self.shortDescr = shortDescr self.image = image self.contactLink = contactLink self.preferences = preferences @@ -119,9 +128,11 @@ public struct Profile: Codable, NamedChat, Hashable { public var displayName: String public var fullName: String + public var shortDescr: String? public var image: String? public var contactLink: String? public var preferences: Preferences? + public var peerType: ChatPeerType? public var localAlias: String { get { "" } } var profileViewName: String { @@ -139,26 +150,32 @@ public struct LocalProfile: Codable, NamedChat, Hashable { profileId: Int64, displayName: String, fullName: String, + shortDescr: String? = nil, image: String? = nil, contactLink: String? = nil, preferences: Preferences? = nil, + peerType: ChatPeerType? = nil, localAlias: String ) { self.profileId = profileId self.displayName = displayName self.fullName = fullName + self.shortDescr = shortDescr self.image = image self.contactLink = contactLink self.preferences = preferences + self.peerType = peerType self.localAlias = localAlias } public var profileId: Int64 public var displayName: String public var fullName: String + public var shortDescr: String? public var image: String? public var contactLink: String? public var preferences: Preferences? + public var peerType: ChatPeerType? public var localAlias: String var profileViewName: String { @@ -176,12 +193,35 @@ public struct LocalProfile: Codable, NamedChat, Hashable { ) } +public enum ChatPeerType: String, Codable { + case human + case bot +} + public func toLocalProfile (_ profileId: Int64, _ profile: Profile, _ localAlias: String) -> LocalProfile { - LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences, localAlias: localAlias) + LocalProfile( + profileId: profileId, + displayName: profile.displayName, + fullName: profile.fullName, + shortDescr: profile.shortDescr, + image: profile.image, + contactLink: profile.contactLink, + preferences: profile.preferences, + peerType: profile.peerType, + localAlias: localAlias + ) } public func fromLocalProfile (_ profile: LocalProfile) -> Profile { - Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences) + Profile( + displayName: profile.displayName, + fullName: profile.fullName, + shortDescr: profile.shortDescr, + image: profile.image, + contactLink: profile.contactLink, + preferences: profile.preferences, + peerType: profile.peerType + ) } public struct UserProfileUpdateSummary: Decodable, Hashable { @@ -201,6 +241,7 @@ public enum ChatType: String, Hashable { public protocol NamedChat { var displayName: String { get } var fullName: String { get } + var shortDescr: String? { get } var image: String? { get } var localAlias: String { get } } @@ -220,20 +261,26 @@ public struct FullPreferences: Decodable, Equatable, Hashable { public var fullDelete: SimplePreference public var reactions: SimplePreference public var voice: SimplePreference + public var files: SimplePreference public var calls: SimplePreference + public var commands: [ChatBotCommand] public init( timedMessages: TimedMessagesPreference, fullDelete: SimplePreference, reactions: SimplePreference, voice: SimplePreference, - calls: SimplePreference + files: SimplePreference, + calls: SimplePreference, + commands: [ChatBotCommand] ) { self.timedMessages = timedMessages self.fullDelete = fullDelete self.reactions = reactions self.voice = voice + self.files = files self.calls = calls + self.commands = commands } public static let sampleData = FullPreferences( @@ -241,7 +288,9 @@ public struct FullPreferences: Decodable, Equatable, Hashable { fullDelete: SimplePreference(allow: .no), reactions: SimplePreference(allow: .yes), voice: SimplePreference(allow: .yes), - calls: SimplePreference(allow: .yes) + files: SimplePreference(allow: .always), + calls: SimplePreference(allow: .yes), + commands: [] ) } @@ -250,20 +299,26 @@ public struct Preferences: Codable, Hashable { public var fullDelete: SimplePreference? public var reactions: SimplePreference? public var voice: SimplePreference? + public var files: SimplePreference? public var calls: SimplePreference? + public var commands: [ChatBotCommand]? public init( timedMessages: TimedMessagesPreference?, fullDelete: SimplePreference?, reactions: SimplePreference?, voice: SimplePreference?, - calls: SimplePreference? + files: SimplePreference?, + calls: SimplePreference?, + commands: [ChatBotCommand]? ) { self.timedMessages = timedMessages self.fullDelete = fullDelete self.reactions = reactions self.voice = voice + self.files = files self.calls = calls + self.commands = commands } func copy( @@ -271,14 +326,18 @@ public struct Preferences: Codable, Hashable { fullDelete: SimplePreference? = nil, reactions: SimplePreference? = nil, voice: SimplePreference? = nil, - calls: SimplePreference? = nil + files: SimplePreference? = nil, + calls: SimplePreference? = nil, + commands: [ChatBotCommand]? = nil ) -> Preferences { Preferences( timedMessages: timedMessages ?? self.timedMessages, fullDelete: fullDelete ?? self.fullDelete, reactions: reactions ?? self.reactions, voice: voice ?? self.voice, - calls: calls ?? self.calls + files: files ?? self.files, + calls: calls ?? self.calls, + commands: commands ?? self.commands ) } @@ -288,6 +347,7 @@ public struct Preferences: Codable, Hashable { case .fullDelete: return copy(fullDelete: SimplePreference(allow: allowed)) case .reactions: return copy(reactions: SimplePreference(allow: allowed)) case .voice: return copy(voice: SimplePreference(allow: allowed)) + case .files: return copy(voice: SimplePreference(allow: allowed)) case .calls: return copy(calls: SimplePreference(allow: allowed)) } } @@ -297,17 +357,72 @@ public struct Preferences: Codable, Hashable { fullDelete: SimplePreference(allow: .no), reactions: SimplePreference(allow: .yes), voice: SimplePreference(allow: .yes), - calls: SimplePreference(allow: .yes) + files: SimplePreference(allow: .always), + calls: SimplePreference(allow: .yes), + commands: nil ) } +public indirect enum ChatBotCommand: Hashable { + case command(keyword: String, label: String, params: String?) + case menu(label: String, commands: [ChatBotCommand]) + + enum CodingKeys: String, CodingKey { + case type + case keyword + case label + case params + case hidden + case commands + } +} + +extension ChatBotCommand: Decodable { + public init(from decoder: Decoder) throws { + let c = try decoder.container(keyedBy: CodingKeys.self) + let type = try c.decode(String.self, forKey: CodingKeys.type) + switch type { + case "command": + let keyword = try c.decode(String.self, forKey: CodingKeys.keyword) + let label = try c.decode(String.self, forKey: CodingKeys.label) + let params = c.contains(CodingKeys.params) ? try c.decode((String?).self, forKey: CodingKeys.params) : nil + self = .command(keyword: keyword, label: label, params: params) + case "menu": + let label = try c.decode(String.self, forKey: CodingKeys.label) + let commands = try c.decode(([ChatBotCommand]).self, forKey: CodingKeys.commands) + self = .menu(label: label, commands: commands) + default: + throw DecodingError.dataCorruptedError(forKey: CodingKeys.type, in: c, debugDescription: "Unsupported command type: \(type)") + } + } +} + +extension ChatBotCommand: Encodable { + public func encode(to encoder: Encoder) throws { + var c = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .command(keyword, label, params): + try c.encode("command", forKey: .type) + try c.encode(keyword, forKey: .keyword) + try c.encode(label, forKey: .label) + if let params { try c.encode(params, forKey: .params) } + case let .menu(label, commands): + try c.encode("menu", forKey: .type) + try c.encode(label, forKey: .label) + try c.encode(commands, forKey: .commands) + } + } +} + public func fullPreferencesToPreferences(_ fullPreferences: FullPreferences) -> Preferences { Preferences( timedMessages: fullPreferences.timedMessages, fullDelete: fullPreferences.fullDelete, reactions: fullPreferences.reactions, voice: fullPreferences.voice, - calls: fullPreferences.calls + files: fullPreferences.files, + calls: fullPreferences.calls, + commands: fullPreferences.commands ) } @@ -317,7 +432,9 @@ public func contactUserPreferencesToPreferences(_ contactUserPreferences: Contac fullDelete: contactUserPreferences.fullDelete.userPreference.preference, reactions: contactUserPreferences.reactions.userPreference.preference, voice: contactUserPreferences.voice.userPreference.preference, - calls: contactUserPreferences.calls.userPreference.preference + files: contactUserPreferences.files.userPreference.preference, + calls: contactUserPreferences.calls.userPreference.preference, + commands: contactUserPreferences.commands ) } @@ -345,6 +462,10 @@ public struct TimedMessagesPreference: Preference, Hashable { public static var ttlValues: [Int?] { [600, 3600, 86400, 7 * 86400, 30 * 86400, 3 * 30 * 86400, nil] } + + public static var profileLevelTTLValues: [Int?] { + [7 * 86400, 30 * 86400, 3 * 30 * 86400, nil] + } } public enum CustomTimeUnit: Hashable { @@ -451,20 +572,26 @@ public struct ContactUserPreferences: Decodable, Hashable { public var fullDelete: ContactUserPreference public var reactions: ContactUserPreference public var voice: ContactUserPreference + public var files: ContactUserPreference public var calls: ContactUserPreference + public var commands: [ChatBotCommand]? public init( timedMessages: ContactUserPreference, fullDelete: ContactUserPreference, reactions: ContactUserPreference, voice: ContactUserPreference, - calls: ContactUserPreference + files: ContactUserPreference, + calls: ContactUserPreference, + commands: [ChatBotCommand]? ) { self.timedMessages = timedMessages self.fullDelete = fullDelete self.reactions = reactions self.voice = voice + self.files = files self.calls = calls + self.commands = commands } public static let sampleData = ContactUserPreferences( @@ -488,11 +615,17 @@ public struct ContactUserPreferences: Decodable, Hashable { userPreference: ContactUserPref.user(preference: SimplePreference(allow: .yes)), contactPreference: SimplePreference(allow: .yes) ), + files: ContactUserPreference( + enabled: FeatureEnabled(forUser: true, forContact: true), + userPreference: ContactUserPref.user(preference: SimplePreference(allow: .yes)), + contactPreference: SimplePreference(allow: .yes) + ), calls: ContactUserPreference( enabled: FeatureEnabled(forUser: true, forContact: true), userPreference: ContactUserPref.user(preference: SimplePreference(allow: .yes)), contactPreference: SimplePreference(allow: .yes) - ) + ), + commands: nil ) } @@ -565,6 +698,7 @@ public enum ChatFeature: String, Decodable, Feature, Hashable { case fullDelete case reactions case voice + case files case calls public var id: Self { self } @@ -591,6 +725,7 @@ public enum ChatFeature: String, Decodable, Feature, Hashable { case .fullDelete: return NSLocalizedString("Delete for everyone", comment: "chat feature") case .reactions: return NSLocalizedString("Message reactions", comment: "chat feature") case .voice: return NSLocalizedString("Voice messages", comment: "chat feature") + case .files: return NSLocalizedString("Files and media", comment: "chat feature") case .calls: return NSLocalizedString("Audio/video calls", comment: "chat feature") } } @@ -601,6 +736,7 @@ public enum ChatFeature: String, Decodable, Feature, Hashable { case .fullDelete: return "trash.slash" case .reactions: return "face.smiling" case .voice: return "mic" + case .files: return "doc" case .calls: return "phone" } } @@ -611,6 +747,7 @@ public enum ChatFeature: String, Decodable, Feature, Hashable { case .fullDelete: return "trash.slash.fill" case .reactions: return "face.smiling.fill" case .voice: return "mic.fill" + case .files: return "doc.fill" case .calls: return "phone.fill" } } @@ -648,6 +785,12 @@ public enum ChatFeature: String, Decodable, Feature, Hashable { case .yes: return "Allow voice messages only if your contact allows them." case .no: return "Prohibit sending voice messages." } + case .files: + switch allowed { + case .always: return "Allow your contacts to send files and media." + case .yes: return "Allow files and media only if your contact allows them." + case .no: return "Prohibit sending files and media." + } case .calls: switch allowed { case .always: return "Allow your contacts to call you." @@ -691,6 +834,14 @@ public enum ChatFeature: String, Decodable, Feature, Hashable { : enabled.forContact ? "Only your contact can send voice messages." : "Voice messages are prohibited in this chat." + case .files: + return enabled.forUser && enabled.forContact + ? "Both you and your contact can send files and media." + : enabled.forUser + ? "Only you can send files and media." + : enabled.forContact + ? "Only your contact can send files and media." + : "Files and media are prohibited in this chat." case .calls: return enabled.forUser && enabled.forContact ? "Both you and your contact can make calls." @@ -924,7 +1075,9 @@ public struct ContactFeaturesAllowed: Equatable, Hashable { public var fullDelete: ContactFeatureAllowed public var reactions: ContactFeatureAllowed public var voice: ContactFeatureAllowed + public var files: ContactFeatureAllowed public var calls: ContactFeatureAllowed + public var commands: [ChatBotCommand]? public init( timedMessagesAllowed: Bool, @@ -932,14 +1085,18 @@ public struct ContactFeaturesAllowed: Equatable, Hashable { fullDelete: ContactFeatureAllowed, reactions: ContactFeatureAllowed, voice: ContactFeatureAllowed, - calls: ContactFeatureAllowed + files: ContactFeatureAllowed, + calls: ContactFeatureAllowed, + commands: [ChatBotCommand]? ) { self.timedMessagesAllowed = timedMessagesAllowed self.timedMessagesTTL = timedMessagesTTL self.fullDelete = fullDelete self.reactions = reactions self.voice = voice + self.files = files self.calls = calls + self.commands = commands } public static let sampleData = ContactFeaturesAllowed( @@ -948,7 +1105,9 @@ public struct ContactFeaturesAllowed: Equatable, Hashable { fullDelete: ContactFeatureAllowed.userDefault(.no), reactions: ContactFeatureAllowed.userDefault(.yes), voice: ContactFeatureAllowed.userDefault(.yes), - calls: ContactFeatureAllowed.userDefault(.yes) + files: ContactFeatureAllowed.userDefault(.always), + calls: ContactFeatureAllowed.userDefault(.yes), + commands: nil ) } @@ -961,7 +1120,9 @@ public func contactUserPrefsToFeaturesAllowed(_ contactUserPreferences: ContactU fullDelete: contactUserPrefToFeatureAllowed(contactUserPreferences.fullDelete), reactions: contactUserPrefToFeatureAllowed(contactUserPreferences.reactions), voice: contactUserPrefToFeatureAllowed(contactUserPreferences.voice), - calls: contactUserPrefToFeatureAllowed(contactUserPreferences.calls) + files: contactUserPrefToFeatureAllowed(contactUserPreferences.files), + calls: contactUserPrefToFeatureAllowed(contactUserPreferences.calls), + commands: contactUserPreferences.commands ) } @@ -983,7 +1144,9 @@ public func contactFeaturesAllowedToPrefs(_ contactFeaturesAllowed: ContactFeatu fullDelete: contactFeatureAllowedToPref(contactFeaturesAllowed.fullDelete), reactions: contactFeatureAllowedToPref(contactFeaturesAllowed.reactions), voice: contactFeatureAllowedToPref(contactFeaturesAllowed.voice), - calls: contactFeatureAllowedToPref(contactFeaturesAllowed.calls) + files: contactFeatureAllowedToPref(contactFeaturesAllowed.files), + calls: contactFeatureAllowedToPref(contactFeaturesAllowed.calls), + commands: contactFeaturesAllowed.commands ) } @@ -1024,6 +1187,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { public var simplexLinks: RoleGroupPreference public var reports: GroupPreference public var history: GroupPreference + public var commands: [ChatBotCommand] public init( timedMessages: TimedMessagesGroupPreference, @@ -1034,7 +1198,8 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { files: RoleGroupPreference, simplexLinks: RoleGroupPreference, reports: GroupPreference, - history: GroupPreference + history: GroupPreference, + commands: [ChatBotCommand] ) { self.timedMessages = timedMessages self.directMessages = directMessages @@ -1045,6 +1210,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { self.simplexLinks = simplexLinks self.reports = reports self.history = history + self.commands = commands } public static let sampleData = FullGroupPreferences( @@ -1056,7 +1222,8 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { files: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil), reports: GroupPreference(enable: .on), - history: GroupPreference(enable: .on) + history: GroupPreference(enable: .on), + commands: [] ) } @@ -1070,6 +1237,7 @@ public struct GroupPreferences: Codable, Hashable { public var simplexLinks: RoleGroupPreference? public var reports: GroupPreference? public var history: GroupPreference? + public var commands: [ChatBotCommand]? public init( timedMessages: TimedMessagesGroupPreference? = nil, @@ -1080,7 +1248,8 @@ public struct GroupPreferences: Codable, Hashable { files: RoleGroupPreference? = nil, simplexLinks: RoleGroupPreference? = nil, reports: GroupPreference? = nil, - history: GroupPreference? = nil + history: GroupPreference? = nil, + commands: [ChatBotCommand]? = nil ) { self.timedMessages = timedMessages self.directMessages = directMessages @@ -1091,6 +1260,7 @@ public struct GroupPreferences: Codable, Hashable { self.simplexLinks = simplexLinks self.reports = reports self.history = history + self.commands = commands } public static let sampleData = GroupPreferences( @@ -1102,7 +1272,8 @@ public struct GroupPreferences: Codable, Hashable { files: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil), reports: GroupPreference(enable: .on), - history: GroupPreference(enable: .on) + history: GroupPreference(enable: .on), + commands: nil ) } @@ -1116,7 +1287,8 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group files: fullPreferences.files, simplexLinks: fullPreferences.simplexLinks, reports: fullPreferences.reports, - history: fullPreferences.history + history: fullPreferences.history, + commands: fullPreferences.commands ) } @@ -1197,7 +1369,7 @@ public enum GroupFeatureEnabled: String, Codable, Identifiable, Hashable { public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case direct(contact: Contact) - case group(groupInfo: GroupInfo) + case group(groupInfo: GroupInfo, groupChatScope: GroupChatScopeInfo?) case local(noteFolder: NoteFolder) case contactRequest(contactRequest: UserContactRequest) case contactConnection(contactConnection: PendingContactConnection) @@ -1211,7 +1383,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.localDisplayName - case let .group(groupInfo): return groupInfo.localDisplayName + case let .group(groupInfo, _): return groupInfo.localDisplayName case .local: return "" case let .contactRequest(contactRequest): return contactRequest.localDisplayName case let .contactConnection(contactConnection): return contactConnection.localDisplayName @@ -1224,7 +1396,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.displayName - case let .group(groupInfo): return groupInfo.displayName + case let .group(groupInfo, _): return groupInfo.displayName case .local: return ChatInfo.privateNotesChatName case let .contactRequest(contactRequest): return contactRequest.displayName case let .contactConnection(contactConnection): return contactConnection.displayName @@ -1237,7 +1409,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.fullName - case let .group(groupInfo): return groupInfo.fullName + case let .group(groupInfo, _): return groupInfo.fullName case .local: return "" case let .contactRequest(contactRequest): return contactRequest.fullName case let .contactConnection(contactConnection): return contactConnection.fullName @@ -1246,11 +1418,22 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var shortDescr: String? { + switch self { + case let .direct(contact): contact.profile.shortDescr + case let .group(groupInfo, _): groupInfo.groupProfile.shortDescr + case .local: nil + case let .contactRequest(contactRequest): contactRequest.profile.shortDescr + case let .contactConnection(contactConnection): nil + case .invalidJSON: nil + } + } + public var image: String? { get { switch self { case let .direct(contact): return contact.image - case let .group(groupInfo): return groupInfo.image + case let .group(groupInfo, _): return groupInfo.image case .local: return nil case let .contactRequest(contactRequest): return contactRequest.image case let .contactConnection(contactConnection): return contactConnection.image @@ -1263,7 +1446,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.localAlias - case let .group(groupInfo): return groupInfo.localAlias + case let .group(groupInfo, _): return groupInfo.localAlias case .local: return "" case let .contactRequest(contactRequest): return contactRequest.localAlias case let .contactConnection(contactConnection): return contactConnection.localAlias @@ -1276,7 +1459,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.id - case let .group(groupInfo): return groupInfo.id + case let .group(groupInfo, _): return groupInfo.id case let .local(noteFolder): return noteFolder.id case let .contactRequest(contactRequest): return contactRequest.id case let .contactConnection(contactConnection): return contactConnection.id @@ -1302,7 +1485,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.apiId - case let .group(groupInfo): return groupInfo.apiId + case let .group(groupInfo, _): return groupInfo.apiId case let .local(noteFolder): return noteFolder.apiId case let .contactRequest(contactRequest): return contactRequest.apiId case let .contactConnection(contactConnection): return contactConnection.apiId @@ -1315,7 +1498,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { get { switch self { case let .direct(contact): return contact.ready - case let .group(groupInfo): return groupInfo.ready + case let .group(groupInfo, _): return groupInfo.ready case let .local(noteFolder): return noteFolder.ready case let .contactRequest(contactRequest): return contactRequest.ready case let .contactConnection(contactConnection): return contactConnection.ready @@ -1323,7 +1506,20 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } } - + + public var sndReady: Bool { + switch self { + case let .direct(contact): contact.sndReady + case let .group(groupInfo, groupScope): + groupInfo.membership.memberActive + && (groupScope != nil || (!groupInfo.membership.memberPending && groupInfo.membership.memberRole != .observer)) + case .local: true + case .contactRequest: false + case .contactConnection: false + case .invalidJSON: false + } + } + public var chatDeleted: Bool { get { switch self { @@ -1333,37 +1529,94 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } - public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + public var nextConnect: Bool { get { switch self { - case let .direct(contact): return contact.userCantSendReason - case let .group(groupInfo): return groupInfo.userCantSendReason - case let .local(noteFolder): return noteFolder.userCantSendReason - case let .contactRequest(contactRequest): return contactRequest.userCantSendReason - case let .contactConnection(contactConnection): return contactConnection.userCantSendReason - case .invalidJSON: return ("can't send messages", nil) + case let .direct(contact): return contact.sendMsgToConnect + case let .group(groupInfo, _): return groupInfo.nextConnectPrepared + default: return false } } } - public var sendMsgEnabled: Bool { + public var nextConnectPrepared: Bool { get { switch self { - case let .direct(contact): return contact.sendMsgEnabled - case let .group(groupInfo): return groupInfo.sendMsgEnabled - case let .local(noteFolder): return noteFolder.sendMsgEnabled - case let .contactRequest(contactRequest): return contactRequest.sendMsgEnabled - case let .contactConnection(contactConnection): return contactConnection.sendMsgEnabled - case .invalidJSON: return false + case let .direct(contact): return contact.nextConnectPrepared + case let .group(groupInfo, _): return groupInfo.nextConnectPrepared + default: return false } } } + public var profileChangeProhibited: Bool { + get { + switch self { + case let .direct(contact): return contact.profileChangeProhibited + case let .group(groupInfo, _): return groupInfo.profileChangeProhibited + default: return false + } + } + } + + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + get { + switch self { + case let .direct(contact): + if contact.sendMsgToConnect { return nil } + if contact.nextAcceptContactRequest { return ("can't send messages", nil) } + if !contact.active { return ("contact deleted", nil) } + if !contact.sndReady { return (contact.preparedContact?.uiConnLinkType == .con ? "request is sent" : "contact not ready", nil) } + if contact.activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) } + if contact.activeConn?.connDisabled ?? true { return ("contact disabled", nil) } + return nil + case let .group(groupInfo, groupChatScope): + if groupInfo.membership.memberActive { + switch(groupChatScope) { + case .none: + if groupInfo.membership.memberPending { return ("reviewed by admins", "Please contact group admin.") } + if groupInfo.membership.memberRole == .observer { return ("you are observer", "Please contact group admin.") } + return nil + case let .some(.memberSupport(groupMember_: .some(supportMember))): + if supportMember.versionRange.maxVersion < GROUP_KNOCKING_VERSION && !supportMember.memberPending { + return ("member has old version", nil) + } + return nil + case .some(.memberSupport(groupMember_: .none)): + return nil + case .some(.reports): + return ("can't send messages", nil) + } + } else if groupInfo.nextConnectPrepared { + return nil + } else { + switch groupInfo.membership.memberStatus { + case .memRejected: return ("request to join rejected", nil) + case .memGroupDeleted: return ("group is deleted", nil) + case .memRemoved: return ("removed from group", nil) + case .memLeft: return ("you left", nil) + default: return ("can't send messages", nil) + } + } + case .local: + return nil + case .contactRequest: + return ("can't send messages", nil) + case .contactConnection: + return ("can't send messages", nil) + case .invalidJSON: + return ("can't send messages", nil) + } + } + } + + public var sendMsgEnabled: Bool { userCantSendReason == nil } + public var incognito: Bool { get { switch self { case let .direct(contact): return contact.contactConnIncognito - case let .group(groupInfo): return groupInfo.membership.memberIncognito + case let .group(groupInfo, _): return groupInfo.membership.memberIncognito case .local: return false case .contactRequest: return false case let .contactConnection(contactConnection): return contactConnection.incognito @@ -1381,14 +1634,14 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { public var contactCard: Bool { switch self { - case let .direct(contact): contact.activeConn == nil && contact.profile.contactLink != nil && contact.active + case let .direct(contact): contact.isContactCard default: false } } - + public var groupInfo: GroupInfo? { switch self { - case let .group(groupInfo): return groupInfo + case let .group(groupInfo, _): return groupInfo default: return nil } } @@ -1403,21 +1656,27 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case .fullDelete: return cups.fullDelete.enabled.forUser case .reactions: return cups.reactions.enabled.forUser case .voice: return cups.voice.enabled.forUser + case .files: return cups.files.enabled.forUser case .calls: return cups.calls.enabled.forUser } - case let .group(groupInfo): + case let .group(groupInfo, _): let prefs = groupInfo.fullGroupPreferences switch feature { case .timedMessages: return prefs.timedMessages.on case .fullDelete: return prefs.fullDelete.on case .reactions: return prefs.reactions.on case .voice: return prefs.voice.on(for: groupInfo.membership) + case .files: return prefs.files.on(for: groupInfo.membership) case .calls: return false } case .local: switch feature { + case .timedMessages: return false + case .fullDelete: return false + case .reactions: return false case .voice: return true - default: return false + case .files: return true + case .calls: return false } default: return false } @@ -1428,7 +1687,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case let .direct(contact): let pref = contact.mergedPreferences.timedMessages return pref.enabled.forUser ? pref.userPreference.preference.ttl : nil - case let .group(groupInfo): + case let .group(groupInfo, _): let pref = groupInfo.fullGroupPreferences.timedMessages return pref.on ? pref.ttl : nil default: @@ -1453,7 +1712,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } else { return .other } - case let .group(groupInfo): + case let .group(groupInfo, _): if !groupInfo.fullGroupPreferences.voice.on(for: groupInfo.membership) { return .groupOwnerCan } else { @@ -1484,11 +1743,18 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { return .other } } - + + public func groupChatScope() -> GroupChatScope? { + switch self { + case let .group(_, groupChatScope): groupChatScope?.toChatScope() + default: nil + } + } + public func ntfsEnabled(chatItem: ChatItem) -> Bool { ntfsEnabled(chatItem.meta.userMention) } - + public func ntfsEnabled(_ userMention: Bool) -> Bool { switch self.chatSettings?.enableNtfs { case .all: true @@ -1500,11 +1766,11 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { public var chatSettings: ChatSettings? { switch self { case let .direct(contact): return contact.chatSettings - case let .group(groupInfo): return groupInfo.chatSettings + case let .group(groupInfo, _): return groupInfo.chatSettings default: return nil } } - + public var nextNtfMode: MsgFilter? { self.chatSettings?.enableNtfs.nextMode(mentions: hasMentions) } @@ -1513,47 +1779,41 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { if case .group = self { true } else { false } } + public var useCommands: Bool { + switch self { + case let .direct(c): c.isBot + case let .group(g, _): (g.groupProfile.groupPreferences?.commands?.count ?? 0) > 0 + default: false + } + } + + public var menuCommands: [ChatBotCommand] { + switch self { + case let .direct(c): c.isBot ? c.profile.preferences?.commands ?? [] : [] + case let .group(g, _): g.groupProfile.groupPreferences?.commands ?? [] + default: [] + } + } + public var chatTags: [Int64]? { switch self { case let .direct(contact): return contact.chatTags - case let .group(groupInfo): return groupInfo.chatTags + case let .group(groupInfo, _): return groupInfo.chatTags default: return nil } } - var createdAt: Date { - switch self { - case let .direct(contact): return contact.createdAt - case let .group(groupInfo): return groupInfo.createdAt - case let .local(noteFolder): return noteFolder.createdAt - case let .contactRequest(contactRequest): return contactRequest.createdAt - case let .contactConnection(contactConnection): return contactConnection.createdAt - case .invalidJSON: return .now - } - } - - public var updatedAt: Date { - switch self { - case let .direct(contact): return contact.updatedAt - case let .group(groupInfo): return groupInfo.updatedAt - case let .local(noteFolder): return noteFolder.updatedAt - case let .contactRequest(contactRequest): return contactRequest.updatedAt - case let .contactConnection(contactConnection): return contactConnection.updatedAt - case .invalidJSON: return .now - } - } - public var chatTs: Date { switch self { case let .direct(contact): return contact.chatTs ?? contact.updatedAt - case let .group(groupInfo): return groupInfo.chatTs ?? groupInfo.updatedAt + case let .group(groupInfo, _): return groupInfo.chatTs ?? groupInfo.updatedAt case let .local(noteFolder): return noteFolder.chatTs case let .contactRequest(contactRequest): return contactRequest.updatedAt case let .contactConnection(contactConnection): return contactConnection.updatedAt case .invalidJSON: return .now } } - + public func ttl(_ globalTTL: ChatItemTTL) -> ChatTTL { switch self { case let .direct(contact): @@ -1562,7 +1822,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } else { ChatTTL.userDefault(globalTTL) } - case let .group(groupInfo): + case let .group(groupInfo, _): return if let ciTTL = groupInfo.chatItemTTL { ChatTTL.chat(ChatItemTTL(ciTTL)) } else { @@ -1582,7 +1842,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { public static var sampleData: ChatInfo.SampleData = SampleData( direct: ChatInfo.direct(contact: Contact.sampleData), - group: ChatInfo.group(groupInfo: GroupInfo.sampleData), + group: ChatInfo.group(groupInfo: GroupInfo.sampleData, groupChatScope: nil), local: ChatInfo.local(noteFolder: NoteFolder.sampleData), contactRequest: ChatInfo.contactRequest(contactRequest: UserContactRequest.sampleData), contactConnection: ChatInfo.contactConnection(contactConnection: PendingContactConnection.getSampleData()) @@ -1601,7 +1861,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { self.chatItems = chatItems self.chatStats = chatStats } - + public static func invalidJSON(_ json: Data?) -> ChatData { ChatData( chatInfo: .invalidJSON(json: json), @@ -1612,7 +1872,13 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { } public struct ChatStats: Decodable, Hashable { - public init(unreadCount: Int = 0, unreadMentions: Int = 0, reportsCount: Int = 0, minUnreadItemId: Int64 = 0, unreadChat: Bool = false) { + public init( + unreadCount: Int = 0, + unreadMentions: Int = 0, + reportsCount: Int = 0, + minUnreadItemId: Int64 = 0, + unreadChat: Bool = false + ) { self.unreadCount = unreadCount self.unreadMentions = unreadMentions self.reportsCount = reportsCount @@ -1629,6 +1895,41 @@ public struct ChatStats: Decodable, Hashable { public var unreadChat: Bool = false } +public enum GroupChatScope: Decodable { + case memberSupport(groupMemberId_: Int64?) + case reports // surrogate scope used for matching new items to opened Reports "chat scope" in UI, this type is not present in backend +} + +public func sameChatScope(_ scope1: GroupChatScope, _ scope2: GroupChatScope) -> Bool { + return switch (scope1, scope2) { + case let (.memberSupport(groupMemberId1_), .memberSupport(groupMemberId2_)): + groupMemberId1_ == groupMemberId2_ + case (.reports, .reports): + true + case (.reports, .memberSupport): + false + case (.memberSupport(groupMemberId_: let groupMemberId_), .reports): + false + } +} + +public enum GroupChatScopeInfo: Decodable, Hashable { + case memberSupport(groupMember_: GroupMember?) + case reports // surrogate scope used for matching new items to opened Reports "chat scope" in UI, this type is not present in backend + + public func toChatScope() -> GroupChatScope { + return switch self { + case let .memberSupport(groupMember_): + if let groupMember = groupMember_ { + .memberSupport(groupMemberId_: groupMember.groupMemberId) + } else { + .memberSupport(groupMemberId_: nil) + } + case .reports: .reports + } + } +} + public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var contactId: Int64 var localDisplayName: ContactName @@ -1643,31 +1944,33 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { var createdAt: Date var updatedAt: Date var chatTs: Date? - var contactGroupMemberId: Int64? + public var preparedContact: PreparedContact? + public var contactRequestId: Int64? + public var contactGroupMemberId: Int64? var contactGrpInvSent: Bool + public var groupDirectInv: GroupDirectInvitation? public var chatTags: [Int64] public var chatItemTTL: Int64? public var uiThemes: ThemeModeOverrides? public var chatDeleted: Bool - + public var id: ChatId { get { "@\(contactId)" } } public var apiId: Int64 { get { contactId } } public var ready: Bool { get { activeConn?.connStatus == .ready } } public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } public var active: Bool { get { contactStatus == .active } } - public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { - // TODO [short links] this will have additional statuses for pending contact requests before they are accepted - if nextSendGrpInv { return nil } - if !active { return ("contact deleted", nil) } - if !sndReady { return ("contact not ready", nil) } - if activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) } - if activeConn?.connDisabled ?? true { return ("contact disabled", nil) } - return nil - } - public var sendMsgEnabled: Bool { userCantSendReason == nil } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } + public var nextConnectPrepared: Bool { active && preparedContact != nil && (activeConn == nil || activeConn?.connStatus == .prepared) } + public var profileChangeProhibited: Bool { activeConn != nil } + public var nextAcceptContactRequest: Bool { + active && + (contactRequestId != nil || groupDirectInv != nil) && + (activeConn == nil || activeConn?.connStatus == .new || activeConn?.connStatus == .prepared) + } + public var sendMsgToConnect: Bool { nextSendGrpInv || nextConnectPrepared } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } + public var shortDescr: String? { profile.shortDescr } public var image: String? { get { profile.image } } public var contactLink: String? { get { profile.contactLink } } public var localAlias: String { profile.localAlias } @@ -1681,16 +1984,30 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { } } + public var isContactCard: Bool { + (activeConn == nil || activeConn?.connStatus == .prepared) && profile.contactLink != nil && active && preparedContact == nil && contactRequestId == nil + } + + @inline(__always) + public var isBot: Bool { + profile.peerType == .bot + } + public var contactConnIncognito: Bool { activeConn?.customUserProfileId != nil } + public var chatIconName: String { + isBot ? "cube.fill" : "person.crop.circle.fill" + } + public func allowsFeature(_ feature: ChatFeature) -> Bool { switch feature { case .timedMessages: return mergedPreferences.timedMessages.contactPreference.allow != .no case .fullDelete: return mergedPreferences.fullDelete.contactPreference.allow != .no case .reactions: return mergedPreferences.reactions.contactPreference.allow != .no case .voice: return mergedPreferences.voice.contactPreference.allow != .no + case .files: return mergedPreferences.files.contactPreference.allow != .no case .calls: return mergedPreferences.calls.contactPreference.allow != .no } } @@ -1701,6 +2018,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { case .fullDelete: return mergedPreferences.fullDelete.userPreference.preference.allow != .no case .reactions: return mergedPreferences.reactions.userPreference.preference.allow != .no case .voice: return mergedPreferences.voice.userPreference.preference.allow != .no + case .files: return mergedPreferences.files.userPreference.preference.allow != .no case .calls: return mergedPreferences.calls.userPreference.preference.allow != .no } } @@ -1723,6 +2041,36 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { ) } +public struct PreparedContact: Decodable, Hashable { + public var connLinkToConnect: CreatedConnLink + public var uiConnLinkType: ConnectionMode +} + +public struct GroupDirectInvitation: Decodable, Hashable { + public var groupDirectInvLink: String + public var fromGroupId_: Int64? + public var fromGroupMemberId_: Int64? + public var fromGroupMemberConnId_: Int64? + public var groupDirectInvStartedConnection: Bool + + public var memberRemoved: Bool { + fromGroupId_ == nil || fromGroupMemberId_ == nil || fromGroupMemberConnId_ == nil + } + + public static let sampleData = GroupDirectInvitation( + groupDirectInvLink: "simplex_link", + fromGroupId_: 1, + fromGroupMemberId_: 1, + fromGroupMemberConnId_: 1, + groupDirectInvStartedConnection: false + ) +} + +public enum ConnectionMode: String, Decodable, Hashable { + case inv + case con +} + public enum ContactStatus: String, Decodable, Hashable { case active = "active" case deleted = "deleted" @@ -1747,7 +2095,7 @@ public struct Connection: Decodable, Hashable { public var connId: Int64 public var agentConnId: String public var peerChatVRange: VersionRange - var connStatus: ConnStatus + public var connStatus: ConnStatus public var connLevel: Int public var viaGroupLink: Bool public var customUserProfileId: Int64? @@ -1822,10 +2170,6 @@ public struct UserContact: Decodable, Hashable { self.userContactLinkId = userContactLinkId } - public init(contactRequest: UserContactRequest) { - self.userContactLinkId = contactRequest.userContactLinkId - } - public var id: String { "@>\(userContactLinkId)" } @@ -1833,26 +2177,25 @@ public struct UserContact: Decodable, Hashable { public struct UserContactRequest: Decodable, NamedChat, Hashable { var contactRequestId: Int64 - public var userContactLinkId: Int64 + public var userContactLinkId_: Int64? public var cReqChatVRange: VersionRange var localDisplayName: ContactName var profile: Profile var createdAt: Date public var updatedAt: Date - public var id: ChatId { get { "<@\(contactRequestId)" } } + public var id: ChatId { get { contactRequestChatId(contactRequestId) } } public var apiId: Int64 { get { contactRequestId } } var ready: Bool { get { true } } - public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } - public var sendMsgEnabled: Bool { get { false } } public var displayName: String { get { profile.displayName } } + public var shortDescr: String? { profile.shortDescr } public var fullName: String { get { profile.fullName } } public var image: String? { get { profile.image } } public var localAlias: String { "" } public static let sampleData = UserContactRequest( contactRequestId: 1, - userContactLinkId: 1, + userContactLinkId_: 1, cReqChatVRange: VersionRange(1, 1), localDisplayName: "alice", profile: Profile.sampleData, @@ -1861,6 +2204,10 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable { ) } +public func contactRequestChatId(_ contactRequestId: Int64) -> ChatId { + return "<@\(contactRequestId)" +} + public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var pccConnId: Int64 var pccAgentConnId: String @@ -1876,8 +2223,6 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var id: ChatId { get { ":\(pccConnId)" } } public var apiId: Int64 { get { pccConnId } } var ready: Bool { get { false } } - public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } - public var sendMsgEnabled: Bool { get { false } } var localDisplayName: String { get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) } } @@ -1896,6 +2241,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { } } public var fullName: String { get { "" } } + public var shortDescr: String? { nil } public var image: String? { get { nil } } public var initiated: Bool { get { (pccConnStatus.initiated ?? false) && !viaContactUri } } @@ -2001,27 +2347,18 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { var createdAt: Date var updatedAt: Date var chatTs: Date? + public var preparedGroup: PreparedGroup? public var uiThemes: ThemeModeOverrides? + public var membersRequireAttention: Int public var id: ChatId { get { "#\(groupId)" } } public var apiId: Int64 { get { groupId } } public var ready: Bool { get { true } } - public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { - return if membership.memberActive { - membership.memberRole == .observer ? ("you are observer", "Please contact group admin.") : nil - } else { - switch membership.memberStatus { - case .memRejected: ("request to join rejected", nil) - case .memGroupDeleted: ("group is deleted", nil) - case .memRemoved: ("removed from group", nil) - case .memLeft: ("you left", nil) - default: ("can't send messages", nil) - } - } - } - public var sendMsgEnabled: Bool { userCantSendReason == nil } + public var nextConnectPrepared: Bool { if let preparedGroup { !preparedGroup.connLinkStartedConnection } else { false } } + public var profileChangeProhibited: Bool { preparedGroup?.connLinkPreparedConnection ?? false } public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } + public var shortDescr: String? { groupProfile.shortDescr } public var image: String? { get { groupProfile.image } } public var chatTags: [Int64] public var chatItemTTL: Int64? @@ -2032,13 +2369,25 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { } public var canDelete: Bool { - return membership.memberRole == .owner || !membership.memberCurrent + return membership.memberRole == .owner || !membership.memberCurrentOrPending } public var canAddMembers: Bool { return membership.memberRole >= .admin && membership.memberActive } + public var canModerate: Bool { + return membership.memberRole >= .moderator && membership.memberActive + } + + public var chatIconName: String { + switch businessChat?.chatType { + case .none: "person.2.circle.fill" + case .business: "briefcase.circle.fill" + case .customer: "person.crop.circle.fill" + } + } + public static let sampleData = GroupInfo( groupId: 1, localDisplayName: "team", @@ -2048,38 +2397,100 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { chatSettings: ChatSettings.defaults, createdAt: .now, updatedAt: .now, + membersRequireAttention: 0, chatTags: [], localAlias: "" ) } +public struct PreparedGroup: Decodable, Hashable { + public var connLinkToConnect: CreatedConnLink + public var connLinkPreparedConnection: Bool + public var connLinkStartedConnection: Bool +} + public struct GroupRef: Decodable, Hashable { public var groupId: Int64 var localDisplayName: GroupName } public struct GroupProfile: Codable, NamedChat, Hashable { - public init(displayName: String, fullName: String, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil) { + public init( + displayName: String, + fullName: String, + shortDescr: String? = nil, + description: String? = nil, + image: String? = nil, + groupPreferences: GroupPreferences? = nil, + memberAdmission: GroupMemberAdmission? = nil + ) { self.displayName = displayName self.fullName = fullName + self.shortDescr = shortDescr self.description = description self.image = image self.groupPreferences = groupPreferences + self.memberAdmission = memberAdmission } public var displayName: String public var fullName: String + public var shortDescr: String? public var description: String? public var image: String? public var groupPreferences: GroupPreferences? + public var memberAdmission: GroupMemberAdmission? public var localAlias: String { "" } + public var memberAdmission_: GroupMemberAdmission { + get { self.memberAdmission ?? GroupMemberAdmission() } + set { memberAdmission = newValue } + } + public static let sampleData = GroupProfile( displayName: "team", fullName: "My Team" ) } +public struct GroupMemberAdmission: Codable, Hashable { + public var review: MemberCriteria? + + public init( + review: MemberCriteria? = nil + ) { + self.review = review + } + + public static let sampleData = GroupMemberAdmission( + review: .all + ) +} + +public enum MemberCriteria: String, Codable, Identifiable, Hashable { + case all + + public static var values: [MemberCriteria] { [.all] } + + public var id: Self { self } + + public var text: String { + switch self { + case .all: return NSLocalizedString("all", comment: "member criteria value") + } + } +} + +public struct ContactShortLinkData: Codable, Hashable { + public var profile: Profile + public var message: MsgContent? + public var business: Bool +} + +public struct GroupShortLinkData: Codable, Hashable { + public var groupProfile: GroupProfile +} + public struct BusinessChatInfo: Decodable, Hashable { public var chatType: BusinessChatType public var businessId: String @@ -2106,6 +2517,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { public var memberContactId: Int64? public var memberContactProfileId: Int64 public var activeConn: Connection? + public var supportChat: GroupSupportChat? public var memberChatVRange: VersionRange public var id: String { "#\(groupId) @\(groupMemberId)" } @@ -2121,7 +2533,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { get { let p = memberProfile let name = p.localAlias == "" ? p.displayName : p.localAlias - return pastMember(name) + return unknownMember(name) } } public var fullName: String { get { memberProfile.fullName } } @@ -2148,23 +2560,29 @@ public struct GroupMember: Identifiable, Decodable, Hashable { ? p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)") : p.localAlias ) - return pastMember(name) + return unknownMember(name) } } - private func pastMember(_ name: String) -> String { + private func unknownMember(_ name: String) -> String { memberStatus == .memUnknown - ? String.localizedStringWithFormat(NSLocalizedString("Past member %@", comment: "past/unknown group member"), name) + ? ( + memberId.hasPrefix(name) + // unknown member was created using memberId for name + ? String.localizedStringWithFormat(NSLocalizedString("Member %@", comment: "past/unknown group member"), name) + // unknown member was created with name + : name + ) : name } - + public var localAliasAndFullName: String { get { let p = memberProfile let fullName = p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)") let name = p.localAlias == "" ? fullName : "\(p.localAlias) (\(fullName))" - return pastMember(name) + return unknownMember(name) } } @@ -2177,6 +2595,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { case .memUnknown: return false case .memInvited: return false case .memPendingApproval: return true + case .memPendingReview: return true case .memIntroduced: return false case .memIntroInvited: return false case .memAccepted: return false @@ -2196,6 +2615,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { case .memUnknown: return false case .memInvited: return false case .memPendingApproval: return false + case .memPendingReview: return false case .memIntroduced: return true case .memIntroInvited: return true case .memAccepted: return true @@ -2206,6 +2626,18 @@ public struct GroupMember: Identifiable, Decodable, Hashable { } } + public var memberPending: Bool { + switch memberStatus { + case .memPendingApproval: return true + case .memPendingReview: return true + default: return false + } + } + + public var memberCurrentOrPending: Bool { + memberCurrent || memberPending + } + public func canBeRemoved(groupInfo: GroupInfo) -> Bool { let userRole = groupInfo.membership.memberRole return memberStatus != .memRemoved && memberStatus != .memLeft @@ -2213,21 +2645,31 @@ public struct GroupMember: Identifiable, Decodable, Hashable { } public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? { - if !canBeRemoved(groupInfo: groupInfo) { return nil } + if !canBeRemoved(groupInfo: groupInfo) || memberPending { return nil } let userRole = groupInfo.membership.memberRole return GroupMemberRole.supportedRoles.filter { $0 <= userRole } } public func canBlockForAll(groupInfo: GroupInfo) -> Bool { let userRole = groupInfo.membership.memberRole - return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .admin - && userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberActive + return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .moderator + && userRole >= .moderator && userRole >= memberRole && groupInfo.membership.memberActive + && !memberPending } - + public var canReceiveReports: Bool { memberRole >= .moderator && versionRange.maxVersion >= REPORTS_VERSION } + public var supportChatNotRead: Bool { + if let supportChat = supportChat, + supportChat.memberAttention > 0 || supportChat.mentions > 0 || supportChat.unread > 0 { + true + } else { + false + } + } + public var versionRange: VersionRange { if let activeConn { activeConn.peerChatVRange @@ -2235,7 +2677,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable { memberChatVRange } } - + public var memberIncognito: Bool { memberProfile.profileId != memberContactProfileId } @@ -2259,6 +2701,13 @@ public struct GroupMember: Identifiable, Decodable, Hashable { ) } +public struct GroupSupportChat: Codable, Hashable { + public var chatTs: Date + public var unread: Int + public var memberAttention: Int + public var mentions: Int +} + public struct GroupMemberSettings: Codable, Hashable { public var showMessages: Bool } @@ -2283,8 +2732,8 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Cod public var id: Self { self } - public static var supportedRoles: [GroupMemberRole] = [.observer, .member, .admin, .owner] - + public static var supportedRoles: [GroupMemberRole] = [.observer, .member, .moderator, .admin, .owner] + public var text: String { switch self { case .observer: return NSLocalizedString("observer", comment: "member role") @@ -2328,6 +2777,7 @@ public enum GroupMemberStatus: String, Decodable, Hashable { case memUnknown = "unknown" case memInvited = "invited" case memPendingApproval = "pending_approval" + case memPendingReview = "pending_review" case memIntroduced = "introduced" case memIntroInvited = "intro-inv" case memAccepted = "accepted" @@ -2345,6 +2795,7 @@ public enum GroupMemberStatus: String, Decodable, Hashable { case .memUnknown: return "unknown status" case .memInvited: return "invited" case .memPendingApproval: return "pending approval" + case .memPendingReview: return "pending review" case .memIntroduced: return "connecting (introduced)" case .memIntroInvited: return "connecting (introduction invitation)" case .memAccepted: return "connecting (accepted)" @@ -2364,6 +2815,7 @@ public enum GroupMemberStatus: String, Decodable, Hashable { case .memUnknown: return "unknown" case .memInvited: return "invited" case .memPendingApproval: return "pending" + case .memPendingReview: return "review" case .memIntroduced: return "connecting" case .memIntroInvited: return "connecting" case .memAccepted: return "connecting" @@ -2386,10 +2838,9 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "*\(noteFolderId)" } } public var apiId: Int64 { get { noteFolderId } } public var ready: Bool { get { true } } - public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { nil } - public var sendMsgEnabled: Bool { get { true } } public var displayName: String { get { ChatInfo.privateNotesChatName } } public var fullName: String { get { "" } } + public var shortDescr: String? { nil } public var image: String? { get { nil } } public var localAlias: String { get { "" } } @@ -2439,7 +2890,7 @@ public enum ConnectionEntity: Decodable, Hashable { nil } } - + // public var localDisplayName: String? { // switch self { // case let .rcvDirectMsgConnection(conn, contact): @@ -2480,7 +2931,7 @@ public struct NtfMsgInfo: Decodable, Hashable { public enum RcvNtfMsgInfo: Decodable { case info(ntfMsgInfo: NtfMsgInfo?) case error(ntfMsgError: AgentErrorType) - + @inline(__always) public var noMsg: Bool { if case let .info(msg) = self { msg == nil } else { true } @@ -2540,7 +2991,7 @@ public struct CIMentionMember: Decodable, Hashable { public struct CIMention: Decodable, Hashable { public var memberId: String public var memberRef: CIMentionMember? - + public init(groupMember m: GroupMember) { self.memberId = m.memberId self.memberRef = CIMentionMember( @@ -2647,12 +3098,15 @@ public struct ChatItem: Identifiable, Decodable, Hashable { case .userDeleted: nil case .groupDeleted: nil case .memberCreatedContact: nil + case .newMemberPendingReview: nil default: .rcvGroupEvent } case let .sndGroupEvent(event): switch event { case .userRole: nil case .userLeft: nil + case .memberAccepted: nil + case .userPendingReview: nil default: .sndGroupEvent } default: @@ -2680,11 +3134,14 @@ public struct ChatItem: Identifiable, Decodable, Hashable { switch rcvDirectEvent { case .contactDeleted: return false case .profileUpdated: return false + case .groupInvLinkReceived: return true } case .rcvGroupEvent(rcvGroupEvent: let rcvGroupEvent): switch rcvGroupEvent { case .groupUpdated: return false case .memberConnected: return false + case .memberAccepted: return false + case .userAccepted: return false case .memberRole: return false case .memberBlocked: return false case .userRole: return true @@ -2696,6 +3153,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { case .invitedViaGroupLink: return false case .memberCreatedContact: return false case .memberProfileUpdated: return false + case .newMemberPendingReview: return true } case .sndGroupEvent: return false case .rcvConnEvent: return false @@ -2715,6 +3173,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { case .rcvDirectE2EEInfo: return false case .sndGroupE2EEInfo: return false case .rcvGroupE2EEInfo: return false + case .chatBanner: return false case .invalidJSON: return false } } @@ -2764,14 +3223,14 @@ public struct ChatItem: Identifiable, Decodable, Hashable { public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember?)? { switch (chatInfo, chatDir) { - case let (.group(groupInfo), .groupRcv(groupMember)): + case let (.group(groupInfo, _), .groupRcv(groupMember)): let m = groupInfo.membership - return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil + return m.memberRole >= .moderator && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil ? (groupInfo, groupMember) : nil - case let (.group(groupInfo), .groupSnd): + case let (.group(groupInfo, _), .groupSnd): let m = groupInfo.membership - return m.memberRole >= .admin ? (groupInfo, nil) : nil + return m.memberRole >= .moderator ? (groupInfo, nil) : nil default: return nil } } @@ -2782,10 +3241,11 @@ public struct ChatItem: Identifiable, Decodable, Hashable { case .rcvDirectE2EEInfo: return false case .sndGroupE2EEInfo: return false case .rcvGroupE2EEInfo: return false + case .chatBanner: return false default: return true } } - + public var isReport: Bool { switch content { case let .sndMsgContent(msgContent), let .rcvMsgContent(msgContent): @@ -2885,14 +3345,14 @@ public struct ChatItem: Identifiable, Decodable, Hashable { file: nil ) } - + public static func getReportSample(text: String, reason: ReportReason, item: ChatItem, sender: GroupMember? = nil) -> ChatItem { let chatDir = if let sender = sender { CIDirection.groupRcv(groupMember: sender) } else { CIDirection.groupSnd } - + return ChatItem( chatDir: chatDir, meta: CIMeta( @@ -2907,7 +3367,8 @@ public struct ChatItem: Identifiable, Decodable, Hashable { itemLive: false, userMention: false, deletable: false, - editable: false + editable: false, + showGroupAsSender: false ), content: .sndMsgContent(msgContent: .report(text: text, reason: reason)), quotedItem: CIQuote.getSample(item.id, item.meta.createdAt, item.text, chatDir: item.chatDir), @@ -2930,7 +3391,8 @@ public struct ChatItem: Identifiable, Decodable, Hashable { itemLive: false, userMention: false, deletable: false, - editable: false + editable: false, + showGroupAsSender: false ), content: .rcvDeleted(deleteMode: .cidmBroadcast), quotedItem: nil, @@ -2953,7 +3415,8 @@ public struct ChatItem: Identifiable, Decodable, Hashable { itemLive: true, userMention: false, deletable: false, - editable: false + editable: false, + showGroupAsSender: false ), content: .sndMsgContent(msgContent: .text("")), quotedItem: nil, @@ -3003,7 +3466,7 @@ public enum CIDirection: Decodable, Hashable { } } } - + public func sameDirection(_ dir: CIDirection) -> Bool { switch (self, dir) { case let (.groupRcv(m1), .groupRcv(m2)): m1.groupMemberId == m2.groupMemberId @@ -3028,6 +3491,7 @@ public struct CIMeta: Decodable, Hashable { public var userMention: Bool public var deletable: Bool public var editable: Bool + public var showGroupAsSender: Bool public var timestampText: Text { Text(formatTimestampMeta(itemTs)) } public var recent: Bool { updatedAt + 10 > .now } @@ -3052,7 +3516,8 @@ public struct CIMeta: Decodable, Hashable { itemLive: itemLive, userMention: false, deletable: deletable, - editable: editable + editable: editable, + showGroupAsSender: false ) } @@ -3069,7 +3534,8 @@ public struct CIMeta: Decodable, Hashable { itemLive: false, userMention: false, deletable: false, - editable: false + editable: false, + showGroupAsSender: false ) } } @@ -3136,7 +3602,7 @@ public enum CIStatus: Decodable, Hashable { case .invalid: return "invalid" } } - + public var sent: Bool { switch self { case .sndNew: true @@ -3151,6 +3617,21 @@ public enum CIStatus: Decodable, Hashable { } } + // as in corresponds to SENT response from agent, opposed to `sent` which means snd status + public var isSent: Bool { + switch self { + case .sndNew: false + case .sndSent: true + case .sndRcvd: false + case .sndErrorAuth: true + case .sndError: true + case .sndWarning: true + case .rcvNew: false + case .rcvRead: false + case .invalid: false + } + } + public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { case .sndNew: nil @@ -3204,6 +3685,17 @@ public enum CIStatus: Decodable, Hashable { } } +public func shouldKeepOldSndCIStatus(oldStatus: CIStatus, newStatus: CIStatus) -> Bool { + switch (oldStatus, newStatus) { + case (.sndRcvd, let new) where !new.isSndRcvd: + return true + case (let old, .sndNew) where old.isSent: + return true + default: + return false + } +} + public enum SndError: Decodable, Hashable { case auth case quota @@ -3410,6 +3902,7 @@ public enum CIContent: Decodable, ItemContent, Hashable { case rcvDirectE2EEInfo(e2eeInfo: E2EEInfo) case sndGroupE2EEInfo(e2eeInfo: E2EEInfo) case rcvGroupE2EEInfo(e2eeInfo: E2EEInfo) + case chatBanner case invalidJSON(json: Data?) public var text: String { @@ -3445,13 +3938,14 @@ public enum CIContent: Decodable, ItemContent, Hashable { case let .rcvDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) case .sndGroupE2EEInfo: return e2eeInfoNoPQStr case .rcvGroupE2EEInfo: return e2eeInfoNoPQStr + case .chatBanner: return "" case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item") } } } private func directE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String { - e2eeInfo.pqEnabled + e2eeInfo.pqEnabled == true ? NSLocalizedString("This chat is protected by quantum resistant end-to-end encryption.", comment: "E2EE info chat item") : e2eeInfoNoPQStr } @@ -3499,6 +3993,14 @@ public enum CIContent: Decodable, ItemContent, Hashable { } } + public var hasMsgContent: Bool { + if let mc = msgContent { + !mc.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } else { + false + } + } + public var showMemberName: Bool { switch self { case .rcvMsgContent: return true @@ -3915,7 +4417,7 @@ public enum FileError: Decodable, Equatable, Hashable { case let .other(fileError): String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "file error text"), fileError) } } - + public var moreInfoButton: (label: LocalizedStringKey, link: URL)? { switch self { case .blocked: ("How it works", contentModerationPostLink) @@ -3932,6 +4434,7 @@ public enum MsgContent: Equatable, Hashable { case voice(text: String, duration: Int) case file(String) case report(text: String, reason: ReportReason) + case chat(text: String, chatLink: MsgChatLink) // TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift case unknown(type: String, text: String) @@ -3944,6 +4447,7 @@ public enum MsgContent: Equatable, Hashable { case let .voice(text, _): return text case let .file(text): return text case let .report(text, _): return text + case let .chat(text, _): return text case let .unknown(_, text): return text } } @@ -3993,6 +4497,7 @@ public enum MsgContent: Equatable, Hashable { } } + @inline(__always) public var cmdString: String { "json \(encodeJSON(self))" } @@ -4004,6 +4509,7 @@ public enum MsgContent: Equatable, Hashable { case image case duration case reason + case chatLink } public static func == (lhs: MsgContent, rhs: MsgContent) -> Bool { @@ -4015,6 +4521,7 @@ public enum MsgContent: Equatable, Hashable { case let (.voice(lt, ld), .voice(rt, rd)): return lt == rt && ld == rd case let (.file(lf), .file(rf)): return lf == rf case let (.report(lt, lr), .report(rt, rr)): return lt == rt && lr == rr + case let (.chat(lt, ll), .chat(rt, rl)): return lt == rt && ll == rl case let (.unknown(lType, lt), .unknown(rType, rt)): return lType == rType && lt == rt default: return false } @@ -4054,6 +4561,10 @@ extension MsgContent: Decodable { let text = try container.decode(String.self, forKey: CodingKeys.text) let reason = try container.decode(ReportReason.self, forKey: CodingKeys.reason) self = .report(text: text, reason: reason) + case "chat": + let text = try container.decode(String.self, forKey: CodingKeys.text) + let chatLink = try container.decode(MsgChatLink.self, forKey: CodingKeys.chatLink) + self = .chat(text: text, chatLink: chatLink) default: let text = try? container.decode(String.self, forKey: CodingKeys.text) self = .unknown(type: type, text: text ?? "unknown message format") @@ -4095,6 +4606,10 @@ extension MsgContent: Encodable { try container.encode("report", forKey: .type) try container.encode(text, forKey: .text) try container.encode(reason, forKey: .reason) + case let .chat(text, chatLink): + try container.encode("chat", forKey: .type) + try container.encode(text, forKey: .text) + try container.encode(chatLink, forKey: .chatLink) // TODO use original JSON and type case let .unknown(_, text): try container.encode("text", forKey: .type) @@ -4103,10 +4618,31 @@ extension MsgContent: Encodable { } } +public enum MsgContentTag: String { + case text + case link + case image + case video + case voice + case file + case report +} + +public enum MsgChatLink: Codable, Equatable, Hashable { + case contact(connLink: String, profile: Profile, business: Bool) + case invitation(invLink: String, profile: Profile) + case group(connLink: String, groupProfile: GroupProfile) +} + public struct FormattedText: Decodable, Hashable { public var text: String public var format: Format? + public init(text: String, format: Format? = nil) { + self.text = text + self.format = format + } + public static func plain(_ text: String) -> [FormattedText] { text.isEmpty ? [] @@ -4116,6 +4652,14 @@ public struct FormattedText: Decodable, Hashable { public var isSecret: Bool { if case .secret = format { true } else { false } } + + public var linkUri: String? { + switch format { + case .uri: text + case let .hyperLink(_, linkUri): linkUri + default: nil + } + } } public enum Format: Decodable, Equatable, Hashable { @@ -4126,10 +4670,13 @@ public enum Format: Decodable, Equatable, Hashable { case secret case colored(color: FormatColor) case uri - case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String]) + case hyperLink(showText: String?, linkUri: String) + case simplexLink(showText: String?, linkType: SimplexLinkType, simplexUri: String, smpHosts: [String]) + case command(commandStr: String) case mention(memberName: String) case email case phone + case unknown public var isSimplexLink: Bool { get { @@ -4146,6 +4693,7 @@ public enum SimplexLinkType: String, Decodable, Hashable { case invitation case group case channel + case relay public var description: String { switch self { @@ -4153,6 +4701,7 @@ public enum SimplexLinkType: String, Decodable, Hashable { case .invitation: return NSLocalizedString("SimpleX one-time invitation", comment: "simplex link type") case .group: return NSLocalizedString("SimpleX group link", comment: "simplex link type") case .channel: return NSLocalizedString("SimpleX channel link", comment: "simplex link type") + case .relay: return NSLocalizedString("SimpleX relay link", comment: "simplex link type") } } } @@ -4188,7 +4737,7 @@ public enum ReportReason: Hashable { case profile case other case unknown(type: String) - + public static var supportedReasons: [ReportReason] = [.spam, .illegal, .community, .profile, .other] public var text: String { @@ -4201,7 +4750,7 @@ public enum ReportReason: Hashable { case let .unknown(type): return type } } - + public var attrString: NSAttributedString { let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) return NSAttributedString(string: text.isEmpty ? self.text : "\(self.text): ", attributes: [ @@ -4242,14 +4791,14 @@ extension ReportReason: Decodable { // Struct to use with simplex API public struct LinkPreview: Codable, Equatable, Hashable { - public init(uri: URL, title: String, description: String = "", image: String) { + public init(uri: String, title: String, description: String = "", image: String) { self.uri = uri self.title = title self.description = description self.image = image } - - public var uri: URL + + public var uri: String public var title: String // TODO remove once optional in haskell public var description: String = "" @@ -4297,7 +4846,7 @@ public enum NtfTknStatus: String, Decodable, Hashable { case .expired: NSLocalizedString("Expired", comment: "token status text") } } - + public func info(register: Bool) -> String { switch self { case .new: return NSLocalizedString("Please wait for token to be registered.", comment: "token info") @@ -4405,17 +4954,20 @@ public enum CIGroupInvitationStatus: String, Decodable, Hashable { } public struct E2EEInfo: Decodable, Hashable { - public var pqEnabled: Bool + public var pqEnabled: Bool? } public enum RcvDirectEvent: Decodable, Hashable { case contactDeleted case profileUpdated(fromProfile: Profile, toProfile: Profile) + case groupInvLinkReceived(groupProfile: Profile) var text: String { switch self { case .contactDeleted: return NSLocalizedString("deleted contact", comment: "rcv direct event chat item") case let .profileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile) + case let .groupInvLinkReceived(groupProfile): + return String.localizedStringWithFormat(NSLocalizedString("requested connection from group %@", comment: "rcv direct event chat item"), groupProfile.displayName) } } @@ -4440,6 +4992,8 @@ public enum RcvDirectEvent: Decodable, Hashable { public enum RcvGroupEvent: Decodable, Hashable { case memberAdded(groupMemberId: Int64, profile: Profile) case memberConnected + case memberAccepted(groupMemberId: Int64, profile: Profile) + case userAccepted case memberLeft case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole) case memberBlocked(groupMemberId: Int64, profile: Profile, blocked: Bool) @@ -4451,12 +5005,16 @@ public enum RcvGroupEvent: Decodable, Hashable { case invitedViaGroupLink case memberCreatedContact case memberProfileUpdated(fromProfile: Profile, toProfile: Profile) + case newMemberPendingReview var text: String { switch self { case let .memberAdded(_, profile): return String.localizedStringWithFormat(NSLocalizedString("invited %@", comment: "rcv group event chat item"), profile.profileViewName) case .memberConnected: return NSLocalizedString("member connected", comment: "rcv group event chat item") + case let .memberAccepted(_, profile): + return String.localizedStringWithFormat(NSLocalizedString("accepted %@", comment: "rcv group event chat item"), profile.profileViewName) + case .userAccepted: return NSLocalizedString("accepted you", comment: "rcv group event chat item") case .memberLeft: return NSLocalizedString("left", comment: "rcv group event chat item") case let .memberRole(_, profile, role): return String.localizedStringWithFormat(NSLocalizedString("changed role of %@ to %@", comment: "rcv group event chat item"), profile.profileViewName, role.text) @@ -4474,8 +5032,9 @@ public enum RcvGroupEvent: Decodable, Hashable { case .groupDeleted: return NSLocalizedString("deleted group", comment: "rcv group event chat item") case .groupUpdated: return NSLocalizedString("updated group profile", comment: "rcv group event chat item") case .invitedViaGroupLink: return NSLocalizedString("invited via your group link", comment: "rcv group event chat item") - case .memberCreatedContact: return NSLocalizedString("connected directly", comment: "rcv group event chat item") + case .memberCreatedContact: return NSLocalizedString("requested connection", comment: "rcv group event chat item") case let .memberProfileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile) + case .newMemberPendingReview: return NSLocalizedString("New member wants to join the group.", comment: "rcv group event chat item") } } @@ -4500,6 +5059,8 @@ public enum SndGroupEvent: Decodable, Hashable { case memberDeleted(groupMemberId: Int64, profile: Profile) case userLeft case groupUpdated(groupProfile: GroupProfile) + case memberAccepted(groupMemberId: Int64, profile: Profile) + case userPendingReview var text: String { switch self { @@ -4517,6 +5078,9 @@ public enum SndGroupEvent: Decodable, Hashable { return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.profileViewName) case .userLeft: return NSLocalizedString("you left", comment: "snd group event chat item") case .groupUpdated: return NSLocalizedString("group profile updated", comment: "snd group event chat item") + case .memberAccepted: return NSLocalizedString("you accepted this member", comment: "snd group event chat item") + case .userPendingReview: + return NSLocalizedString("Please wait for group moderators to review your request to join the group.", comment: "snd group event chat item") } } } @@ -4663,9 +5227,9 @@ public enum ChatItemTTL: Identifiable, Comparable, Hashable { public enum ChatTTL: Identifiable, Hashable { case userDefault(ChatItemTTL) case chat(ChatItemTTL) - + public var id: Self { self } - + public var text: String { switch self { case let .chat(ttl): return ttl.deleteAfterText @@ -4674,21 +5238,21 @@ public enum ChatTTL: Identifiable, Hashable { ttl.deleteAfterText) } } - + public var neverExpires: Bool { switch self { case let .chat(ttl): return ttl.seconds == 0 case let .userDefault(ttl): return ttl.seconds == 0 } } - + public var value: Int64? { switch self { case let .chat(ttl): return ttl.seconds case .userDefault: return nil } } - + public var usingDefault: Bool { switch self { case .userDefault: return true @@ -4701,9 +5265,9 @@ public struct ChatTag: Decodable, Hashable { public var chatTagId: Int64 public var chatTagText: String public var chatTagEmoji: String? - + public var id: Int64 { chatTagId } - + public init(chatTagId: Int64, chatTagText: String, chatTagEmoji: String?) { self.chatTagId = chatTagId self.chatTagText = chatTagText diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index 6cbc76ec98..98ee9cd5d4 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -16,7 +16,7 @@ public protocol ChatLike { extension ChatLike { public func groupFeatureEnabled(_ feature: GroupFeature) -> Bool { - if case let .group(groupInfo) = self.chatInfo { + if case let .group(groupInfo, _) = self.chatInfo { let p = groupInfo.fullGroupPreferences return switch feature { case .timedMessages: p.timedMessages.on @@ -82,9 +82,9 @@ public func foundChat(_ chat: ChatLike, _ searchStr: String) -> Bool { private func canForwardToChat(_ cInfo: ChatInfo) -> Bool { switch cInfo { - case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv - case let .group(groupInfo): groupInfo.sendMsgEnabled - case let .local(noteFolder): noteFolder.sendMsgEnabled + case let .direct(contact): cInfo.sendMsgEnabled && !contact.sendMsgToConnect + case .group: cInfo.sendMsgEnabled + case .local: cInfo.sendMsgEnabled case .contactRequest: false case .contactConnection: false case .invalidJSON: false @@ -93,13 +93,8 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool { public func chatIconName(_ cInfo: ChatInfo) -> String { switch cInfo { - case .direct: "person.crop.circle.fill" - case let .group(groupInfo): - switch groupInfo.businessChat?.chatType { - case .none: "person.2.circle.fill" - case .business: "briefcase.circle.fill" - case .customer: "person.crop.circle.fill" - } + case let .direct(contact): contact.chatIconName + case let .group(groupInfo, _): groupInfo.chatIconName case .local: "folder.circle.fill" case .contactRequest: "person.crop.circle.fill" default: "circle.fill" diff --git a/apps/ios/SimpleXChat/ErrorAlert.swift b/apps/ios/SimpleXChat/ErrorAlert.swift index a433d2313b..2920c2383c 100644 --- a/apps/ios/SimpleXChat/ErrorAlert.swift +++ b/apps/ios/SimpleXChat/ErrorAlert.swift @@ -11,7 +11,6 @@ import SwiftUI public struct ErrorAlert: Error { public let title: LocalizedStringKey public let message: LocalizedStringKey? - public let actions: Optional<() -> AnyView> public init( title: LocalizedStringKey, @@ -19,7 +18,6 @@ public struct ErrorAlert: Error { ) { self.title = title self.message = message - self.actions = nil } public init( @@ -29,7 +27,6 @@ public struct ErrorAlert: Error { ) { self.title = title self.message = message - self.actions = { AnyView(actions()) } } public init(_ title: LocalizedStringKey) { @@ -75,11 +72,7 @@ extension View { set: { if !$0 { errorAlert.wrappedValue = nil } } ), actions: { - if let actions_ = errorAlert.wrappedValue?.actions { - actions_() - } else { - if let alert = errorAlert.wrappedValue { actions(alert) } - } + if let alert = errorAlert.wrappedValue { actions(alert) } }, message: { if let message = errorAlert.wrappedValue?.message { @@ -94,6 +87,8 @@ public func getNetworkErrorAlert(_ e: ChatError) -> ErrorAlert? { switch e { case let .errorAgent(.BROKER(addr, .TIMEOUT)): ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") + case let .errorAgent(.BROKER(addr, .NETWORK(.unknownCAError))): + ErrorAlert(title: "Connection error", message: "Fingerprint in server address does not match certificate: \(serverHostname(addr)).") case let .errorAgent(.BROKER(addr, .NETWORK)): ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") case let .errorAgent(.BROKER(addr, .HOST)): @@ -112,6 +107,8 @@ private func smpProxyErrorAlert(_ proxyErr: ProxyError, _ srvAddr: String) -> Er switch proxyErr { case .BROKER(brokerErr: .TIMEOUT): return ErrorAlert(title: "Private routing error", message: "Error connecting to forwarding server \(serverHostname(srvAddr)). Please try later.") + case .BROKER(brokerErr: .NETWORK(.unknownCAError)): + return ErrorAlert(title: "Private routing error", message: "Fingerprint in forwarding server address does not match certificate: \(serverHostname(srvAddr)).") case .BROKER(brokerErr: .NETWORK): return ErrorAlert(title: "Private routing error", message: "Error connecting to forwarding server \(serverHostname(srvAddr)). Please try later.") case .BROKER(brokerErr: .HOST): @@ -127,6 +124,8 @@ private func proxyDestinationErrorAlert(_ proxyErr: ProxyError, _ proxyServer: S switch proxyErr { case .BROKER(brokerErr: .TIMEOUT): return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.") + case .BROKER(brokerErr: .NETWORK(.unknownCAError)): + return ErrorAlert(title: "Private routing error", message: "Fingerprint in destination server address does not match certificate: \(serverHostname(relayServer)).") case .BROKER(brokerErr: .NETWORK): return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.") case .NO_SESSION: diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index be43158bc1..c70ca5edd8 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -446,7 +446,7 @@ public func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) { let resized = resizeImageToStrSizeSync(image, maxDataSize: 14000), let title = metadata.title, let uri = metadata.originalURL { - linkPreview = LinkPreview(uri: uri, title: title, image: resized) + linkPreview = LinkPreview(uri: uri.absoluteString, title: title, image: resized) } } cb(linkPreview) diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift index 5579449caa..70db4476d5 100644 --- a/apps/ios/SimpleXChat/Notifications.swift +++ b/apps/ios/SimpleXChat/Notifications.swift @@ -62,7 +62,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact, public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent { let previewMode = ntfPreviewModeGroupDefault.get() var title: String - if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir { + if case let .group(groupInfo, _) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir { title = groupMsgNtfTitle(groupInfo, groupMember, hideContent: previewMode == .hidden) } else { title = previewMode == .hidden ? contactHidden : "\(cInfo.chatViewName):" diff --git a/apps/ios/SimpleXChat/SimpleX.h b/apps/ios/SimpleXChat/SimpleX.h index 92dfafca21..5a3541e06d 100644 --- a/apps/ios/SimpleXChat/SimpleX.h +++ b/apps/ios/SimpleXChat/SimpleX.h @@ -20,10 +20,11 @@ typedef void* chat_ctrl; extern char *chat_migrate_init_key(char *path, char *key, int keepKey, char *confirm, int backgroundMode, chat_ctrl *ctrl); extern char *chat_close_store(chat_ctrl ctl); extern char *chat_reopen_store(chat_ctrl ctl); -extern char *chat_send_cmd(chat_ctrl ctl, char *cmd); +extern char *chat_send_cmd_retry(chat_ctrl ctl, char *cmd, int retryNum); extern char *chat_recv_msg_wait(chat_ctrl ctl, int wait); extern char *chat_parse_markdown(char *str); extern char *chat_parse_server(char *str); +extern char *chat_parse_uri(char *str, int safe); extern char *chat_password_hash(char *pwd, char *salt); extern char *chat_valid_name(char *name); extern int chat_json_length(char *str); diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index e4bc8f2150..1ce7b53767 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -61,6 +61,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Сканирай / Постави линк**: за свързване чрез получения линк."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: Незабавните push известия изискват парола, запазена в Keychain."; @@ -121,6 +124,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ е потвърдено"; +/* No comment provided by engineer. */ +"%@ server" = "%@ сървър"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ сървъри"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ качено"; @@ -142,9 +151,24 @@ /* time interval */ "%d days" = "%d дни"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d файл(ове) все още се теглят."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "Тегленето неуспешно за %d файл(ове)."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d файл(ове) бяха изтрити."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d файл(ове) не бяха изтеглени."; + /* time interval */ "%d hours" = "%d часа"; +/* alert title */ +"%d messages not forwarded" = "%d непрепратени съобщения"; + /* time interval */ "%d min" = "%d мин."; @@ -154,6 +178,9 @@ /* time interval */ "%d sec" = "%d сек."; +/* delete after time */ +"%d seconds(s)" = "%d секунди"; + /* integrity error chat item */ "%d skipped message(s)" = "%d пропуснато(и) съобщение(я)"; @@ -259,9 +286,15 @@ time interval */ time interval */ "1 week" = "1 седмица"; +/* delete after time */ +"1 year" = "1 година"; + /* No comment provided by engineer. */ "1-time link" = "Еднократен линк"; +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Еднократният линк може да се използва само веднъж *с един контакт* - споделете го лично или чрез някой месинджър."; + /* No comment provided by engineer. */ "5 minutes" = "5 минути"; @@ -309,22 +342,35 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Приеми"; +/* alert action */ +"Accept as member" = "Приеми като член"; + +/* alert action */ +"Accept as observer" = "Приеми като наблюдател"; + /* No comment provided by engineer. */ "Accept conditions" = "Приеми условията"; /* No comment provided by engineer. */ "Accept connection request?" = "Приемане на заявка за връзка?"; +/* alert title */ +"Accept contact request" = "Приеми заявка за контакт"; + /* notification body */ "Accept contact request from %@?" = "Приемане на заявка за контакт от %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Приеми инкогнито"; +/* alert title */ +"Accept member" = "Приеми член"; + /* call status */ "accepted call" = "обаждането прието"; @@ -337,6 +383,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Грешки при потвърждението"; +/* token status text */ +"Active" = "Активен"; + /* No comment provided by engineer. */ "Active connections" = "Активни връзки"; @@ -346,6 +395,12 @@ swipe action */ /* No comment provided by engineer. */ "Add friends" = "Добави приятели"; +/* No comment provided by engineer. */ +"Add list" = "Добави списък"; + +/* placeholder for sending contact request */ +"Add message" = "Добави съобщение"; + /* No comment provided by engineer. */ "Add profile" = "Добави профил"; @@ -361,6 +416,9 @@ swipe action */ /* No comment provided by engineer. */ "Add to another device" = "Добави към друго устройство"; +/* No comment provided by engineer. */ +"Add to list" = "Добави към списъка"; + /* No comment provided by engineer. */ "Add welcome message" = "Добави съобщение при посрещане"; @@ -418,12 +476,18 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "съгласуване на криптиране…"; +/* No comment provided by engineer. */ +"All" = "Всички"; + /* No comment provided by engineer. */ "All app data is deleted." = "Всички данни от приложението бяха изтрити."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Всички чатове и съобщения ще бъдат изтрити - това не може да бъде отменено!"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Всички чатове ще бъдат премахнати от списъка %@, а списъкът ще бъде изтрит."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Всички данни се изтриват при въвеждане."; @@ -436,6 +500,9 @@ swipe action */ /* feature role */ "all members" = "всички членове"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Всички съобщения и файлове се изпращат с **криптиране от край до край**, с постквантова сигурност в директните съобщения."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Всички съобщения ще бъдат изтрити - това не може да бъде отменено!"; @@ -448,6 +515,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Всички профили"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Всички доклади за нарушения ще бъдат архивирани за вас."; + +/* No comment provided by engineer. */ +"All servers" = "Всички сървъри"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Всички ваши контакти ще останат свързани."; @@ -472,6 +545,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Позволи понижаване"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "Разреши файлове и медия само ако вашият контакт ги разрешава."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа)"; @@ -493,6 +569,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Позволи необратимо изтриване на изпратените съобщения. (24 часа)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Позволи докладването на съобщения на модераторите."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Позволи изпращане на файлове и медия."; @@ -520,16 +599,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Позволи на вашите контакти да изпращат изчезващи съобщения."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Позволи на вашите контактите да изпращат файлове и медия."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Позволи на вашите контакти да изпращат гласови съобщения."; /* No comment provided by engineer. */ "Already connected?" = "Вече сте свързани?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "В процес на свързване!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Вече се присъединихте към групата!"; /* pref value */ @@ -547,6 +629,9 @@ swipe action */ /* No comment provided by engineer. */ "and %lld other events" = "и %lld други събития"; +/* report reason */ +"Another reason" = "Друга причина"; + /* No comment provided by engineer. */ "Answer call" = "Отговор на повикване"; @@ -562,6 +647,9 @@ swipe action */ /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Приложението криптира нови локални файлове (с изключение на видеоклипове)."; +/* No comment provided by engineer. */ +"App group:" = "Група приложения:"; + /* No comment provided by engineer. */ "App icon" = "Икона на приложението"; @@ -589,12 +677,30 @@ swipe action */ /* No comment provided by engineer. */ "Apply to" = "Приложи към"; +/* No comment provided by engineer. */ +"Archive" = "Архивирай"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "Архивирай %lld доклад(а)?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Архивиране на всички доклади за нарушения?"; + /* No comment provided by engineer. */ "Archive and upload" = "Архивиране и качване"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Архивирайте контактите, за да разговаряте по-късно."; +/* No comment provided by engineer. */ +"Archive report" = "Архивирай доклад за нарушения"; + +/* No comment provided by engineer. */ +"Archive report?" = "Архивирай доклад за нарушения?"; + +/* swipe action */ +"Archive reports" = "Архивирай докладите за нарушения"; + /* No comment provided by engineer. */ "Archived contacts" = "Архивирани контакти"; @@ -643,9 +749,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Автоматично приемане на изображения"; -/* alert title */ -"Auto-accept settings" = "Автоматично приемане на настройки"; - /* No comment provided by engineer. */ "Back" = "Назад"; @@ -673,6 +776,9 @@ swipe action */ /* No comment provided by engineer. */ "Better groups" = "По-добри групи"; +/* No comment provided by engineer. */ +"Better groups performance" = "По-добра производителност на групите"; + /* No comment provided by engineer. */ "Better message dates." = "По-добри дати на съобщението."; @@ -685,12 +791,21 @@ swipe action */ /* No comment provided by engineer. */ "Better notifications" = "Подобрени известия"; +/* No comment provided by engineer. */ +"Better privacy and security" = "По-добра поверителност и сигурност"; + /* No comment provided by engineer. */ "Better security ✅" = "По-добра сигурност ✅"; /* No comment provided by engineer. */ "Better user experience" = "Подобрен интерфейс"; +/* No comment provided by engineer. */ +"Bio" = "Био"; + +/* alert title */ +"Bio too large" = "Биографията е твърде дълга"; + /* No comment provided by engineer. */ "Black" = "Черна"; @@ -734,6 +849,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "bold" = "удебелен"; +/* No comment provided by engineer. */ +"Bot" = "Бот"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "И вие, и вашият контакт можете да добавяте реакции към съобщението."; @@ -746,6 +864,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "И вие, и вашият контакт можете да изпращате изчезващи съобщения."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "И вие, и вашият контакт можете да изпращате файлове и медия."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "И вие, и вашият контакт можете да изпращате гласови съобщения."; @@ -758,9 +879,18 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Бизнес чатове"; +/* No comment provided by engineer. */ +"Business connection" = "Бизнес връзка"; + +/* No comment provided by engineer. */ +"Businesses" = "Бизнеси"; + /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "С използването на SimpleX Chat вие се съгласявате със:\n- изпращане само на легално съдържание в публични групи.\n- уважение към другите потребители – без спам."; + /* No comment provided by engineer. */ "Call already ended!" = "Разговорът вече приключи!"; @@ -788,14 +918,21 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Обаждането на члена не е позволено"; +/* alert title */ +"Can't change profile" = "Промяната на профила е невъзможна"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Не може да покани контакта!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Не може да поканят контактите!"; +/* No comment provided by engineer. */ +"Can't message member" = "Изпращането на съобщения на груповия член не е налично"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Отказ"; /* No comment provided by engineer. */ @@ -807,6 +944,9 @@ alert button */ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Няма достъп до Keychain за запазване на паролата за базата данни"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Съобщение не може да бъде препратено"; + /* alert title */ "Cannot receive file" = "Файлът не може да бъде получен"; @@ -819,6 +959,9 @@ alert button */ /* No comment provided by engineer. */ "Change" = "Промени"; +/* alert title */ +"Change automatic message deletion?" = "Промяна на автоматичното изтриване на съобщения?"; + /* authentication reason */ "Change chat profiles" = "Промени чат профилите"; @@ -865,6 +1008,18 @@ set passcode view */ /* chat item text */ "changing address…" = "промяна на адреса…"; +/* No comment provided by engineer. */ +"Chat" = "Чат"; + +/* No comment provided by engineer. */ +"Chat already exists" = "Чатът вече съществува"; + +/* new chat sheet title */ +"Chat already exists!" = "Чатът вече съществува!"; + +/* No comment provided by engineer. */ +"Chat colors" = "Цветове на чата"; + /* No comment provided by engineer. */ "Chat console" = "Конзола"; @@ -874,6 +1029,9 @@ set passcode view */ /* No comment provided by engineer. */ "Chat database deleted" = "Базата данни на чата е изтрита"; +/* No comment provided by engineer. */ +"Chat database exported" = "Базата данни е експортирана"; + /* No comment provided by engineer. */ "Chat database imported" = "Базата данни на е импортирана"; @@ -886,18 +1044,51 @@ set passcode view */ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чатът е спрян. Ако вече сте използвали тази база данни на друго устройство, трябва да я прехвърлите обратно, преди да стартирате чата отново."; +/* No comment provided by engineer. */ +"Chat list" = "Списък с чатове"; + /* No comment provided by engineer. */ "Chat migrated!" = "Чатът е мигриран!"; /* No comment provided by engineer. */ "Chat preferences" = "Чат настройки"; +/* alert message */ +"Chat preferences were changed." = "Настройките на чата бяха променени."; + /* No comment provided by engineer. */ "Chat profile" = "Потребителски профил"; +/* No comment provided by engineer. */ +"Chat theme" = "Тема на чата"; + +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "Чатът ще бъде изтрит за всички членове - това не може да бъде отменено!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "Чатът ще бъде изтрит за вас - това не може да бъде отменено!"; + +/* chat toolbar */ +"Chat with admins" = "Чат с администраторите"; + +/* No comment provided by engineer. */ +"Chat with member" = "Чат с член"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Разговаряйте с членовете, преди да се присъединят."; + /* No comment provided by engineer. */ "Chats" = "Чатове"; +/* No comment provided by engineer. */ +"Chats with members" = "Чатове с членовете"; + +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Проверявай за съобщенията на всеки 20 минути."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Проверявай за съобщенията, когато е разрешено."; + /* alert title */ "Check server address and try again." = "Проверете адреса на сървъра и опитайте отново."; @@ -913,6 +1104,15 @@ set passcode view */ /* No comment provided by engineer. */ "Choose from library" = "Избери от библиотеката"; +/* No comment provided by engineer. */ +"Chunks deleted" = "Изтрити парчета"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "Изтеглени парчета"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "Качени парчета"; + /* swipe action */ "Clear" = "Изчисти"; @@ -922,15 +1122,30 @@ set passcode view */ /* No comment provided by engineer. */ "Clear conversation?" = "Изчисти разговора?"; +/* No comment provided by engineer. */ +"Clear group?" = "Изчисти група?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "Изчисти или изтрий група?"; + /* No comment provided by engineer. */ "Clear private notes?" = "Изчистване на лични бележки?"; /* No comment provided by engineer. */ "Clear verification" = "Изчисти проверката"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "Цветни чатове с нови теми."; + +/* No comment provided by engineer. */ +"Color mode" = "Цветен режим"; + /* No comment provided by engineer. */ "colored" = "цветен"; +/* report reason */ +"Community guidelines violation" = "Нарушение на правилата на общността"; + /* server test step */ "Compare file" = "Сравни файл"; @@ -940,15 +1155,48 @@ set passcode view */ /* No comment provided by engineer. */ "complete" = "завършен"; +/* No comment provided by engineer. */ +"Completed" = "Завършен"; + +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Условия, приети на: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Условията са приети за оператора(ите): **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for these operator(s): **%@**." = "Условията вече са приети за тези оператори: **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Условия за ползване"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Условията ще бъдат приети за операторите: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Условията ще бъдат приети на: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Условията ще бъдат автоматично приети за активираните оператори на: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Конфигурирай ICE сървъри"; +/* No comment provided by engineer. */ +"Configure server operators" = "Конфигуриране на сървърни оператори"; + /* No comment provided by engineer. */ "Confirm" = "Потвърди"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "Потвърди изтриването на контакта?"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "Потвърди актуализаациите на базата данни"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Потвърди файлове от неизвестни сървъри."; + /* No comment provided by engineer. */ "Confirm network settings" = "Потвърди мрежовите настройки"; @@ -967,6 +1215,9 @@ set passcode view */ /* No comment provided by engineer. */ "Confirm upload" = "Потвърди качването"; +/* token status text */ +"Confirmed" = "Потвърдено"; + /* server test step */ "Connect" = "Свързване"; @@ -974,7 +1225,7 @@ set passcode view */ "Connect automatically" = "Автоматично свъзрване"; /* No comment provided by engineer. */ -"Connect incognito" = "Свързване инкогнито"; +"Connect faster! 🚀" = "Свържете се по-бързо! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Свързване с настолно устройство"; @@ -983,34 +1234,37 @@ set passcode view */ "connect to SimpleX Chat developers." = "свържете се с разработчиците на SimpleX Chat."; /* No comment provided by engineer. */ -"Connect to yourself?" = "Свърване със себе си?"; +"Connect to your friends faster." = "Свържете се с приятелите си по-бързо."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Свърване със себе си?\nТова е вашят еднократен линк за връзка!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Свърване със себе си?\nТова е вашият личен SimpleX адрес!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Свързване чрез адрес за контакт"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Свърване чрез линк"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Свързване чрез еднократен линк за връзка"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Свързване с %@"; /* No comment provided by engineer. */ "connected" = "свързан"; +/* No comment provided by engineer. */ +"Connected" = "Свързан"; + /* No comment provided by engineer. */ "Connected desktop" = "Свързано настолно устройство"; -/* rcv group event chat item */ -"connected directly" = "свързан директно"; +/* No comment provided by engineer. */ +"Connected servers" = "Свързани сървъри"; /* No comment provided by engineer. */ "Connected to desktop" = "Свързан с настолно устройство"; @@ -1018,6 +1272,9 @@ set passcode view */ /* No comment provided by engineer. */ "connecting" = "свързване"; +/* No comment provided by engineer. */ +"Connecting" = "Свързване"; + /* No comment provided by engineer. */ "connecting (accepted)" = "свързване (прието)"; @@ -1039,6 +1296,9 @@ set passcode view */ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Свързване със сървър…(грешка: %@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "Тече свързване с контакт, моля изчакайте или проверете по-късно!"; + /* No comment provided by engineer. */ "Connecting to desktop" = "Свързване с настолно устройство"; @@ -1049,6 +1309,12 @@ set passcode view */ "Connection" = "Връзка"; /* No comment provided by engineer. */ +"Connection and servers status." = "Състояние на връзката и сървърите."; + +/* No comment provided by engineer. */ +"Connection blocked" = "Връзката е блокирана"; + +/* alert title */ "Connection error" = "Грешка при свързване"; /* No comment provided by engineer. */ @@ -1057,13 +1323,25 @@ set passcode view */ /* chat list item title (it should not be shown */ "connection established" = "установена е връзка"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "Връзката е блокирана от оператора на сървъра:\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "Връзката не е готова."; + +/* No comment provided by engineer. */ +"Connection notifications" = "Известия за връзката"; + /* No comment provided by engineer. */ "Connection request sent!" = "Заявката за връзка е изпратена!"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "Връзката изисква предоговаряне на криптирането."; + /* No comment provided by engineer. */ "Connection terminated" = "Връзката е прекратена"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Времето на изчакване за установяване на връзката изтече"; /* connection information */ @@ -1144,9 +1422,6 @@ set passcode view */ /* server test step */ "Create queue" = "Създай опашка"; -/* No comment provided by engineer. */ -"Create secret group" = "Създай тайна група"; - /* No comment provided by engineer. */ "Create SimpleX address" = "Създаване на адрес в SimpleX"; @@ -1485,7 +1760,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't enable" = "Не активирай"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Не показвай отново"; /* No comment provided by engineer. */ @@ -1705,7 +1980,7 @@ chat item action */ /* No comment provided by engineer. */ "Error changing role" = "Грешка при промяна на ролята"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Грешка при промяна на настройката"; /* No comment provided by engineer. */ @@ -1729,19 +2004,19 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Грешка при декриптирането на файла"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "Грешка при изтриване на базата данни"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Грешка при изтриването на чата!"; /* No comment provided by engineer. */ "Error deleting connection" = "Грешка при изтриване на връзката"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Грешка при изтриване на базата данни"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Грешка при изтриване на старата база данни"; /* No comment provided by engineer. */ @@ -1762,10 +2037,10 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Грешка при криптиране на базата данни"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Грешка при експортиране на базата данни"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Грешка при импортиране на базата данни"; /* No comment provided by engineer. */ @@ -1777,7 +2052,7 @@ chat item action */ /* alert title */ "Error receiving file" = "Грешка при получаване на файл"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "Грешка при отстраняване на член"; /* No comment provided by engineer. */ @@ -1935,6 +2210,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Намирайте чатове по-бързо"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен"; + /* No comment provided by engineer. */ "Fix" = "Поправи"; @@ -2001,7 +2279,7 @@ snd error text */ /* No comment provided by engineer. */ "Group already exists" = "Групата вече съществува"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Групата вече съществува!"; /* No comment provided by engineer. */ @@ -2247,7 +2525,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Невалидно име!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Невалиден линк"; /* No comment provided by engineer. */ @@ -2335,24 +2613,18 @@ snd error text */ "Join" = "Присъединяване"; /* No comment provided by engineer. */ -"join as %@" = "присъединяване като %@"; +"Join as %@" = "присъединяване като %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Влез в групата"; /* No comment provided by engineer. */ "Join group conversations" = "Присъединяване към групи"; -/* No comment provided by engineer. */ -"Join group?" = "Влез в групата?"; - /* No comment provided by engineer. */ "Join incognito" = "Влез инкогнито"; -/* No comment provided by engineer. */ -"Join with current profile" = "Присъединяване с текущия профил"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Влез в твоята група?\nТова е вашят линк за група %@!"; /* No comment provided by engineer. */ @@ -2747,6 +3019,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "изключено"; @@ -2759,7 +3032,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "предлага %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ок"; /* No comment provided by engineer. */ @@ -2828,13 +3103,13 @@ time to disappear */ /* alert action */ "Open" = "Отвори"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Отвори чат"; /* authentication reason */ "Open chat console" = "Отвори конзолата"; -/* No comment provided by engineer. */ +/* new chat action */ "Open group" = "Отвори група"; /* authentication reason */ @@ -2885,9 +3160,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Парола за показване"; -/* past/unknown group member */ -"Past member %@" = "Бивш член %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Постави адрес на настолно устройство"; @@ -2924,7 +3196,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Моля, проверете дали сте използвали правилния линк или поискайте вашия контакт, за да ви изпрати друг."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Моля, проверете мрежовата си връзка с %@ и опитайте отново."; /* No comment provided by engineer. */ @@ -2963,9 +3235,6 @@ time to disappear */ /* No comment provided by engineer. */ "Polish interface" = "Полски интерфейс"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Запазете последната чернова на съобщението с прикачени файлове."; @@ -3131,14 +3400,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Намалена консумация на батерията"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Отхвърляне"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Отхвърляне (подателят НЕ бива уведомен)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Отхвърли заявката за контакт"; /* call status */ @@ -3186,18 +3456,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Предоговори криптирането?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Изпрати отново заявката за свързване?"; - /* No comment provided by engineer. */ "Repeat download" = "Повтори изтеглянето"; /* No comment provided by engineer. */ "Repeat import" = "Повтори импортирането"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Изпрати отново заявката за присъединяване?"; - /* No comment provided by engineer. */ "Repeat upload" = "Повтори качването"; @@ -3234,7 +3498,7 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Грешка при възстановяване на базата данни"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Опитай отново"; /* chat item action */ @@ -3385,9 +3649,6 @@ chat item action */ /* No comment provided by engineer. */ "Send delivery receipts to" = "Изпращайте потвърждениe за доставка на"; -/* No comment provided by engineer. */ -"send direct message" = "изпрати лично съобщение"; - /* No comment provided by engineer. */ "Send direct message to connect" = "Изпрати лично съобщение за свързване"; @@ -3461,10 +3722,10 @@ chat item action */ "Sent messages will be deleted after set time." = "Изпратените съобщения ще бъдат изтрити след зададеното време."; /* server test error */ -"Server requires authorization to create queues, check password" = "Сървърът изисква оторизация за създаване на опашки, проверете паролата"; +"Server requires authorization to create queues, check password." = "Сървърът изисква оторизация за създаване на опашки, проверете паролата"; /* server test error */ -"Server requires authorization to upload, check password" = "Сървърът изисква оторизация за качване, проверете паролата"; +"Server requires authorization to upload, check password." = "Сървърът изисква оторизация за качване, проверете паролата"; /* No comment provided by engineer. */ "Server test failed!" = "Тестът на сървъра е неуспешен!"; @@ -3560,6 +3821,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX Адрес"; +/* alert title */ +"SimpleX address settings" = "Автоматично приемане на настройки"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Сигурността на SimpleX Chat беше одитирана от Trail of Bits."; @@ -3776,13 +4040,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти."; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Втората отметка, която пропуснахме! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Подателят НЯМА да бъде уведомен"; /* No comment provided by engineer. */ @@ -3827,12 +4088,6 @@ chat item action */ /* No comment provided by engineer. */ "This group no longer exists." = "Тази група вече не съществува."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "Това е вашят еднократен линк за връзка!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Това е вашият личен SimpleX адрес!"; - /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Тази настройка се прилага за съобщения в текущия ви профил **%@**."; @@ -4007,7 +4262,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "Използвай чата"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Използвай текущия профил"; /* No comment provided by engineer. */ @@ -4019,7 +4274,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "Използвай интерфейса за повикване на iOS"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "Използвай нов инкогнито профил"; /* No comment provided by engineer. */ @@ -4220,33 +4475,27 @@ chat item action */ /* No comment provided by engineer. */ "You are already connected to %@." = "Вече сте вече свързани с %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Вече се свързвате с %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Вече се свързвате чрез този еднократен линк за връзка!"; /* No comment provided by engineer. */ "You are already in group %@." = "Вече сте в група %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Вече се присъединявате към групата %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Вие вече се присъединявате към групата чрез този линк!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Вие вече се присъединявате към групата чрез този линк."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Вече се присъединихте към групата!\nИзпрати отново заявката за присъединяване?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Вие сте свързани към сървъра, използван за получаване на съобщения от този контакт."; -/* No comment provided by engineer. */ -"you are invited to group" = "вие сте поканени в групата"; - /* No comment provided by engineer. */ "You are invited to group" = "Поканени сте в групата"; @@ -4301,7 +4550,7 @@ chat item action */ /* alert message */ "You can view invitation link again in connection details." = "Можете да видите отново линкът за покана в подробностите за връзката."; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "Не може да изпращате съобщения!"; /* chat item text */ @@ -4322,10 +4571,7 @@ chat item action */ /* No comment provided by engineer. */ "You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Вече сте заявили връзка през този адрес!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Вече сте направили заявката за връзка!\nИзпрати отново заявката за свързване?"; /* No comment provided by engineer. */ @@ -4382,9 +4628,6 @@ chat item action */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Ще трябва да се идентифицирате, когато стартирате или възобновите приложението след 30 секунди във фонов режим."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Ще се свържете с всички членове на групата."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Все още ще получавате обаждания и известия от заглушени профили, когато са активни."; @@ -4446,10 +4689,10 @@ chat item action */ "Your profile **%@** will be shared." = "Вашият профил **%@** ще бъде споделен."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил."; +"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти."; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил."; /* No comment provided by engineer. */ "Your random profile" = "Вашият автоматично генериран профил"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 08a94615a3..f8597fa4a5 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -10,12 +10,18 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- více stabilní doručování zpráv.\n- o trochu lepší skupiny.\n- a více!"; +/* No comment provided by engineer. */ +"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- volitelně informuje smazané kontakty.\n- profilová jména s mezerami.\n- a více!"; + /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 minutové hlasové zprávy.\n- vlastní čas mizení.\n- historie úprav."; /* No comment provided by engineer. */ "!1 colored!" = "!1 barevný!"; +/* No comment provided by engineer. */ +"(new)" = "(nový)"; + /* No comment provided by engineer. */ "(this device v%@)" = "(toto zařízení v%@)"; @@ -28,6 +34,12 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat)"; +/* No comment provided by engineer. */ +"**Create 1-time link**: to create and share a new invitation link." = "**Vytvořit jednorázový odkaz**: pro vytvoření a sdílení nové pozvánky."; + +/* No comment provided by engineer. */ +"**Create group**: to create a new group." = "**Vytvořit skupinu**: pro vytvoření nové skupiny."; + /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e šifrovaný** audio hovor"; @@ -46,9 +58,15 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Skenovat / Vložit odkaz**: pro připojení pomocí odkazu který jste obdrželi."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Varování**: archiv bude odstraněn."; + /* No comment provided by engineer. */ "*bold*" = "\\*tučně*"; @@ -109,12 +127,18 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ servery"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ nahrán"; + /* notification title */ "%@ wants to connect!" = "%@ se chce připojit!"; /* format for date separator in chat */ "%@, %@" = "%1$@, %2$@"; +/* No comment provided by engineer. */ +"%@, %@ and %lld members" = "%@, %@ a %lld členů"; + /* No comment provided by engineer. */ "%@, %@ and %lld other members connected" = "%@, %@ a %lld ostatní členové připojeni"; @@ -139,6 +163,9 @@ /* time interval */ "%d hours" = "%d hodin"; +/* alert title */ +"%d messages not forwarded" = "%d zprávy nebyly přeposlány"; + /* time interval */ "%d min" = "%d minuty"; @@ -148,6 +175,9 @@ /* time interval */ "%d sec" = "%d sek"; +/* delete after time */ +"%d seconds(s)" = "%d sekund"; + /* integrity error chat item */ "%d skipped message(s)" = "%d přeskočené zprávy"; @@ -178,6 +208,9 @@ /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld zprávy označeno jako smazáno"; +/* No comment provided by engineer. */ +"%lld messages moderated by %@" = "%lld zprávy moderované %@"; + /* No comment provided by engineer. */ "%lld minutes" = "%lld minut"; @@ -223,6 +256,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~stávka~"; +/* time to disappear */ +"0 sec" = "0 sek"; + /* No comment provided by engineer. */ "0s" = "0s"; @@ -244,6 +280,12 @@ time interval */ time interval */ "1 week" = "1 týden"; +/* delete after time */ +"1 year" = "1 rok"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Jednorázový odkaz lze použít *pouze s jedním kontaktem* - sdílejte osobně nebo prostřednictvím libovolného komunikátoru."; + /* No comment provided by engineer. */ "5 minutes" = "5 minut"; @@ -277,6 +319,9 @@ time interval */ /* No comment provided by engineer. */ "Abort changing address?" = "Přerušit změnu adresy?"; +/* No comment provided by engineer. */ +"About operators" = "O operátorech"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "O SimpleX chat"; @@ -285,25 +330,53 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Přijmout"; +/* alert action */ +"Accept as member" = "Přijmout za člena"; + +/* alert action */ +"Accept as observer" = "Přijmout jako pozorovatele"; + +/* No comment provided by engineer. */ +"Accept conditions" = "Přijmout podmínky"; + /* No comment provided by engineer. */ "Accept connection request?" = "Přijmout kontakt?"; +/* alert title */ +"Accept contact request" = "Přijmout žádost o kontakt"; + /* notification body */ "Accept contact request from %@?" = "Přijmout žádost o kontakt od %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Přijmout inkognito"; +/* alert title */ +"Accept member" = "Přijmout člena"; + /* call status */ "accepted call" = "přijatý hovor"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Přijaté podmínky"; + +/* token status text */ +"Active" = "Aktivní"; + +/* No comment provided by engineer. */ +"Active connections" = "Aktivní spojení"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům."; +/* No comment provided by engineer. */ +"Add friends" = "Přidat přátele"; + /* No comment provided by engineer. */ "Add profile" = "Přidat profil"; @@ -313,48 +386,96 @@ swipe action */ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Přidejte servery skenováním QR kódů."; +/* No comment provided by engineer. */ +"Add team members" = "Přidat členy týmu"; + /* No comment provided by engineer. */ "Add to another device" = "Přidat do jiného zařízení"; +/* No comment provided by engineer. */ +"Add to list" = "Přidat do seznamu"; + /* No comment provided by engineer. */ "Add welcome message" = "Přidat uvítací zprávu"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Přidat členy týmu do konverzace."; + +/* No comment provided by engineer. */ +"Added message servers" = "Přidané servery zpráv"; + +/* No comment provided by engineer. */ +"Additional accent" = "Další zbarvení"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "Další zbarvení 2"; + /* No comment provided by engineer. */ "Address" = "Adresa"; /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Změna adresy bude přerušena. Budou použity staré přijímací adresy."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Adresa nebo jednorázový odkaz?"; + +/* No comment provided by engineer. */ +"Address settings" = "Nastavení adresy"; + /* member role */ "admin" = "správce"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Správci mohou blokovat člena pro všechny."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Správci mohou vytvářet odkazy pro připojení ke skupinám."; /* No comment provided by engineer. */ "Advanced network settings" = "Pokročilá nastavení sítě"; +/* No comment provided by engineer. */ +"Advanced settings" = "Pokročilá nastavení"; + /* chat item text */ "agreeing encryption for %@…" = "povoluji šifrování pro %@…"; /* chat item text */ "agreeing encryption…" = "povoluji šifrování…"; +/* No comment provided by engineer. */ +"All" = "Vše"; + /* No comment provided by engineer. */ "All app data is deleted." = "Všechna data aplikace jsou smazána."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Všechny chaty a zprávy budou smazány – tuto akci nelze vrátit zpět!"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Všechny chaty budou odstraněny ze seznamu %@ a seznam bude odstraněn."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Všechna data se při zadání vymažou."; +/* No comment provided by engineer. */ +"All data is kept private on your device." = "Všechna data jsou uchována ve vašem zařízení."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Všichni členové skupiny zůstanou připojeni."; /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás."; +/* No comment provided by engineer. */ +"All new messages from %@ will be hidden!" = "Všechny nové zprávy od %@ budou skryté!"; + +/* profile dropdown */ +"All profiles" = "Všechny profily"; + +/* No comment provided by engineer. */ +"All servers" = "Všechny servery"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Všechny vaše kontakty zůstanou připojeny."; @@ -367,6 +488,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "Povolte hovory, pouze pokud je váš kontakt povolí."; +/* No comment provided by engineer. */ +"Allow calls?" = "Povolit volání?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí."; @@ -385,12 +509,21 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Povolit odesílání mizících zpráv."; +/* No comment provided by engineer. */ +"Allow sharing" = "Povolit sdílení"; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Povolit nevratné smazání odeslaných zpráv. (24 hodin)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Povolit nahlášení zpráv moderátorům."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Povolit odesílání souborů a médii."; +/* No comment provided by engineer. */ +"Allow to send SimpleX links." = "Povolit odesílání SimpleX odkazů."; + /* No comment provided by engineer. */ "Allow to send voice messages." = "Povolit odesílání hlasových zpráv."; @@ -412,21 +545,36 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Umožněte svým kontaktům odesílat mizící zprávy."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Povolit vašim kontaktům odesílání souborů a médii."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Povolte svým kontaktům odesílání hlasových zpráv."; /* No comment provided by engineer. */ "Already connected?" = "Již připojeno?"; +/* new chat sheet title */ +"Already connecting!" = "Již připojováno!"; + +/* new chat sheet title */ +"Already joining the group!" = "Již se ke skupině připojujete!"; + /* pref value */ "always" = "vždy"; +/* No comment provided by engineer. */ +"Always use private routing." = "Vždy používat soukromé směrování."; + /* No comment provided by engineer. */ "Always use relay" = "Spojení přes relé"; /* No comment provided by engineer. */ "An empty chat profile with the provided name is created, and the app opens as usual." = "Vytvořit prázdný chat profil se zadaným názvem a otevřít aplikaci jako obvykle."; +/* report reason */ +"Another reason" = "Jiný důvod"; + /* No comment provided by engineer. */ "Answer call" = "Přijmout hovor"; @@ -436,6 +584,9 @@ swipe action */ /* No comment provided by engineer. */ "App build: %@" = "Sestavení aplikace: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Přenos dat aplikace"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Aplikace šifruje nové místní soubory (s výjimkou videí)."; @@ -457,6 +608,21 @@ swipe action */ /* No comment provided by engineer. */ "Appearance" = "Vzhled"; +/* No comment provided by engineer. */ +"Apply" = "Použít"; + +/* No comment provided by engineer. */ +"Archive" = "Archiv"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Archivovat všechny hlášení?"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivovat a nahrát"; + +/* No comment provided by engineer. */ +"Archived contacts" = "Archivované kontakty"; + /* No comment provided by engineer. */ "Attach" = "Připojit"; @@ -499,6 +665,9 @@ swipe action */ /* No comment provided by engineer. */ "Back" = "Zpět"; +/* No comment provided by engineer. */ +"Background" = "Pozadí"; + /* integrity error chat item */ "bad message hash" = "špatný hash zprávy"; @@ -511,9 +680,45 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Špatné ID zprávy"; +/* No comment provided by engineer. */ +"Better calls" = "Lepší volání"; + +/* No comment provided by engineer. */ +"Better groups" = "Lepší skupiny"; + +/* No comment provided by engineer. */ +"Better groups performance" = "Lepší výkon skupin"; + +/* No comment provided by engineer. */ +"Better message dates." = "Lepší datumy zpráv."; + /* No comment provided by engineer. */ "Better messages" = "Lepší zprávy"; +/* No comment provided by engineer. */ +"Better networking" = "Lepší sítě"; + +/* No comment provided by engineer. */ +"Better notifications" = "Lepší upozornění"; + +/* No comment provided by engineer. */ +"Better privacy and security" = "Lepší soukromí a zabezpečení"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Lepší zabezpečení ✅"; + +/* No comment provided by engineer. */ +"Block member" = "Blokovat člena"; + +/* No comment provided by engineer. */ +"Block member?" = "Blokovat člena?"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "Blokován správcem"; + +/* No comment provided by engineer. */ +"Blur media" = "Rozmazat média"; + /* No comment provided by engineer. */ "bold" = "tučně"; @@ -529,12 +734,18 @@ swipe action */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Vy i váš kontakt můžete posílat mizící zprávy."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Vy i vaše kontakty můžete posílat soubory a média."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Hlasové zprávy můžete posílat vy i váš kontakt."; /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulharský, finský, thajský a ukrajinský - díky uživatelům a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +/* No comment provided by engineer. */ +"Business address" = "Obchodní adresa"; + /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Podle chat profilu (výchozí) nebo [podle připojení](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; @@ -553,28 +764,65 @@ swipe action */ /* No comment provided by engineer. */ "Calls" = "Hovory"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "Volání zakázáno!"; + +/* No comment provided by engineer. */ +"Camera not available" = "Kamera není k dispozici"; + +/* No comment provided by engineer. */ +"Can't call contact" = "Kontaktu nelze volat"; + +/* No comment provided by engineer. */ +"Can't call member" = "Členu nelze volat"; + +/* alert title */ +"Can't change profile" = "Nelze změnit profil"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Nelze pozvat kontakt!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Nelze pozvat kontakty!"; +/* No comment provided by engineer. */ +"Can't message member" = "Členu nelze poslat zprávu"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Zrušit"; +/* No comment provided by engineer. */ +"Cancel migration" = "Zrušit přesun"; + /* feature offered item */ "cancelled %@" = "zrušeno %@"; /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Nelze získat přístup ke klíčence pro uložení hesla databáze"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Zprávu nelze přeposlat"; + /* alert title */ "Cannot receive file" = "Nelze přijmout soubor"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Kapacita překročena - příjemce neobdrží dříve poslané zprávy."; + +/* No comment provided by engineer. */ +"Cellular" = "Mobilní"; + /* No comment provided by engineer. */ "Change" = "Změnit"; +/* alert title */ +"Change automatic message deletion?" = "Změnit automatické mazání zpráv?"; + +/* authentication reason */ +"Change chat profiles" = "Změnit chat profily"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Změnit přístupovou frázi databáze?"; @@ -618,6 +866,9 @@ set passcode view */ /* chat item text */ "changing address…" = "změna adresy…"; +/* No comment provided by engineer. */ +"Chat colors" = "Barvy chatu"; + /* No comment provided by engineer. */ "Chat console" = "Konzola pro chat"; @@ -627,6 +878,9 @@ set passcode view */ /* No comment provided by engineer. */ "Chat database deleted" = "Databáze chatu odstraněna"; +/* No comment provided by engineer. */ +"Chat database exported" = "Chat databáze exportována"; + /* No comment provided by engineer. */ "Chat database imported" = "Importovaná databáze chatu"; @@ -636,6 +890,9 @@ set passcode view */ /* No comment provided by engineer. */ "Chat is stopped" = "Chat je zastaven"; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat přesunut!"; + /* No comment provided by engineer. */ "Chat preferences" = "Předvolby chatu"; @@ -699,27 +956,30 @@ set passcode view */ /* No comment provided by engineer. */ "Confirm password" = "Potvrdit heslo"; +/* No comment provided by engineer. */ +"Confirm upload" = "Potvrdit nahrání"; + /* server test step */ "Connect" = "Připojit"; /* No comment provided by engineer. */ -"Connect incognito" = "Spojit se inkognito"; +"Connect automatically" = "Připojit automaticky"; /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "připojit se k vývojářům SimpleX Chat."; -/* No comment provided by engineer. */ +/* new chat sheet title */ +"Connect to yourself?\nThis is your own one-time link!" = "Připojit se k sobě?\nToto je váš vlastní jednorázový odkaz!"; + +/* new chat sheet title */ "Connect via link" = "Připojte se prostřednictvím odkazu"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Připojit se jednorázovým odkazem"; /* No comment provided by engineer. */ "connected" = "připojeno"; -/* rcv group event chat item */ -"connected directly" = "připojeno přímo"; - /* No comment provided by engineer. */ "connecting" = "připojování"; @@ -750,7 +1010,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection" = "Připojení"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Chyba připojení"; /* No comment provided by engineer. */ @@ -762,7 +1022,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection request sent!" = "Požadavek na připojení byl odeslán!"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Časový limit připojení"; /* connection information */ @@ -828,9 +1088,6 @@ set passcode view */ /* server test step */ "Create queue" = "Vytvořit frontu"; -/* No comment provided by engineer. */ -"Create secret group" = "Vytvořit tajnou skupinu"; - /* No comment provided by engineer. */ "Create SimpleX address" = "Vytvořit SimpleX adresu"; @@ -1127,7 +1384,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't enable" = "Nepovolovat"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Znovu neukazuj"; /* No comment provided by engineer. */ @@ -1304,7 +1561,7 @@ swipe action */ /* No comment provided by engineer. */ "Error changing role" = "Chyba při změně role"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Chyba změny nastavení"; /* No comment provided by engineer. */ @@ -1325,19 +1582,19 @@ swipe action */ /* No comment provided by engineer. */ "Error decrypting file" = "Chyba dešifrování souboru"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "Chyba při mazání databáze chatu"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Chyba při mazání chatu!"; /* No comment provided by engineer. */ "Error deleting connection" = "Chyba při mazání připojení"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Chyba při mazání databáze"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Chyba při mazání staré databáze"; /* No comment provided by engineer. */ @@ -1355,10 +1612,10 @@ swipe action */ /* No comment provided by engineer. */ "Error encrypting database" = "Chyba šifrování databáze"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Chyba při exportu databáze chatu"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Chyba při importu databáze chatu"; /* No comment provided by engineer. */ @@ -1367,7 +1624,7 @@ swipe action */ /* alert title */ "Error receiving file" = "Chyba při příjmu souboru"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "Chyba při odebrání člena"; /* No comment provided by engineer. */ @@ -1495,6 +1752,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Najděte chaty rychleji"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Je možné, že otisk certifikátu v adrese serveru je nesprávný"; + /* No comment provided by engineer. */ "Fix" = "Opravit"; @@ -1823,9 +2083,9 @@ snd error text */ "Join" = "Připojte se na"; /* No comment provided by engineer. */ -"join as %@" = "připojit se jako %@"; +"Join as %@" = "připojit se jako %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Připojit ke skupině"; /* No comment provided by engineer. */ @@ -2145,6 +2405,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "vypnuto"; @@ -2157,7 +2418,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "nabídl %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -2223,7 +2486,7 @@ time to disappear */ /* alert action */ "Open" = "Otevřít"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Otevřete chat"; /* authentication reason */ @@ -2277,7 +2540,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Zkontrolujte, zda jste použili správný odkaz, nebo požádejte kontakt, aby vám poslal jiný."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Zkontrolujte síťové připojení pomocí %@ a zkuste to znovu."; /* No comment provided by engineer. */ @@ -2310,9 +2573,6 @@ time to disappear */ /* No comment provided by engineer. */ "Polish interface" = "Polské rozhraní"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Je možné, že otisk certifikátu v adrese serveru je nesprávný"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachování posledního návrhu zprávy s přílohami."; @@ -2451,14 +2711,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Snížení spotřeby baterie"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Odmítnout"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Odmítnout kontakt (odesílatel NEBUDE upozorněn)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Odmítnout žádost o kontakt"; /* call status */ @@ -2654,9 +2915,6 @@ chat item action */ /* No comment provided by engineer. */ "Send delivery receipts to" = "Potvrzení o doručení zasílat na"; -/* No comment provided by engineer. */ -"send direct message" = "odeslat přímou zprávu"; - /* No comment provided by engineer. */ "Send direct message to connect" = "Odeslat přímou zprávu pro připojení"; @@ -2727,10 +2985,10 @@ chat item action */ "Sent messages will be deleted after set time." = "Odeslané zprávy se po uplynutí nastavené doby odstraní."; /* server test error */ -"Server requires authorization to create queues, check password" = "Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo"; +"Server requires authorization to create queues, check password." = "Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo"; /* server test error */ -"Server requires authorization to upload, check password" = "Server vyžaduje autorizaci pro nahrávání, zkontrolujte heslo"; +"Server requires authorization to upload, check password." = "Server vyžaduje autorizaci pro nahrávání, zkontrolujte heslo"; /* No comment provided by engineer. */ "Server test failed!" = "Test serveru se nezdařil!"; @@ -2988,13 +3246,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Odesílatel NEBUDE informován"; /* No comment provided by engineer. */ @@ -3153,7 +3408,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "Použijte chat"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Použít aktuální profil"; /* No comment provided by engineer. */ @@ -3162,7 +3417,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "Použít rozhraní volání iOS"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "Použít nový inkognito profil"; /* No comment provided by engineer. */ @@ -3300,9 +3555,6 @@ chat item action */ /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Jste připojeni k serveru, který se používá k přijímání zpráv od tohoto kontaktu."; -/* No comment provided by engineer. */ -"you are invited to group" = "jste pozváni do skupiny"; - /* No comment provided by engineer. */ "You are invited to group" = "Jste pozváni do skupiny"; @@ -3345,7 +3597,7 @@ chat item action */ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "K formátování zpráv můžete použít markdown:"; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "Nemůžete posílat zprávy!"; /* chat item text */ @@ -3472,10 +3724,10 @@ chat item action */ "Your profile **%@** will be shared." = "Váš profil **%@** bude sdílen."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil."; +"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil."; /* No comment provided by engineer. */ "Your random profile" = "Váš náhodný profil"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 8da7835c43..3008d29608 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -342,22 +342,38 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Annehmen"; +/* alert action */ +"Accept as member" = "Als Mitglied übernehmen"; + +/* alert action */ +"Accept as observer" = "Als Beobachter übernehmen"; + /* No comment provided by engineer. */ "Accept conditions" = "Nutzungsbedingungen akzeptieren"; /* No comment provided by engineer. */ "Accept connection request?" = "Kontaktanfrage annehmen?"; +/* alert title */ +"Accept contact request" = "Kontaktanfrage annehmen"; + /* notification body */ "Accept contact request from %@?" = "Die Kontaktanfrage von %@ annehmen?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Inkognito akzeptieren"; +/* alert title */ +"Accept member" = "Mitglied annehmen"; + +/* rcv group event chat item */ +"accepted %@" = "%@ angenommen"; + /* call status */ "accepted call" = "Anruf angenommen"; @@ -367,6 +383,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "Einladung angenommen"; +/* rcv group event chat item */ +"accepted you" = "hat Sie angenommen"; + /* No comment provided by engineer. */ "Acknowledged" = "Bestätigt"; @@ -388,6 +407,9 @@ swipe action */ /* No comment provided by engineer. */ "Add list" = "Liste hinzufügen"; +/* placeholder for sending contact request */ +"Add message" = "Nachricht hinzufügen"; + /* No comment provided by engineer. */ "Add profile" = "Profil hinzufügen"; @@ -463,6 +485,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "Verschlüsselung zustimmen…"; +/* member criteria value */ +"all" = "alle"; + /* No comment provided by engineer. */ "All" = "Alle"; @@ -532,6 +557,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Herabstufung erlauben"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "Erlauben Sie Dateien und Medien nur dann, wenn es Ihr Kontakt ebenfalls erlaubt."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden)"; @@ -569,7 +597,7 @@ swipe action */ "Allow voice messages only if your contact allows them." = "Erlauben Sie Sprachnachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt."; /* No comment provided by engineer. */ -"Allow voice messages?" = "Sprachnachricht erlauben?"; +"Allow voice messages?" = "Sprachnachrichten erlauben?"; /* No comment provided by engineer. */ "Allow your contacts adding message reactions." = "Erlauben Sie Ihren Kontakten Reaktionen auf Nachrichten zu geben."; @@ -583,16 +611,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Erlauben Sie Ihren Kontakten Dateien und Medien zu senden."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Erlauben Sie Ihren Kontakten Sprachnachrichten zu senden."; /* No comment provided by engineer. */ "Already connected?" = "Sind Sie bereits verbunden?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Bereits verbunden!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Sie sind bereits Mitglied der Gruppe!"; /* pref value */ @@ -736,9 +767,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Bilder automatisch akzeptieren"; -/* alert title */ -"Auto-accept settings" = "Einstellungen automatisch akzeptieren"; - /* No comment provided by engineer. */ "Back" = "Zurück"; @@ -790,6 +818,12 @@ swipe action */ /* No comment provided by engineer. */ "Better user experience" = "Verbesserte Nutzer-Erfahrung"; +/* No comment provided by engineer. */ +"Bio" = "Biografie"; + +/* alert title */ +"Bio too large" = "Biografie zu lang"; + /* No comment provided by engineer. */ "Black" = "Schwarz"; @@ -828,11 +862,14 @@ marked deleted chat item preview text */ "Blur for better privacy." = "Für bessere Privatsphäre verpixeln."; /* No comment provided by engineer. */ -"Blur media" = "Medium verpixeln"; +"Blur media" = "Medien verpixeln"; /* No comment provided by engineer. */ "bold" = "fett"; +/* No comment provided by engineer. */ +"Bot" = "Bot"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "Sowohl Sie, als auch Ihr Kontakt können Reaktionen auf Nachrichten geben."; @@ -845,6 +882,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Ihr Kontakt und Sie können beide verschwindende Nachrichten senden."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Sowohl Sie, als auch Ihr Kontakt können Dateien und Medien senden."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden."; @@ -857,6 +897,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Geschäftliche Chats"; +/* No comment provided by engineer. */ +"Business connection" = "Geschäftliche Verbindung"; + /* No comment provided by engineer. */ "Businesses" = "Unternehmen"; @@ -896,6 +939,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Mitglied kann nicht angerufen werden"; +/* alert title */ +"Can't change profile" = "Änderung des Profils nicht möglich"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Kontakt kann nicht eingeladen werden!"; @@ -905,8 +951,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; +/* No comment provided by engineer. */ +"can't send messages" = "Es können keine Nachrichten gesendet werden"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Abbrechen"; /* No comment provided by engineer. */ @@ -988,7 +1038,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "Chat besteht bereits"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "Chat besteht bereits!"; /* No comment provided by engineer. */ @@ -1042,9 +1092,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; +/* chat toolbar */ +"Chat with admins" = "Chat mit Administratoren"; + +/* No comment provided by engineer. */ +"Chat with member" = "Chat mit einem Mitglied"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Mit Mitgliedern chatten bevor sie beitreten."; + /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Chats with members" = "Chats mit Mitgliedern"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Alle 20min Nachrichten überprüfen."; @@ -1187,7 +1249,7 @@ set passcode view */ "Connect automatically" = "Automatisch verbinden"; /* No comment provided by engineer. */ -"Connect incognito" = "Inkognito verbinden"; +"Connect faster! 🚀" = "Schneller miteinander verbinden! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Mit dem Desktop verbinden"; @@ -1198,25 +1260,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Schneller mit Ihren Freunden verbinden."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Mit Ihnen selbst verbinden?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Mit Ihnen selbst verbinden?\nDas ist Ihr eigener Einmal-Link!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Sich mit Ihnen selbst verbinden?\nDas ist Ihre eigene SimpleX-Adresse!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Über die Kontakt-Adresse verbinden"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Über einen Link verbinden"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Über einen Einmal-Link verbinden"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Mit %@ verbinden"; /* No comment provided by engineer. */ @@ -1228,9 +1287,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Verbundener Desktop"; -/* rcv group event chat item */ -"connected directly" = "Direkt miteinander verbunden"; - /* No comment provided by engineer. */ "Connected servers" = "Verbundene Server"; @@ -1282,7 +1338,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "Verbindung blockiert"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Verbindungsfehler"; /* No comment provided by engineer. */ @@ -1312,7 +1368,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Verbindung beendet"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Verbindungszeitüberschreitung"; /* No comment provided by engineer. */ @@ -1333,9 +1389,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Der Kontakt ist bereits vorhanden"; +/* No comment provided by engineer. */ +"contact deleted" = "Kontakt gelöscht"; + /* No comment provided by engineer. */ "Contact deleted!" = "Kontakt gelöscht!"; +/* No comment provided by engineer. */ +"contact disabled" = "Kontakt deaktiviert"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "Kontakt nutzt E2E-Verschlüsselung"; @@ -1354,9 +1416,18 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Kontaktname"; +/* No comment provided by engineer. */ +"contact not ready" = "Kontakt nicht bereit"; + /* No comment provided by engineer. */ "Contact preferences" = "Kontakt-Präferenzen"; +/* No comment provided by engineer. */ +"Contact requests from groups" = "KONTAKTANFRAGEN VON GRUPPEN"; + +/* No comment provided by engineer. */ +"contact should accept…" = "Kontakt sollte annehmen…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "Kontakt wird gelöscht. Dies kann nicht rückgängig gemacht werden!"; @@ -1424,10 +1495,10 @@ set passcode view */ "Create queue" = "Erzeuge Warteschlange"; /* No comment provided by engineer. */ -"Create secret group" = "Geheime Gruppe erstellen"; +"Create SimpleX address" = "SimpleX-Adresse erstellen"; /* No comment provided by engineer. */ -"Create SimpleX address" = "SimpleX-Adresse erstellen"; +"Create your address" = "Ihre Adresse erstellen"; /* No comment provided by engineer. */ "Create your profile" = "Erstellen Sie Ihr Profil"; @@ -1602,6 +1673,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Chat-Profil löschen?"; +/* alert title */ +"Delete chat with member?" = "Chat mit dem Mitglied löschen?"; + /* No comment provided by engineer. */ "Delete chat?" = "Chat löschen?"; @@ -1728,9 +1802,15 @@ swipe action */ /* No comment provided by engineer. */ "Delivery receipts!" = "Empfangsbestätigungen!"; +/* No comment provided by engineer. */ +"Deprecated options" = "Veraltete Optionen"; + /* No comment provided by engineer. */ "Description" = "Beschreibung"; +/* alert title */ +"Description too large" = "Beschreibung zu lang"; + /* No comment provided by engineer. */ "Desktop address" = "Desktop-Adresse"; @@ -1872,7 +1952,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "Verpassen Sie keine wichtigen Nachrichten."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Nicht nochmals anzeigen"; /* No comment provided by engineer. */ @@ -1933,6 +2013,9 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Gruppenprofil bearbeiten"; +/* No comment provided by engineer. */ +"Empty message!" = "Leere Nachricht!"; + /* No comment provided by engineer. */ "Enable" = "Aktivieren"; @@ -1945,6 +2028,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Kamera-Zugriff aktivieren"; +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Verschwindende Nachrichten sind per Voreinstellung aktiviert."; + /* No comment provided by engineer. */ "Enable Flux in Network & servers settings for better metadata privacy." = "Für einen besseren Metadatenschutz Flux in den Netzwerk- und Servereinstellungen aktivieren."; @@ -2116,22 +2202,31 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage"; +/* alert title */ +"Error accepting member" = "Fehler beim Annehmen des Mitglieds"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; /* alert title */ "Error adding server" = "Fehler beim Hinzufügen des Servers"; +/* No comment provided by engineer. */ +"Error adding short link" = "Fehler beim Hinzufügen eines verkürzten Links"; + /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der Empfängeradresse"; +/* alert title */ +"Error changing chat profile" = "Fehler beim Wechseln des Chat-Profils"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Fehler beim Wechseln des Verbindungs-Profils"; /* No comment provided by engineer. */ "Error changing role" = "Fehler beim Ändern der Rolle"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Fehler beim Ändern der Einstellung"; /* No comment provided by engineer. */ @@ -2140,7 +2235,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Fehler beim Überprüfen des Token-Status"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es später erneut."; /* No comment provided by engineer. */ @@ -2170,19 +2265,22 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Fehler beim Entschlüsseln der Datei"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Fehler beim Löschen des Chats mit dem Mitglied"; + +/* alert title */ "Error deleting chat database" = "Fehler beim Löschen der Chat-Datenbank"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Fehler beim Löschen des Chats!"; /* No comment provided by engineer. */ "Error deleting connection" = "Fehler beim Löschen der Verbindung"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Fehler beim Löschen der Datenbank"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Fehler beim Löschen der alten Datenbank"; /* No comment provided by engineer. */ @@ -2203,13 +2301,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Fehler beim Verschlüsseln der Datenbank"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Fehler beim Exportieren der Chat-Datenbank"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Fehler beim Exportieren des Designs: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Fehler beim Importieren der Chat-Datenbank"; /* No comment provided by engineer. */ @@ -2222,7 +2320,10 @@ chat item action */ "Error migrating settings" = "Fehler beim Migrieren der Einstellungen"; /* No comment provided by engineer. */ -"Error opening chat" = "Fehler beim Öffnen des Chats"; +"Error opening chat" = "Fehler beim Öffnen des Chat"; + +/* No comment provided by engineer. */ +"Error opening group" = "Fehler beim Vorbereiten der Gruppe"; /* alert title */ "Error receiving file" = "Fehler beim Herunterladen der Datei"; @@ -2236,7 +2337,10 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Fehler beim Registrieren für Benachrichtigungen"; -/* No comment provided by engineer. */ +/* alert title */ +"Error rejecting contact request" = "Fehler bei der Ablehnung der Kontaktanfrage"; + +/* alert title */ "Error removing member" = "Fehler beim Entfernen des Mitglieds"; /* alert title */ @@ -2281,6 +2385,9 @@ chat item action */ /* No comment provided by engineer. */ "Error sending message" = "Fehler beim Senden der Nachricht"; +/* No comment provided by engineer. */ +"Error setting auto-accept" = "Fehler bei der Einstellung des automatischen Akzeptierens"; + /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Fehler beim Setzen von Empfangsbestätigungen!"; @@ -2290,7 +2397,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Fehler beim Beenden des Chats"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Fehler beim Wechseln des Profils"; /* alertTitle */ @@ -2439,6 +2546,9 @@ snd error text */ /* chat feature */ "Files and media" = "Dateien und Medien"; +/* No comment provided by engineer. */ +"Files and media are prohibited in this chat." = "In diesem Chat sind Dateien und Medien nicht erlaubt."; + /* No comment provided by engineer. */ "Files and media are prohibited." = "In dieser Gruppe sind Dateien und Medien nicht erlaubt."; @@ -2463,6 +2573,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Chats schneller finden"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig"; + /* No comment provided by engineer. */ "Fix" = "Reparieren"; @@ -2532,8 +2645,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "%lld Nachricht(en) wird/werden weitergeleitet"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es später erneut."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Weiterleitungsserver %1$@ konnte sich nicht mit dem Zielserver %2$@ verbinden. Bitte versuchen Sie es später erneut."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "Adresse des Weiterleitungsservers ist nicht kompatibel mit den Netzwerkeinstellungen: %@."; @@ -2580,13 +2693,16 @@ snd error text */ /* message preview */ "Good morning!" = "Guten Morgen!"; +/* shown on group welcome message */ +"group" = "Gruppe"; + /* No comment provided by engineer. */ "Group" = "Gruppe"; /* No comment provided by engineer. */ "Group already exists" = "Die Gruppe besteht bereits"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Die Gruppe besteht bereits!"; /* No comment provided by engineer. */ @@ -2610,6 +2726,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Die Gruppeneinladung ist nicht mehr gültig, da sie vom Absender entfernt wurde."; +/* No comment provided by engineer. */ +"group is deleted" = "Gruppe wurde gelöscht"; + /* No comment provided by engineer. */ "Group link" = "Gruppen-Link"; @@ -2634,6 +2753,9 @@ snd error text */ /* snd group event chat item */ "group profile updated" = "Gruppenprofil aktualisiert"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Das Gruppenprofil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an die Gruppenmitglieder gesendet."; + /* No comment provided by engineer. */ "Group welcome message" = "Gruppen-Begrüßungsmeldung"; @@ -2880,7 +3002,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Ungültiger Anzeigename!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Ungültiger Link"; /* No comment provided by engineer. */ @@ -2980,24 +3102,18 @@ snd error text */ "Join" = "Beitreten"; /* No comment provided by engineer. */ -"join as %@" = "beitreten als %@"; +"Join as %@" = "Als %@ beitreten"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Treten Sie der Gruppe bei"; /* No comment provided by engineer. */ "Join group conversations" = "Gruppenunterhaltungen beitreten"; -/* No comment provided by engineer. */ -"Join group?" = "Der Gruppe beitreten?"; - /* No comment provided by engineer. */ "Join incognito" = "Inkognito beitreten"; -/* No comment provided by engineer. */ -"Join with current profile" = "Mit dem aktuellen Profil beitreten"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Ihrer Gruppe beitreten?\nDas ist Ihr Link für die Gruppe %@!"; /* No comment provided by engineer. */ @@ -3015,6 +3131,9 @@ snd error text */ /* alert title */ "Keep unused invitation?" = "Nicht genutzte Einladung behalten?"; +/* No comment provided by engineer. */ +"Keep your chats clean" = "Ihre Chats übersichtlich halten"; + /* No comment provided by engineer. */ "Keep your connections" = "Ihre Verbindungen beibehalten"; @@ -3048,6 +3167,9 @@ snd error text */ /* rcv group event chat item */ "left" = "hat die Gruppe verlassen"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Weniger Datenverkehr in mobilen Netzen."; + /* email subject */ "Let's talk in SimpleX Chat" = "Lassen Sie uns in SimpleX Chat kommunizieren"; @@ -3084,6 +3206,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "Live Nachrichten"; +/* in progress text */ +"Loading profile…" = "Profil wird geladen…"; + /* No comment provided by engineer. */ "Local name" = "Lokaler Name"; @@ -3135,15 +3260,27 @@ snd error text */ /* No comment provided by engineer. */ "Member" = "Mitglied"; +/* past/unknown group member */ +"Member %@" = "Mitglied %@"; + /* profile update event chat item */ "member %@ changed to %@" = "Der Mitgliedsname von %1$@ wurde auf %2$@ geändert"; +/* No comment provided by engineer. */ +"Member admission" = "Aufnahme von Mitgliedern"; + /* rcv group event chat item */ "member connected" = "ist der Gruppe beigetreten"; +/* No comment provided by engineer. */ +"member has old version" = "Das Mitglied hat eine alte App-Version"; + /* item status text */ "Member inactive" = "Mitglied inaktiv"; +/* No comment provided by engineer. */ +"Member is deleted - can't accept request" = "Mitglied ist gelöscht - Anfrage kann nicht angenommen werden"; + /* chat feature */ "Member reports" = "Mitglieder-Meldungen"; @@ -3162,6 +3299,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!"; +/* alert message */ +"Member will join the group, accept member?" = "Mitglied wird der Gruppe beitreten. Annehmen?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; @@ -3210,11 +3350,14 @@ snd error text */ /* item status text */ "Message forwarded" = "Nachricht weitergeleitet"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Sobald Sie auf Verbinden tippen, erhalten Sie sofort eine Nachricht."; + /* item status description */ "Message may be delivered later if member becomes active." = "Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird."; /* No comment provided by engineer. */ -"Message queue info" = "Nachrichten-Warteschlangen-Information"; +"Message queue info" = "Information Nachrichtenwarteschlange"; /* chat feature */ "Message reactions" = "Reaktionen auf Nachrichten"; @@ -3258,6 +3401,9 @@ snd error text */ /* No comment provided by engineer. */ "Messages & files" = "Nachrichten"; +/* No comment provided by engineer. */ +"Messages are protected by **end-to-end encryption**." = "Nachrichten sind durch **Ende-zu-Ende-Verschlüsselung** geschützt."; + /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!"; @@ -3423,6 +3569,9 @@ snd error text */ /* notification */ "New events" = "Neue Ereignisse"; +/* No comment provided by engineer. */ +"New group role: Moderator" = "Neue Gruppen-Rolle: Moderator"; + /* No comment provided by engineer. */ "New in %@" = "Neu in %@"; @@ -3432,6 +3581,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Neue Mitgliedsrolle"; +/* rcv group event chat item */ +"New member wants to join the group." = "Ein neues Mitglied will der Gruppe beitreten."; + /* notification */ "new message" = "Neue Nachricht"; @@ -3471,6 +3623,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Keine Chats in der Liste %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Keine Chats mit Mitgliedern"; + /* No comment provided by engineer. */ "No contacts selected" = "Keine Kontakte ausgewählt"; @@ -3522,6 +3677,9 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten"; +/* alert title */ +"No private routing session" = "Keine private Routing-Sitzung"; + /* No comment provided by engineer. */ "No push server" = "Lokal"; @@ -3555,6 +3713,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Nicht kompatibel!"; +/* No comment provided by engineer. */ +"not synchronized" = "Nicht synchronisiert"; + /* No comment provided by engineer. */ "Notes" = "Anmerkungen"; @@ -3587,6 +3748,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "Aus"; @@ -3599,7 +3761,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "angeboten %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3659,6 +3823,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only you can send disappearing messages." = "Nur Sie können verschwindende Nachrichten senden."; +/* No comment provided by engineer. */ +"Only you can send files and media." = "Nur Sie können Dateien und Medien senden."; + /* No comment provided by engineer. */ "Only you can send voice messages." = "Nur Sie können Sprachnachrichten versenden."; @@ -3674,6 +3841,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send disappearing messages." = "Nur Ihr Kontakt kann verschwindende Nachrichten senden."; +/* No comment provided by engineer. */ +"Only your contact can send files and media." = "Nur Ihr Kontakt kann Dateien und Medien senden."; + /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden."; @@ -3683,24 +3853,51 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Änderungen öffnen"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Chat öffnen"; /* authentication reason */ "Open chat console" = "Chat-Konsole öffnen"; +/* alert action */ +"Open clean link" = "Trackingfreien Link öffnen"; + /* No comment provided by engineer. */ "Open conditions" = "Nutzungsbedingungen öffnen"; -/* No comment provided by engineer. */ +/* alert action */ +"Open full link" = "Vollständigen Link öffnen"; + +/* new chat action */ "Open group" = "Gruppe öffnen"; +/* alert title */ +"Open link?" = "Link öffnen?"; + /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; +/* new chat action */ +"Open new chat" = "Neuen Chat öffnen"; + +/* new chat action */ +"Open new group" = "Neue Gruppe öffnen"; + /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; +/* No comment provided by engineer. */ +"Open to accept" = "Zum Akzeptieren öffnen"; + +/* No comment provided by engineer. */ +"Open to connect" = "Zum Verbinden öffnen"; + +/* No comment provided by engineer. */ +"Open to join" = "Zum Beitreten öffnen"; + +/* No comment provided by engineer. */ +"Open to use bot" = "Bot für die Nutzung erlaubt"; + /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; @@ -3770,9 +3967,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Passwort anzeigen"; -/* past/unknown group member */ -"Past member %@" = "Ehemaliges Mitglied %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Desktop-Adresse einfügen"; @@ -3797,6 +3991,9 @@ time to disappear */ /* No comment provided by engineer. */ "pending approval" = "ausstehende Genehmigung"; +/* No comment provided by engineer. */ +"pending review" = "Ausstehende Überprüfung"; + /* No comment provided by engineer. */ "Periodic" = "Periodisch"; @@ -3827,7 +4024,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Überprüfen Sie bitte, ob Sie den richtigen Link genutzt haben oder bitten Sie Ihren Kontakt nochmal darum, Ihnen einen Link zuzusenden."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Bitte überprüfen Sie Ihre Netzwerkverbindung mit %@ und versuchen Sie es erneut."; /* No comment provided by engineer. */ @@ -3866,6 +4063,9 @@ time to disappear */ /* token info */ "Please try to disable and re-enable notfications." = "Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können."; + /* token info */ "Please wait for token activation to complete." = "Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist."; @@ -3878,9 +4078,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Port"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren."; @@ -3929,9 +4126,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Privates Routing"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Fehler beim privaten Routing"; +/* alert title */ +"Private routing timeout" = "Zeitüberschreitung der privaten Routing-Sitzung"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil und Serververbindungen"; @@ -3992,6 +4192,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais, die Ihre Kontakte ausgewählt haben.\nAktivieren Sie es in den *Netzwerk & Server* Einstellungen."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Protokoll Hintergrund-Zeitüberschreitung"; + /* No comment provided by engineer. */ "Protocol timeout" = "Protokollzeitüberschreitung"; @@ -4136,16 +4339,20 @@ time to disappear */ /* token status text */ "Registered" = "Registriert"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Ablehnen"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Kontakt ablehnen (der Absender wird NICHT benachrichtigt)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Kontaktanfrage ablehnen"; +/* alert title */ +"Reject member?" = "Mitglied ablehnen?"; + /* No comment provided by engineer. */ "rejected" = "abgelehnt"; @@ -4167,6 +4374,9 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Bild entfernen"; +/* No comment provided by engineer. */ +"Remove link tracking" = "Link-Tracking entfernen"; + /* No comment provided by engineer. */ "Remove member" = "Mitglied entfernen"; @@ -4185,12 +4395,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "Die Kontaktadresse wurde entfernt"; +/* No comment provided by engineer. */ +"removed from group" = "Aus der Gruppe entfernt"; + /* profile update event chat item */ "removed profile picture" = "Das Profil-Bild wurde entfernt"; /* rcv group event chat item */ "removed you" = "hat Sie aus der Gruppe entfernt"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Entfernt Nachrichten und blockiert Mitglieder."; + /* No comment provided by engineer. */ "Renegotiate" = "Neu aushandeln"; @@ -4200,18 +4416,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Verschlüsselung neu aushandeln?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Verbindungsanfrage wiederholen?"; - /* No comment provided by engineer. */ "Repeat download" = "Herunterladen wiederholen"; /* No comment provided by engineer. */ "Repeat import" = "Import wiederholen"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Verbindungsanfrage wiederholen?"; - /* No comment provided by engineer. */ "Repeat upload" = "Hochladen wiederholen"; @@ -4233,6 +4443,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Grund der Meldung?"; +/* alert title */ +"Report sent to moderators" = "Meldung wurde an die Moderatoren gesendet"; + /* report reason */ "Report spam: only group moderators will see it." = "Spam melden: Nur Gruppenmoderatoren werden es sehen."; @@ -4248,6 +4461,18 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Meldungen"; +/* No comment provided by engineer. */ +"request is sent" = "Anfrage wurde gesendet"; + +/* No comment provided by engineer. */ +"request to join rejected" = "Beitrittsanfrage abgelehnt"; + +/* rcv group event chat item */ +"requested connection" = "Angefragte Verbindung"; + +/* rcv direct event chat item */ +"requested connection from group %@" = "Angefragte Verbindung von Gruppe %@"; + /* chat list item title */ "requested to connect" = "Zur Verbindung aufgefordert"; @@ -4296,15 +4521,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Fehler bei der Wiederherstellung der Datenbank"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Wiederholen"; /* chat item action */ "Reveal" = "Aufdecken"; +/* No comment provided by engineer. */ +"review" = "Überprüfung"; + /* No comment provided by engineer. */ "Review conditions" = "Nutzungsbedingungen einsehen"; +/* No comment provided by engineer. */ +"Review group members" = "Gruppenmitglieder überprüfen"; + +/* admission stage */ +"Review members" = "Überprüfung der Mitglieder"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\")."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "Von Administratoren überprüft"; + /* No comment provided by engineer. */ "Revoke" = "Widerrufen"; @@ -4333,6 +4573,12 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)"; +/* alert button */ +"Save (and notify members)" = "Speichern (und Mitglieder benachrichtigen)"; + +/* alert title */ +"Save admission settings?" = "Speichern der Aufnahme-Einstellungen?"; + /* alert button */ "Save and notify contact" = "Speichern und Kontakt benachrichtigen"; @@ -4348,6 +4594,9 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; +/* alert title */ +"Save group profile?" = "Gruppenprofil speichern?"; + /* No comment provided by engineer. */ "Save list" = "Liste speichern"; @@ -4424,7 +4673,7 @@ chat item action */ "Search" = "Suche"; /* No comment provided by engineer. */ -"Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks akzeptiert."; +"Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks angenommen."; /* No comment provided by engineer. */ "Search or paste SimpleX link" = "Suchen oder SimpleX-Link einfügen"; @@ -4487,10 +4736,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Eine Live Nachricht senden - der/die Empfänger sieht/sehen Nachrichtenaktualisierungen, während Sie sie eingeben"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "Empfangsbestätigungen senden an"; +"Send contact request?" = "Kontakt-Anfrage senden?"; /* No comment provided by engineer. */ -"send direct message" = "Direktnachricht senden"; +"Send delivery receipts to" = "Empfangsbestätigungen senden an"; /* No comment provided by engineer. */ "Send direct message to connect" = "Eine Direktnachricht zum Verbinden senden"; @@ -4528,12 +4777,21 @@ chat item action */ /* No comment provided by engineer. */ "Send receipts" = "Bestätigungen senden"; +/* No comment provided by engineer. */ +"Send request" = "Anfrage senden"; + +/* No comment provided by engineer. */ +"Send request without message" = "Anfrage ohne Nachricht senden"; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden."; +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Senden Sie Ihr privates Feedback an Gruppen."; + /* alert message */ "Sender cancelled file transfer." = "Der Absender hat die Dateiübertragung abgebrochen."; @@ -4622,10 +4880,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "Server-Warteschlangen-Information: %1$@\n\nZuletzt empfangene Nachricht: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort"; +"Server requires authorization to create queues, check password." = "Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort"; /* server test error */ -"Server requires authorization to upload, check password" = "Bitte das Passwort überprüfen - für den Upload benötigt der Server eine Berechtigung"; +"Server requires authorization to upload, check password." = "Bitte das Passwort überprüfen - für den Upload benötigt der Server eine Berechtigung"; /* No comment provided by engineer. */ "Server test failed!" = "Server Test ist fehlgeschlagen!"; @@ -4669,6 +4927,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen."; +/* No comment provided by engineer. */ +"Set member admission" = "Aufnahme von Mitgliedern festlegen"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Verfallsdatum von Nachrichten in Chats festlegen."; @@ -4687,6 +4948,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Passwort für den Export festlegen"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Sie können eine Profil-Biografie und eine Begrüßungsmeldung eingeben."; + /* No comment provided by engineer. */ "Set the message shown to new members!" = "Definieren Sie eine Begrüßungsmeldung, die neuen Mitgliedern angezeigt wird!"; @@ -4727,6 +4991,12 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Link teilen"; +/* alert button */ +"Share old address" = "Alte Adresse teilen"; + +/* alert button */ +"Share old link" = "Vollständigen Link teilen"; + /* No comment provided by engineer. */ "Share profile" = "Profil teilen"; @@ -4742,9 +5012,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Mit Kontakten teilen"; +/* No comment provided by engineer. */ +"Share your address" = "Ihre Adresse teilen"; + +/* No comment provided by engineer. */ +"Short description" = "Kurze Beschreibung"; + /* No comment provided by engineer. */ "Short link" = "Verkürzter Link"; +/* No comment provided by engineer. */ +"Short SimpleX address" = "Verkürzte SimpleX-Adresse"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Bei Nachrichten, die über privates Routing versendet wurden, → anzeigen."; @@ -4787,6 +5066,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-Adresse oder Einmal-Link?"; +/* alert title */ +"SimpleX address settings" = "Einstellungen automatisch akzeptieren"; + /* simplex link type */ "SimpleX channel link" = "SimpleX-Kanal-Link"; @@ -4832,6 +5114,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Die SimpleX-Protokolle wurden von Trail of Bits überprüft."; +/* simplex link type */ +"SimpleX relay link" = "SimpleX Relais-Link"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereinfachter Inkognito-Modus"; @@ -4980,9 +5265,21 @@ report reason */ /* No comment provided by engineer. */ "Tap button " = "Schaltfläche antippen "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Verbinden tippen, um zu chatten"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Verbinden tippen, um die Anfrage zu senden"; + +/* No comment provided by engineer. */ +"Tap Connect to use bot" = "Verbinden tippen, um den Bot zu nutzen."; + /* No comment provided by engineer. */ "Tap Create SimpleX address in the menu to create it later." = "Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen."; +/* No comment provided by engineer. */ +"Tap Join group" = "Tippen, um der Gruppe beizutreten"; + /* No comment provided by engineer. */ "Tap to activate profile." = "Zum Aktivieren des Profils tippen."; @@ -5004,6 +5301,9 @@ report reason */ /* No comment provided by engineer. */ "TCP connection" = "TCP-Verbindung"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "TCP-Verbindung Hintergrund-Zeitüberschreitung"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Timeout der TCP-Verbindung"; @@ -5046,6 +5346,9 @@ report reason */ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Dank der Nutzer - Tragen Sie per Weblate bei!"; +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "Die Adresse wird gekürzt sein, und Ihr Profil wird über die Adresse geteilt."; + /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; @@ -5085,6 +5388,9 @@ report reason */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Die ID der nächsten Nachricht ist falsch (kleiner oder gleich der Vorherigen).\nDies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompromittiert wurde."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "Der Link wird gekürzt sein, und das Gruppen-Profil wird über den Link geteilt."; + /* No comment provided by engineer. */ "The message will be deleted for all members." = "Diese Nachricht wird für alle Gruppenmitglieder gelöscht."; @@ -5100,9 +5406,6 @@ report reason */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; @@ -5112,7 +5415,7 @@ report reason */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Wir haben das zweite Häkchen vermisst! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Der Absender wird NICHT benachrichtigt"; /* No comment provided by engineer. */ @@ -5140,16 +5443,16 @@ report reason */ "They can be overridden in contact and group settings." = "Sie können in den Kontakteinstellungen überschrieben werden."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden!"; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Dieser Vorgang kann nicht rückgängig gemacht werden - alle empfangenen und gesendeten Dateien sowie Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten."; /* No comment provided by engineer. */ -"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. Diese Aktion kann nicht rückgängig gemacht werden!"; +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Dieser Vorgang kann nicht rückgängig gemacht werden - die früher als ausgewählt gesendeten und empfangenen Nachrichten werden gelöscht. Dies kann ein paar Minuten dauern."; /* alert message */ -"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Die älteren als die ausgewählten gesendeten und empfangenen Nachrichten in diesem Chat werden gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!"; +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Dieser Vorgang kann nicht rückgängig gemacht werden - die in diesem Chat früher als ausgewählt gesendeten und empfangenen Nachrichten werden gelöscht."; /* No comment provided by engineer. */ -"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. Diese Aktion kann nicht rückgängig gemacht werden!"; +"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Ihr Profil, Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. Diese Aktion kann nicht rückgängig gemacht werden!"; /* E2EE info chat item */ "This chat is protected by end-to-end encryption." = "Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt."; @@ -5172,12 +5475,6 @@ report reason */ /* No comment provided by engineer. */ "This group no longer exists." = "Diese Gruppe existiert nicht mehr."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "Das ist Ihr eigener Einmal-Link!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Das ist Ihre eigene SimpleX-Adresse!"; - /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden."; @@ -5190,6 +5487,12 @@ report reason */ /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**."; +/* No comment provided by engineer. */ +"This setting is for your current profile **%@**." = "Diese Einstellung gilt für Ihr aktuelles Profil **%@**."; + +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Die Zeit bis zum Verschwinden wird nur für neue Kontakte eingestellt."; + /* No comment provided by engineer. */ "Title" = "Bezeichnung"; @@ -5238,9 +5541,15 @@ report reason */ /* No comment provided by engineer. */ "To send" = "Für das Senden"; +/* alert message */ +"To send commands you must be connected." = "Um Befehle senden zu können, müssen Sie verbunden sein."; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Wenn Sie nach dem Verbindungsversuch ein anderes Profil verwenden möchten, löschen Sie den Chat und verwenden Sie den Link erneut."; + /* No comment provided by engineer. */ "To use the servers of **%@**, accept conditions of use." = "Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren."; @@ -5403,9 +5712,27 @@ report reason */ /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden."; +/* alert button */ +"Upgrade" = "Aktualisieren"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Adresse aktualisieren"; + +/* alert message */ +"Upgrade address?" = "Adresse aktualisieren?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Aktualisieren und den Chat öffnen"; +/* alert message */ +"Upgrade group link?" = "Gruppen-Link aktualisieren?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Link hinzufügen"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Ihre Adresse aktualisieren"; + /* No comment provided by engineer. */ "Upload errors" = "Fehler beim Hochladen"; @@ -5433,7 +5760,7 @@ report reason */ /* No comment provided by engineer. */ "Use chat" = "Verwenden Sie Chat"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Aktuelles Profil nutzen"; /* No comment provided by engineer. */ @@ -5449,9 +5776,12 @@ report reason */ "Use from desktop" = "Vom Desktop aus nutzen"; /* No comment provided by engineer. */ -"Use iOS call interface" = "iOS Anrufschnittstelle nutzen"; +"Use incognito profile" = "Inkognito-Profil nutzen"; /* No comment provided by engineer. */ +"Use iOS call interface" = "iOS Anrufschnittstelle nutzen"; + +/* new chat action */ "Use new incognito profile" = "Neues Inkognito-Profil nutzen"; /* No comment provided by engineer. */ @@ -5469,9 +5799,6 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Verwende Server"; -/* No comment provided by engineer. */ -"Use short links (BETA)" = "Kurze Links verwenden (BETA)"; - /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Verwenden Sie SimpleX-Chat-Server?"; @@ -5643,6 +5970,9 @@ report reason */ /* No comment provided by engineer. */ "Welcome message is too long" = "Die Begrüßungsmeldung ist zu lang"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Begrüßen Sie Ihre Kontakte 👋"; + /* No comment provided by engineer. */ "What's new" = "Was ist neu"; @@ -5710,7 +6040,10 @@ report reason */ "You **must not** use the same database on two devices." = "Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen."; /* No comment provided by engineer. */ -"You accepted connection" = "Sie haben die Verbindung akzeptiert"; +"You accepted connection" = "Sie haben die Verbindung angenommen"; + +/* snd group event chat item */ +"you accepted this member" = "Sie haben dieses Mitglied angenommen"; /* No comment provided by engineer. */ "You allow" = "Sie erlauben"; @@ -5724,33 +6057,27 @@ report reason */ /* No comment provided by engineer. */ "You are already connected with %@." = "Sie sind bereits mit %@ verbunden."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Sie sind bereits mit %@ verbunden."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Sie sind bereits über diesen Einmal-Link verbunden!"; /* No comment provided by engineer. */ "You are already in group %@." = "Sie sind bereits Mitglied der Gruppe %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Sie sind bereits Mitglied der Gruppe %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Sie sind über diesen Link bereits Mitglied der Gruppe!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Sie sind über diesen Link bereits Mitglied der Gruppe."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Sie sind bereits Mitglied dieser Gruppe!\nVerbindungsanfrage wiederholen?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Sie sind mit dem Server verbunden, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird."; -/* No comment provided by engineer. */ -"you are invited to group" = "Sie sind zu der Gruppe eingeladen"; - /* No comment provided by engineer. */ "You are invited to group" = "Sie sind zu der Gruppe eingeladen"; @@ -5823,7 +6150,10 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "Sie können Ihre Meldungen im Chat mit den Administratoren sehen."; + +/* alert title */ "You can't send messages!" = "Sie können keine Nachrichten versenden!"; /* chat item text */ @@ -5844,10 +6174,7 @@ report reason */ /* No comment provided by engineer. */ "You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Sie haben über diese Adresse bereits eine Verbindung beantragt!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Sie haben bereits ein Verbindungsanfrage beantragt!\nVerbindungsanfrage wiederholen?"; /* No comment provided by engineer. */ @@ -5901,6 +6228,9 @@ report reason */ /* snd group event chat item */ "you unblocked %@" = "Sie haben %@ freigegeben"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Sie können erst dann Nachrichten versenden, **sobald Ihre Anfrage angenommen wurde**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!"; @@ -5908,7 +6238,7 @@ report reason */ "You will be connected when group link host's device is online, please wait or check later!" = "Sie werden verbunden, sobald das Endgerät des Gruppenlink-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach!"; /* No comment provided by engineer. */ -"You will be connected when your connection request is accepted, please wait or check later!" = "Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach!"; +"You will be connected when your connection request is accepted, please wait or check later!" = "Sie werden verbunden, sobald Ihre Verbindungsanfrage angenommen wird. Bitte warten oder schauen Sie später nochmal nach!"; /* No comment provided by engineer. */ "You will be connected when your contact's device is online, please wait or check later!" = "Sie werden verbunden, sobald das Endgerät Ihres Kontakts online ist. Bitte warten oder schauen Sie später nochmal nach!"; @@ -5916,9 +6246,6 @@ report reason */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Sie müssen sich authentifizieren, wenn Sie die im Hintergrund befindliche App nach 30 Sekunden starten oder fortsetzen."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Sie werden mit allen Gruppenmitgliedern verbunden."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind."; @@ -5940,6 +6267,9 @@ report reason */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt"; +/* No comment provided by engineer. */ +"Your business contact" = "Ihr geschäftlicher Kontakt"; + /* No comment provided by engineer. */ "Your calls" = "Anrufe"; @@ -5955,8 +6285,14 @@ report reason */ /* No comment provided by engineer. */ "Your chat profiles" = "Ihre Chat-Profile"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ihr Chat wurde nach %@ verschoben, aber es ist ein unerwarteter Fehler aufgetreten, als Sie zum Profil weitergeleitet wurden."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf."; +"Your connection was moved to %@ but an error happened when switching profile." = "Ihre Verbindung wurde auf %@ verschoben. Während Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf."; + +/* No comment provided by engineer. */ +"Your contact" = "Ihr Kontakt"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@)."; @@ -5976,6 +6312,9 @@ report reason */ /* No comment provided by engineer. */ "Your current profile" = "Mein aktuelles Chat-Profil"; +/* No comment provided by engineer. */ +"Your group" = "Ihre Gruppe"; + /* No comment provided by engineer. */ "Your ICE servers" = "Ihre ICE-Server"; @@ -5991,15 +6330,15 @@ report reason */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert."; - /* No comment provided by engineer. */ "Your random profile" = "Ihr Zufallsprofil"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 28ba0f0642..6c8232fb63 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -342,22 +342,38 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Aceptar"; +/* alert action */ +"Accept as member" = "Aceptar como miembro"; + +/* alert action */ +"Accept as observer" = "Aceptar como observador"; + /* No comment provided by engineer. */ "Accept conditions" = "Aceptar condiciones"; /* No comment provided by engineer. */ "Accept connection request?" = "¿Aceptar solicitud de conexión?"; +/* alert title */ +"Accept contact request" = "Aceptar petición de contacto"; + /* notification body */ "Accept contact request from %@?" = "¿Aceptar solicitud de contacto de %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Aceptar incógnito"; +/* alert title */ +"Accept member" = "Aceptar miembro"; + +/* rcv group event chat item */ +"accepted %@" = "%@ aceptado"; + /* call status */ "accepted call" = "llamada aceptada"; @@ -367,6 +383,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "invitación aceptada"; +/* rcv group event chat item */ +"accepted you" = "te ha aceptado"; + /* No comment provided by engineer. */ "Acknowledged" = "Confirmaciones"; @@ -388,6 +407,9 @@ swipe action */ /* No comment provided by engineer. */ "Add list" = "Añadir lista"; +/* placeholder for sending contact request */ +"Add message" = "Añadir mensaje"; + /* No comment provided by engineer. */ "Add profile" = "Añadir perfil"; @@ -463,6 +485,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "acordando cifrado…"; +/* member criteria value */ +"all" = "todos"; + /* No comment provided by engineer. */ "All" = "Todo"; @@ -527,13 +552,16 @@ swipe action */ "Allow calls?" = "¿Permitir llamadas?"; /* No comment provided by engineer. */ -"Allow disappearing messages only if your contact allows it to you." = "Se permiten los mensajes temporales pero sólo si tu contacto también los permite para tí."; +"Allow disappearing messages only if your contact allows it to you." = "Se permiten los mensajes temporales pero sólo si tu contacto también los permite."; /* No comment provided by engineer. */ "Allow downgrade" = "Permitir versión anterior"; /* No comment provided by engineer. */ -"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también la permite para tí. (24 horas)"; +"Allow files and media only if your contact allows them." = "Se permiten archivos y multimedia pero sólo si tu contacto también los permite."; + +/* No comment provided by engineer. */ +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite. (24 horas)"; /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Se permiten las reacciones a los mensajes pero sólo si tu contacto también las permite."; @@ -572,27 +600,30 @@ swipe action */ "Allow voice messages?" = "¿Permites los mensajes de voz?"; /* No comment provided by engineer. */ -"Allow your contacts adding message reactions." = "Permitir que tus contactos añadan reacciones a los mensajes."; +"Allow your contacts adding message reactions." = "Permites que tus contactos añadan reacciones a los mensajes."; /* No comment provided by engineer. */ -"Allow your contacts to call you." = "Permites que tus contactos puedan llamarte."; +"Allow your contacts to call you." = "Permites que tus contactos te llamen."; /* No comment provided by engineer. */ -"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Permites a tus contactos eliminar irreversiblemente los mensajes enviados. (24 horas)"; +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Permites que tus contactos eliminan irreversiblemente los mensajes enviados. (24 horas)"; /* No comment provided by engineer. */ -"Allow your contacts to send disappearing messages." = "Permites a tus contactos enviar mensajes temporales."; +"Allow your contacts to send disappearing messages." = "Permites que tus contactos envien mensajes temporales."; /* No comment provided by engineer. */ -"Allow your contacts to send voice messages." = "Permites a tus contactos enviar mensajes de voz."; +"Allow your contacts to send files and media." = "Permes que tus contactos envíen archivos y multimedia."; + +/* No comment provided by engineer. */ +"Allow your contacts to send voice messages." = "Permites que tus contactos envien mensajes de voz."; /* No comment provided by engineer. */ "Already connected?" = "¿Ya está conectado?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "¡Ya en proceso de conexión!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "¡Ya en proceso de unirte al grupo!"; /* pref value */ @@ -698,7 +729,7 @@ swipe action */ "attempts" = "intentos"; /* No comment provided by engineer. */ -"Audio & video calls" = "Llamadas y videollamadas"; +"Audio & video calls" = "Llamadas y Videollamadas"; /* No comment provided by engineer. */ "Audio and video calls" = "Llamadas y videollamadas"; @@ -736,9 +767,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Aceptar imágenes automáticamente"; -/* alert title */ -"Auto-accept settings" = "Auto aceptar configuración"; - /* No comment provided by engineer. */ "Back" = "Volver"; @@ -790,6 +818,12 @@ swipe action */ /* No comment provided by engineer. */ "Better user experience" = "Experiencia de usuario mejorada"; +/* No comment provided by engineer. */ +"Bio" = "Biografía"; + +/* alert title */ +"Bio too large" = "Biografía demasiado larga"; + /* No comment provided by engineer. */ "Black" = "Negro"; @@ -833,6 +867,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "bold" = "negrita"; +/* No comment provided by engineer. */ +"Bot" = "Bot"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "Tanto tú como tu contacto podéis añadir reacciones a los mensajes."; @@ -845,6 +882,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Tanto tú como tu contacto podéis enviar mensajes temporales."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Tanto tú como tu contacto podéis enviar archivos y multimedia."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Tanto tú como tu contacto podéis enviar mensajes de voz."; @@ -857,6 +897,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Chats empresariales"; +/* No comment provided by engineer. */ +"Business connection" = "Conexión empresarial"; + /* No comment provided by engineer. */ "Businesses" = "Empresas"; @@ -896,6 +939,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "No se puede llamar al miembro"; +/* alert title */ +"Can't change profile" = "No se puede cambiar el perfil"; + /* No comment provided by engineer. */ "Can't invite contact!" = "¡No se puede invitar el contacto!"; @@ -905,8 +951,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "No se pueden enviar mensajes al miembro"; +/* No comment provided by engineer. */ +"can't send messages" = "no se pueden enviar mensajes"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Cancelar"; /* No comment provided by engineer. */ @@ -988,7 +1038,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "El chat ya existe"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "¡El chat ya existe!"; /* No comment provided by engineer. */ @@ -1042,9 +1092,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No puede deshacerse!"; +/* chat toolbar */ +"Chat with admins" = "Chatea con administradores"; + +/* No comment provided by engineer. */ +"Chat with member" = "Chat con miembro"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Chatea con el miembro antes de unirse."; + /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Chats with members" = "Chat con miembros"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Comprobar mensajes cada 20 min."; @@ -1061,7 +1123,7 @@ set passcode view */ "Choose _Migrate from another device_ on the new device and scan QR code." = "En el nuevo dispositivo selecciona _Migrar desde otro dispositivo_ y escanéa el código QR."; /* No comment provided by engineer. */ -"Choose file" = "Elije archivo"; +"Choose file" = "Elegir archivo"; /* No comment provided by engineer. */ "Choose from library" = "Elige de la biblioteca"; @@ -1187,7 +1249,7 @@ set passcode view */ "Connect automatically" = "Conectar automáticamente"; /* No comment provided by engineer. */ -"Connect incognito" = "Conectar incognito"; +"Connect faster! 🚀" = "¡Conéctate más rápido! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Conectar con ordenador"; @@ -1196,27 +1258,24 @@ set passcode view */ "connect to SimpleX Chat developers." = "contacta con los desarrolladores de SimpleX Chat."; /* No comment provided by engineer. */ -"Connect to your friends faster." = "Conecta más rápido con tus amigos."; +"Connect to your friends faster." = "Conéctate más rápido con tus amigos."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "¿Conectarte a tí mismo?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "¿Conectarte a tí mismo?\n¡Este es tu propio enlace de un solo uso!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "¿Conectarte a tí mismo?\n¡Esta es tu propia dirección SimpleX!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Conectar mediante dirección de contacto"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Conectar mediante enlace"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Conectar mediante enlace de un sólo uso"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Conectar con %@"; /* No comment provided by engineer. */ @@ -1228,9 +1287,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Ordenador conectado"; -/* rcv group event chat item */ -"connected directly" = "conectado directamente"; - /* No comment provided by engineer. */ "Connected servers" = "Servidores conectados"; @@ -1282,7 +1338,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "Conexión bloqueada"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Error conexión"; /* No comment provided by engineer. */ @@ -1312,7 +1368,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Conexión finalizada"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Tiempo de conexión agotado"; /* No comment provided by engineer. */ @@ -1333,9 +1389,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "El contácto ya existe"; +/* No comment provided by engineer. */ +"contact deleted" = "contacto eliminado"; + /* No comment provided by engineer. */ "Contact deleted!" = "¡Contacto eliminado!"; +/* No comment provided by engineer. */ +"contact disabled" = "contacto desactivado"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "el contacto dispone de cifrado de extremo a extremo"; @@ -1354,9 +1416,18 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Contacto"; +/* No comment provided by engineer. */ +"contact not ready" = "en espera de ser aceptado"; + /* No comment provided by engineer. */ "Contact preferences" = "Preferencias de contacto"; +/* No comment provided by engineer. */ +"Contact requests from groups" = "Solicitudes de contacto en grupo"; + +/* No comment provided by engineer. */ +"contact should accept…" = "el contacto debe aceptarte…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "El contacto será eliminado. ¡No puede deshacerse!"; @@ -1394,7 +1465,7 @@ set passcode view */ "Create" = "Crear"; /* No comment provided by engineer. */ -"Create 1-time link" = "Crear enlace de un uso"; +"Create 1-time link" = "Crear enlace de un solo uso"; /* No comment provided by engineer. */ "Create a group using a random profile." = "Crear grupo usando perfil aleatorio."; @@ -1424,10 +1495,10 @@ set passcode view */ "Create queue" = "Crear cola"; /* No comment provided by engineer. */ -"Create secret group" = "Crea grupo secreto"; +"Create SimpleX address" = "Crear dirección SimpleX"; /* No comment provided by engineer. */ -"Create SimpleX address" = "Crear dirección SimpleX"; +"Create your address" = "Crea tu dirección"; /* No comment provided by engineer. */ "Create your profile" = "Crea tu perfil"; @@ -1602,6 +1673,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "¿Eliminar perfil?"; +/* alert title */ +"Delete chat with member?" = "¿Eliminar chat con miembro?"; + /* No comment provided by engineer. */ "Delete chat?" = "¿Eliminar chat?"; @@ -1728,9 +1802,15 @@ swipe action */ /* No comment provided by engineer. */ "Delivery receipts!" = "¡Confirmación de entrega!"; +/* No comment provided by engineer. */ +"Deprecated options" = "Opciones obsoletas"; + /* No comment provided by engineer. */ "Description" = "Descripción"; +/* alert title */ +"Description too large" = "Descripción demasiado larga"; + /* No comment provided by engineer. */ "Desktop address" = "Dirección ordenador"; @@ -1852,7 +1932,7 @@ swipe action */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado."; /* No comment provided by engineer. */ -"Do not use credentials with proxy." = "No uses credenciales con proxy."; +"Do not use credentials with proxy." = "No se usan credenciales con proxy."; /* No comment provided by engineer. */ "Do NOT use private routing." = "NO usar enrutamiento privado."; @@ -1872,7 +1952,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "No pierdas los mensajes importantes."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "No volver a mostrar"; /* No comment provided by engineer. */ @@ -1933,6 +2013,9 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Editar perfil de grupo"; +/* No comment provided by engineer. */ +"Empty message!" = "¡Mensaje vacío!"; + /* No comment provided by engineer. */ "Enable" = "Activar"; @@ -1945,6 +2028,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Permitir acceso a la cámara"; +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Activa por defecto los mensajes temporales."; + /* No comment provided by engineer. */ "Enable Flux in Network & servers settings for better metadata privacy." = "Habilitar Flux en la configuración de Red y servidores para mejorar la privacidad de los metadatos."; @@ -1955,7 +2041,7 @@ chat item action */ "Enable in direct chats (BETA)!" = "¡Activar en chats directos (BETA)!"; /* No comment provided by engineer. */ -"Enable instant notifications?" = "¿Activar notificación instantánea?"; +"Enable instant notifications?" = "¿Activar notificaciones instantáneas?"; /* No comment provided by engineer. */ "Enable lock" = "Activar bloqueo"; @@ -2093,10 +2179,10 @@ chat item action */ "Enter this device name…" = "Nombre de este dispositivo…"; /* placeholder */ -"Enter welcome message…" = "Introduce mensaje de bienvenida…"; +"Enter welcome message…" = "Deja un mensaje de bienvenida…"; /* placeholder */ -"Enter welcome message… (optional)" = "Introduce mensaje de bienvenida… (opcional)"; +"Enter welcome message… (optional)" = "Deja un mensaje de bienvenida… (opcional)"; /* No comment provided by engineer. */ "Enter your name…" = "Introduce tu nombre…"; @@ -2116,22 +2202,31 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Error al aceptar solicitud del contacto"; +/* alert title */ +"Error accepting member" = "Error al aceptar el miembro"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; /* alert title */ "Error adding server" = "Error al añadir servidor"; +/* No comment provided by engineer. */ +"Error adding short link" = "Error al añadir enlace corto"; + /* No comment provided by engineer. */ "Error changing address" = "Error al cambiar servidor"; +/* alert title */ +"Error changing chat profile" = "Error al cambiar perfil de chat"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Error al cambiar el perfil de conexión"; /* No comment provided by engineer. */ "Error changing role" = "Error al cambiar rol"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Error cambiando configuración"; /* No comment provided by engineer. */ @@ -2140,7 +2235,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Error al verificar el estado del token"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Error al conectar con el servidor de reenvío %@. Por favor, inténtalo más tarde."; /* No comment provided by engineer. */ @@ -2170,19 +2265,22 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Error al descifrar el archivo"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Error al eliminar el chat con el miembro"; + +/* alert title */ "Error deleting chat database" = "Error al eliminar base de datos"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "¡Error al eliminar chat!"; /* No comment provided by engineer. */ "Error deleting connection" = "Error al eliminar conexión"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Error al eliminar base de datos"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Error al eliminar base de datos antigua"; /* No comment provided by engineer. */ @@ -2203,13 +2301,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Error al cifrar base de datos"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Error al exportar base de datos"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Error al exportar tema: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Error al importar base de datos"; /* No comment provided by engineer. */ @@ -2224,6 +2322,9 @@ chat item action */ /* No comment provided by engineer. */ "Error opening chat" = "Error al abrir chat"; +/* No comment provided by engineer. */ +"Error opening group" = "Error al abrir el grupo"; + /* alert title */ "Error receiving file" = "Error al recibir archivo"; @@ -2236,7 +2337,10 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Error al registrarse para notificaciones"; -/* No comment provided by engineer. */ +/* alert title */ +"Error rejecting contact request" = "Error al rechazar la solicitud del contacto"; + +/* alert title */ "Error removing member" = "Error al expulsar miembro"; /* alert title */ @@ -2281,6 +2385,9 @@ chat item action */ /* No comment provided by engineer. */ "Error sending message" = "Error al enviar mensaje"; +/* No comment provided by engineer. */ +"Error setting auto-accept" = "Error al configurar auto aceptar"; + /* No comment provided by engineer. */ "Error setting delivery receipts!" = "¡Error al configurar confirmaciones de entrega!"; @@ -2290,7 +2397,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Error al parar SimpleX"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Error al cambiar perfil"; /* alertTitle */ @@ -2439,6 +2546,9 @@ snd error text */ /* chat feature */ "Files and media" = "Archivos y multimedia"; +/* No comment provided by engineer. */ +"Files and media are prohibited in this chat." = "Los archivos y multimedia no están permitidos en este chat."; + /* No comment provided by engineer. */ "Files and media are prohibited." = "Los archivos y multimedia no están permitidos en este grupo."; @@ -2463,6 +2573,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Encuentra chats mas rápido"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Posiblemente la huella del certificado en la dirección del servidor es incorrecta"; + /* No comment provided by engineer. */ "Fix" = "Reparar"; @@ -2532,8 +2645,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "Reenviando %lld mensajes"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "El servidor de reenvío %@ no ha podido conectarse al servidor de destino %@. Por favor, intentalo más tarde."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "El servidor de reenvío %1$@ no ha podido conectarse al servidor de destino %2$@. Por favor, intentalo más tarde."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "La dirección del servidor de reenvío es incompatible con la configuración de red: %@."; @@ -2580,13 +2693,16 @@ snd error text */ /* message preview */ "Good morning!" = "¡Buenos días!"; +/* shown on group welcome message */ +"group" = "grupo"; + /* No comment provided by engineer. */ "Group" = "Grupo"; /* No comment provided by engineer. */ "Group already exists" = "El grupo ya existe"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "¡El grupo ya existe!"; /* No comment provided by engineer. */ @@ -2610,6 +2726,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "La invitación al grupo ya no es válida, ha sido eliminada por el remitente."; +/* No comment provided by engineer. */ +"group is deleted" = "el grupo ha sido eliminado"; + /* No comment provided by engineer. */ "Group link" = "Enlace de grupo"; @@ -2634,6 +2753,9 @@ snd error text */ /* snd group event chat item */ "group profile updated" = "perfil de grupo actualizado"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "El perfil del grupo ha cambiado. Si lo guardas, el perfil actualizado se enviará a los miembros del grupo."; + /* No comment provided by engineer. */ "Group welcome message" = "Mensaje de bienvenida en grupos"; @@ -2671,7 +2793,7 @@ snd error text */ "Hide profile" = "Ocultar perfil"; /* No comment provided by engineer. */ -"Hide:" = "Ocultar:"; +"Hide:" = "Oculta:"; /* No comment provided by engineer. */ "History" = "Historial"; @@ -2880,7 +3002,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "¡Nombre mostrado no válido!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Enlace no válido"; /* No comment provided by engineer. */ @@ -2977,27 +3099,21 @@ snd error text */ "Japanese interface" = "Interfáz en japonés"; /* swipe action */ -"Join" = "Unirte"; +"Join" = "Unirme"; /* No comment provided by engineer. */ -"join as %@" = "unirte como %@"; +"Join as %@" = "Unirme como %@"; -/* No comment provided by engineer. */ -"Join group" = "Unirte al grupo"; +/* new chat sheet title */ +"Join group" = "Unirme al grupo"; /* No comment provided by engineer. */ "Join group conversations" = "Unirse a la conversación del grupo"; /* No comment provided by engineer. */ -"Join group?" = "¿Unirte al grupo?"; +"Join incognito" = "Unirme en modo incógnito"; -/* No comment provided by engineer. */ -"Join incognito" = "Unirte en modo incógnito"; - -/* No comment provided by engineer. */ -"Join with current profile" = "Unirte con el perfil actual"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "¿Unirse a tu grupo?\n¡Este es tu enlace para el grupo %@!"; /* No comment provided by engineer. */ @@ -3013,7 +3129,10 @@ snd error text */ "Keep the app open to use it from desktop" = "Mantén la aplicación abierta para usarla desde el ordenador"; /* alert title */ -"Keep unused invitation?" = "¿Guardar invitación no usada?"; +"Keep unused invitation?" = "¿Guardar enlace no usado?"; + +/* No comment provided by engineer. */ +"Keep your chats clean" = "Mantén los chats limpios"; /* No comment provided by engineer. */ "Keep your connections" = "Conserva tus conexiones"; @@ -3048,6 +3167,9 @@ snd error text */ /* rcv group event chat item */ "left" = "ha salido"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Menos tráfico en redes móviles."; + /* email subject */ "Let's talk in SimpleX Chat" = "Hablemos en SimpleX Chat"; @@ -3084,6 +3206,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "Mensajes en vivo"; +/* in progress text */ +"Loading profile…" = "Cargando perfil. . ."; + /* No comment provided by engineer. */ "Local name" = "Nombre local"; @@ -3135,20 +3260,32 @@ snd error text */ /* No comment provided by engineer. */ "Member" = "Miembro"; +/* past/unknown group member */ +"Member %@" = "Miembro %@"; + /* profile update event chat item */ "member %@ changed to %@" = "el miembro %1$@ ha cambiado a %2$@"; +/* No comment provided by engineer. */ +"Member admission" = "Admisión de miembros"; + /* rcv group event chat item */ "member connected" = "conectado"; +/* No comment provided by engineer. */ +"member has old version" = "el miembro usa una versión antigua"; + /* item status text */ "Member inactive" = "Miembro inactivo"; +/* No comment provided by engineer. */ +"Member is deleted - can't accept request" = "Miembro eliminado, no puede aceptar solicitudes"; + /* chat feature */ "Member reports" = "Informes de miembros"; /* No comment provided by engineer. */ -"Member role will be changed to \"%@\". All chat members will be notified." = "El rol del miembro cambiará a \"%@\" y todos serán notificados."; +"Member role will be changed to \"%@\". All chat members will be notified." = "El rol del miembro cambiará a \"%@\". Se notificará en el chat."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "El rol del miembro cambiará a \"%@\" y se notificará al grupo."; @@ -3162,6 +3299,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No puede deshacerse!"; +/* alert message */ +"Member will join the group, accept member?" = "El miembro se unirá al grupo, ¿aceptas al miembro?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; @@ -3210,6 +3350,9 @@ snd error text */ /* item status text */ "Message forwarded" = "Mensaje reenviado"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Tras pulsar Contactar, mensajea ya."; + /* item status description */ "Message may be delivered later if member becomes active." = "El mensaje podría ser entregado más tarde si el miembro vuelve a estar activo."; @@ -3259,7 +3402,10 @@ snd error text */ "Messages & files" = "Mensajes"; /* No comment provided by engineer. */ -"Messages from %@ will be shown!" = "¡Los mensajes de %@ serán mostrados!"; +"Messages are protected by **end-to-end encryption**." = "Los mensajes están protegidos mediante **cifrado de extremo a extremo**."; + +/* No comment provided by engineer. */ +"Messages from %@ will be shown!" = "¡Los mensajes nuevos de %@ serán mostrados!"; /* alert message */ "Messages in this chat will never be deleted." = "Los mensajes de esta conversación nunca se eliminan."; @@ -3423,6 +3569,9 @@ snd error text */ /* notification */ "New events" = "Eventos nuevos"; +/* No comment provided by engineer. */ +"New group role: Moderator" = "Nuevo rol de grupo: Moderador"; + /* No comment provided by engineer. */ "New in %@" = "Nuevo en %@"; @@ -3432,6 +3581,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Nuevo rol de miembro"; +/* rcv group event chat item */ +"New member wants to join the group." = "Un miembro nuevo desea unirse al grupo."; + /* notification */ "new message" = "mensaje nuevo"; @@ -3471,6 +3623,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Sin chats en la lista %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Sin chats"; + /* No comment provided by engineer. */ "No contacts selected" = "Ningún contacto seleccionado"; @@ -3522,6 +3677,9 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Sin permiso para grabar mensajes de voz"; +/* alert title */ +"No private routing session" = "Ninguna sesión con enrutamiento privado"; + /* No comment provided by engineer. */ "No push server" = "Sin servidores push"; @@ -3555,6 +3713,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "¡No compatible!"; +/* No comment provided by engineer. */ +"not synchronized" = "no sincronizado"; + /* No comment provided by engineer. */ "Notes" = "Notas"; @@ -3587,6 +3748,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "desactivado"; @@ -3599,7 +3761,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "ofrecido %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3659,6 +3823,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only you can send disappearing messages." = "Sólo tú puedes enviar mensajes temporales."; +/* No comment provided by engineer. */ +"Only you can send files and media." = "Sólo tú puedes enviar archivos y multimedia."; + /* No comment provided by engineer. */ "Only you can send voice messages." = "Sólo tú puedes enviar mensajes de voz."; @@ -3674,6 +3841,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send disappearing messages." = "Sólo tu contacto puede enviar mensajes temporales."; +/* No comment provided by engineer. */ +"Only your contact can send files and media." = "Sólo tu contacto puede enviar archivos y multimedia."; + /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sólo tu contacto puede enviar mensajes de voz."; @@ -3683,24 +3853,51 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Abrir cambios"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Abrir chat"; /* authentication reason */ "Open chat console" = "Abrir consola de Chat"; +/* alert action */ +"Open clean link" = "Abrir enlace limpio"; + /* No comment provided by engineer. */ "Open conditions" = "Abrir condiciones"; -/* No comment provided by engineer. */ +/* alert action */ +"Open full link" = "Abrir enlace completo"; + +/* new chat action */ "Open group" = "Grupo abierto"; +/* alert title */ +"Open link?" = "¿Abrir enlace?"; + /* authentication reason */ "Open migration to another device" = "Abrir menú migración a otro dispositivo"; +/* new chat action */ +"Open new chat" = "Abrir chat nuevo"; + +/* new chat action */ +"Open new group" = "Abrir grupo nuevo"; + /* No comment provided by engineer. */ "Open Settings" = "Abrir Configuración"; +/* No comment provided by engineer. */ +"Open to accept" = "Abrir para aceptar"; + +/* No comment provided by engineer. */ +"Open to connect" = "Abrir para conectar"; + +/* No comment provided by engineer. */ +"Open to join" = "Abre para unirte"; + +/* No comment provided by engineer. */ +"Open to use bot" = "Abre para usar el bot"; + /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; @@ -3770,9 +3967,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Contraseña para hacerlo visible"; -/* past/unknown group member */ -"Past member %@" = "Miembro pasado %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Pegar dirección de ordenador"; @@ -3797,6 +3991,9 @@ time to disappear */ /* No comment provided by engineer. */ "pending approval" = "pendiente de aprobación"; +/* No comment provided by engineer. */ +"pending review" = "pendiente de revisión"; + /* No comment provided by engineer. */ "Periodic" = "Periódicamente"; @@ -3827,7 +4024,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Comprueba que has usado el enlace correcto o pide a tu contacto que te envíe otro."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Comprueba tu conexión de red con %@ e inténtalo de nuevo."; /* No comment provided by engineer. */ @@ -3849,7 +4046,7 @@ time to disappear */ "Please enter the previous password after restoring database backup. This action can not be undone." = "Introduce la contraseña anterior después de restaurar la copia de seguridad de la base de datos. Esta acción no se puede deshacer."; /* No comment provided by engineer. */ -"Please remember or store it securely - there is no way to recover a lost passcode!" = "Por favor, recuerda y guarda el código de acceso en un lugar seguro. ¡No hay forma de recuperar un código perdido!"; +"Please remember or store it securely - there is no way to recover a lost passcode!" = "Por favor, recuerda y guarda el código de acceso en un lugar seguro. ¡No hay manera de recuperar un código perdido!"; /* No comment provided by engineer. */ "Please report it to the developers." = "Por favor, informa a los desarrolladores."; @@ -3858,14 +4055,17 @@ time to disappear */ "Please restart the app and migrate the database to enable push notifications." = "Reinicia la aplicación y migra la base de datos para activar las notificaciones automáticas."; /* No comment provided by engineer. */ -"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Guarda la contraseña de forma segura, NO podrás acceder al chat si la pierdes."; +"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Guarda la contraseña de forma segura, NO podrás acceder al chat si se pierde."; /* No comment provided by engineer. */ -"Please store passphrase securely, you will NOT be able to change it if you lose it." = "Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes."; +"Please store passphrase securely, you will NOT be able to change it if you lose it." = "Guarda la contraseña de forma segura, NO podrás cambiarla si se pierde."; /* token info */ "Please try to disable and re-enable notfications." = "Por favor, intenta desactivar y reactivar las notificaciones."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Por favor, espera a que tu solicitud sea revisada por los moderadores del grupo."; + /* token info */ "Please wait for token activation to complete." = "Por favor, espera a que el token de activación se complete."; @@ -3878,9 +4078,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Puerto"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella del certificado en la dirección del servidor es incorrecta"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; @@ -3929,9 +4126,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Enrutamiento privado"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Error de enrutamiento privado"; +/* alert title */ +"Private routing timeout" = "Timeout enrutamiento privado"; + /* No comment provided by engineer. */ "Profile and server connections" = "Eliminar perfil y conexiones"; @@ -3992,6 +4192,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Protege tu dirección IP de los servidores de retransmisión elegidos por tus contactos.\nActívalo en ajustes de *Servidores y Redes*."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Timeout protocolo en segundo plano"; + /* No comment provided by engineer. */ "Protocol timeout" = "Timeout protocolo"; @@ -4136,16 +4339,20 @@ time to disappear */ /* token status text */ "Registered" = "Registrado"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Rechazar"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Rechazar contacto (NO se notifica al remitente)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Rechazar solicitud de contacto"; +/* alert title */ +"Reject member?" = "¿Rechazar al miembro?"; + /* No comment provided by engineer. */ "rejected" = "rechazado"; @@ -4167,6 +4374,9 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Eliminar imagen"; +/* No comment provided by engineer. */ +"Remove link tracking" = "Limpiar enlaces de seguimiento"; + /* No comment provided by engineer. */ "Remove member" = "Expulsar miembro"; @@ -4185,12 +4395,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "dirección de contacto eliminada"; +/* No comment provided by engineer. */ +"removed from group" = "expulsado del grupo"; + /* profile update event chat item */ "removed profile picture" = "ha eliminado la imagen del perfil"; /* rcv group event chat item */ "removed you" = "te ha expulsado"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Elimina mensajes y bloquea miembros."; + /* No comment provided by engineer. */ "Renegotiate" = "Renegociar"; @@ -4200,18 +4416,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "¿Renegociar cifrado?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "¿Repetir solicitud de conexión?"; - /* No comment provided by engineer. */ "Repeat download" = "Repetir descarga"; /* No comment provided by engineer. */ "Repeat import" = "Repetir importación"; -/* No comment provided by engineer. */ -"Repeat join request?" = "¿Repetir solicitud de admisión?"; - /* No comment provided by engineer. */ "Repeat upload" = "Repetir subida"; @@ -4233,6 +4443,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "¿Motivo del informe?"; +/* alert title */ +"Report sent to moderators" = "Informe enviado a los moderadores"; + /* report reason */ "Report spam: only group moderators will see it." = "Informar de spam: sólo los moderadores del grupo lo verán."; @@ -4248,6 +4461,18 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Informes"; +/* No comment provided by engineer. */ +"request is sent" = "petición enviada"; + +/* No comment provided by engineer. */ +"request to join rejected" = "petición para unirse rechazada"; + +/* rcv group event chat item */ +"requested connection" = "conexión solicitada"; + +/* rcv direct event chat item */ +"requested connection from group %@" = "conexión solicitada desde el grupo %@"; + /* chat list item title */ "requested to connect" = "solicitado para conectar"; @@ -4296,15 +4521,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Error al restaurar base de datos"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Reintentar"; /* chat item action */ "Reveal" = "Revelar"; +/* No comment provided by engineer. */ +"review" = "por revisar"; + /* No comment provided by engineer. */ "Review conditions" = "Revisar condiciones"; +/* No comment provided by engineer. */ +"Review group members" = "Revisa los miembros del grupo"; + +/* admission stage */ +"Review members" = "Revisar miembros"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Revisa a los miembros antes de admitirles en el grupo."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "en revisión por los administradores"; + /* No comment provided by engineer. */ "Revoke" = "Revocar"; @@ -4333,6 +4573,12 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Guardar (y notificar contactos)"; +/* alert button */ +"Save (and notify members)" = "Guardar (y notificar miembros)"; + +/* alert title */ +"Save admission settings?" = "¿Guardar configuración?"; + /* alert button */ "Save and notify contact" = "Guardar y notificar contacto"; @@ -4348,6 +4594,9 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; +/* alert title */ +"Save group profile?" = "¿Guardar perfil del grupo?"; + /* No comment provided by engineer. */ "Save list" = "Guardar lista"; @@ -4487,10 +4736,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Envía un mensaje en vivo: se actualizará para el (los) destinatario(s) a medida que se escribe"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "Enviar confirmaciones de entrega a"; +"Send contact request?" = "¿Enviar solicitud de contacto?"; /* No comment provided by engineer. */ -"send direct message" = "Enviar mensaje directo"; +"Send delivery receipts to" = "Enviar confirmaciones de entrega a"; /* No comment provided by engineer. */ "Send direct message to connect" = "Envía un mensaje para conectar"; @@ -4528,12 +4777,21 @@ chat item action */ /* No comment provided by engineer. */ "Send receipts" = "Enviar confirmaciones"; +/* No comment provided by engineer. */ +"Send request" = "Enviar solicitud"; + +/* No comment provided by engineer. */ +"Send request without message" = "Enviar solicitud sin mensaje"; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Envíalos desde la galería o desde teclados personalizados."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Se envían hasta 100 mensajes más recientes a los miembros nuevos."; +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Envía tu comentario privado a los grupos."; + /* alert message */ "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; @@ -4622,10 +4880,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "información cola del servidor: %1$@\n\núltimo mensaje recibido: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "El servidor requiere autorización para crear colas, comprueba la contraseña"; +"Server requires authorization to create queues, check password." = "El servidor requiere autorización para crear colas, comprueba la contraseña"; /* server test error */ -"Server requires authorization to upload, check password" = "El servidor requiere autorización para subir, comprueba la contraseña"; +"Server requires authorization to upload, check password." = "El servidor requiere autorización para subir, comprueba la contraseña"; /* No comment provided by engineer. */ "Server test failed!" = "¡Prueba no superada!"; @@ -4669,6 +4927,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Úsalo en lugar de la autenticación del sistema."; +/* No comment provided by engineer. */ +"Set member admission" = "Admisión miembro"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Establece el vencimiento para los mensajes en los chats."; @@ -4687,6 +4948,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Escribe la contraseña para exportar"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Añade mensaje de bienvenida y biografía del perfil."; + /* No comment provided by engineer. */ "Set the message shown to new members!" = "¡Guarda un mensaje para ser mostrado a los miembros nuevos!"; @@ -4727,6 +4991,12 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Compartir enlace"; +/* alert button */ +"Share old address" = "Compartir dirección antigua"; + +/* alert button */ +"Share old link" = "Comparte enlace completo"; + /* No comment provided by engineer. */ "Share profile" = "Perfil a compartir"; @@ -4742,9 +5012,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Compartir con contactos"; +/* No comment provided by engineer. */ +"Share your address" = "Comparte tu dirección"; + +/* No comment provided by engineer. */ +"Short description" = "Descripción corta"; + /* No comment provided by engineer. */ "Short link" = "Enlace corto"; +/* No comment provided by engineer. */ +"Short SimpleX address" = "Direcciones SimpleX cortas"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Mostrar → en mensajes con enrutamiento privado."; @@ -4770,7 +5049,7 @@ chat item action */ "Show QR code" = "Mostrar código QR"; /* No comment provided by engineer. */ -"Show:" = "Mostrar:"; +"Show:" = "Muestra:"; /* No comment provided by engineer. */ "SimpleX" = "SimpleX"; @@ -4785,7 +5064,10 @@ chat item action */ "SimpleX address and 1-time links are safe to share via any messenger." = "Compartir enlaces de un solo uso y direcciones SimpleX es seguro a través de cualquier medio."; /* No comment provided by engineer. */ -"SimpleX address or 1-time link?" = "¿Dirección SimpleX o enlace de un uso?"; +"SimpleX address or 1-time link?" = "¿Dirección SimpleX o enlace de un solo uso?"; + +/* alert title */ +"SimpleX address settings" = "Auto aceptar configuración"; /* simplex link type */ "SimpleX channel link" = "Enlace de canal SimpleX"; @@ -4832,6 +5114,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Protocolos de SimpleX auditados por Trail of Bits."; +/* simplex link type */ +"SimpleX relay link" = "Enlace de servidor SimpleX"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modo incógnito simplificado"; @@ -4975,14 +5260,26 @@ report reason */ "Tail" = "Cola"; /* No comment provided by engineer. */ -"Take picture" = "Tomar foto"; +"Take picture" = "Hacer foto"; /* No comment provided by engineer. */ "Tap button " = "Pulsa el botón "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Pulsa Conectar para chatear"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Pulsa Conectar para enviar solicitud"; + +/* No comment provided by engineer. */ +"Tap Connect to use bot" = "Pulsa Conectar para usar el bot"; + /* No comment provided by engineer. */ "Tap Create SimpleX address in the menu to create it later." = "Pulsa Crear dirección SimpleX en el menú para crearla más tarde."; +/* No comment provided by engineer. */ +"Tap Join group" = "Pulsa Unirme al grupo"; + /* No comment provided by engineer. */ "Tap to activate profile." = "Pulsa sobre un perfil para activarlo."; @@ -4996,7 +5293,7 @@ report reason */ "Tap to join incognito" = "Pulsa para unirte en modo incógnito"; /* No comment provided by engineer. */ -"Tap to paste link" = "Pulsa para pegar el enlacePulsa para pegar enlace"; +"Tap to paste link" = "Pulsa aquí para pegar el enlace"; /* No comment provided by engineer. */ "Tap to scan" = "Pulsa para escanear"; @@ -5004,6 +5301,9 @@ report reason */ /* No comment provided by engineer. */ "TCP connection" = "Conexión TCP"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "Timeout conexión TCP sp"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Timeout de la conexión TCP"; @@ -5044,7 +5344,10 @@ report reason */ "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "¡Nuestro agradecimiento a todos los colaboradores, [puedes contribuir a través de Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#traducir-el-aplicaciones)!"; /* No comment provided by engineer. */ -"Thanks to the users – contribute via Weblate!" = "¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate"; +"Thanks to the users – contribute via Weblate!" = "¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate"; + +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "La dirección pasará a ser corta y tu perfil será compartido mediante la dirección."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; @@ -5085,6 +5388,9 @@ report reason */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "El ID del siguiente mensaje es incorrecto (menor o igual que el anterior).\nPuede ocurrir por algún bug o cuando la conexión está comprometida."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "El enlace será corto y el perfil del grupo se compartirá mediante el enlace."; + /* No comment provided by engineer. */ "The message will be deleted for all members." = "El mensaje se eliminará para todos los miembros."; @@ -5100,9 +5406,6 @@ report reason */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; @@ -5112,7 +5415,7 @@ report reason */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "¡El doble check que nos faltaba! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "El remitente NO será notificado"; /* No comment provided by engineer. */ @@ -5164,7 +5467,7 @@ report reason */ "This device name" = "Nombre del dispositivo"; /* No comment provided by engineer. */ -"This display name is invalid. Please choose another name." = "Éste nombre mostrado no es válido. Por favor, elije otro nombre."; +"This display name is invalid. Please choose another name." = "Éste nombre mostrado no es válido. Por favor, elige otro nombre."; /* No comment provided by engineer. */ "This group has over %lld members, delivery receipts are not sent." = "Este grupo tiene más de %lld miembros, no se enviarán confirmaciones de entrega."; @@ -5172,12 +5475,6 @@ report reason */ /* No comment provided by engineer. */ "This group no longer exists." = "Este grupo ya no existe."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "¡Este es tu propio enlace de un solo uso!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "¡Esta es tu propia dirección SimpleX!"; - /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible."; @@ -5190,6 +5487,12 @@ report reason */ /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Esta configuración se aplica a los mensajes del perfil actual **%@**."; +/* No comment provided by engineer. */ +"This setting is for your current profile **%@**." = "Esta configuración se aplica al perfil actual **%@**."; + +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Mensajes temporales activados sólo para los contactos nuevos."; + /* No comment provided by engineer. */ "Title" = "Título"; @@ -5238,9 +5541,15 @@ report reason */ /* No comment provided by engineer. */ "To send" = "Para enviar"; +/* alert message */ +"To send commands you must be connected." = "Para enviar comandos debes estar conectado."; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Para usar otro perfil tras el intento de conexión, elimina el chat y usa el enlace de nuevo."; + /* No comment provided by engineer. */ "To use the servers of **%@**, accept conditions of use." = "Para usar los servidores de **%@**, debes aceptar las condiciones de uso."; @@ -5296,7 +5605,7 @@ report reason */ "Unblock member" = "Desbloquear miembro"; /* No comment provided by engineer. */ -"Unblock member for all?" = "¿Desbloquear el miembro para todos?"; +"Unblock member for all?" = "¿Desbloquear al miembro para todos?"; /* No comment provided by engineer. */ "Unblock member?" = "¿Desbloquear miembro?"; @@ -5403,9 +5712,27 @@ report reason */ /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Para actualizar la configuración el cliente se reconectará a todos los servidores."; +/* alert button */ +"Upgrade" = "Actualizar"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Actualizar dirección"; + +/* alert message */ +"Upgrade address?" = "¿Actualizar la dirección?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Actualizar y abrir Chat"; +/* alert message */ +"Upgrade group link?" = "¿Actualizar enlace de grupo?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Añadir enlace"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Actualiza tu dirección"; + /* No comment provided by engineer. */ "Upload errors" = "Errores en subida"; @@ -5433,7 +5760,7 @@ report reason */ /* No comment provided by engineer. */ "Use chat" = "Usar Chat"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Usar perfil actual"; /* No comment provided by engineer. */ @@ -5449,9 +5776,12 @@ report reason */ "Use from desktop" = "Usar desde ordenador"; /* No comment provided by engineer. */ -"Use iOS call interface" = "Usar interfaz de llamada de iOS"; +"Use incognito profile" = "Usar perfil incógnito"; /* No comment provided by engineer. */ +"Use iOS call interface" = "Usar interfaz de llamada de iOS"; + +/* new chat action */ "Use new incognito profile" = "Usar nuevo perfil incógnito"; /* No comment provided by engineer. */ @@ -5469,9 +5799,6 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Usar servidores"; -/* No comment provided by engineer. */ -"Use short links (BETA)" = "Usar enlaces cortos (BETA)"; - /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "¿Usar servidores SimpleX Chat?"; @@ -5643,6 +5970,9 @@ report reason */ /* No comment provided by engineer. */ "Welcome message is too long" = "Mensaje de bienvenida demasiado largo"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Da la bienvenida a tus contactos 👋"; + /* No comment provided by engineer. */ "What's new" = "Novedades"; @@ -5712,6 +6042,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Has aceptado la conexión"; +/* snd group event chat item */ +"you accepted this member" = "has aceptado al miembro"; + /* No comment provided by engineer. */ "You allow" = "Permites"; @@ -5724,33 +6057,27 @@ report reason */ /* No comment provided by engineer. */ "You are already connected with %@." = "Ya estás conectado con %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Ya estás conectando con %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "¡Ya estás conectando mediante este enlace de un solo uso!"; /* No comment provided by engineer. */ "You are already in group %@." = "Ya estás en el grupo %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Ya estás uniéndote al grupo %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "¡Ya estás uniéndote al grupo mediante este enlace!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Ya estás uniéndote al grupo mediante este enlace."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "¡En proceso de unirte al grupo!\n¿Repetir solicitud de admisión?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Estás conectado al servidor usado para recibir mensajes de este contacto."; -/* No comment provided by engineer. */ -"you are invited to group" = "has sido invitado a un grupo"; - /* No comment provided by engineer. */ "You are invited to group" = "Has sido invitado a un grupo"; @@ -5803,7 +6130,7 @@ report reason */ "You can set lock screen notification preview via settings." = "Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puedes compartir un enlace o código QR para que cualquiera pueda unirse al grupo. Si decides eliminarlo más tarde, los miembros del grupo se mantendrán."; +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo."; /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Puedes compartir esta dirección con tus contactos para que puedan conectar con **%@**."; @@ -5821,9 +6148,12 @@ report reason */ "You can use markdown to format messages:" = "Puedes usar la sintaxis markdown para dar formato a tus mensajes:"; /* alert message */ -"You can view invitation link again in connection details." = "Podrás ver el enlace de invitación en detalles de conexión."; +"You can view invitation link again in connection details." = "Puedes ver el enlace de invitación de nuevo en los detalles de la conexión."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "Puedes ver tus informes en Chat con administradores."; + +/* alert title */ "You can't send messages!" = "¡No puedes enviar mensajes!"; /* chat item text */ @@ -5844,10 +6174,7 @@ report reason */ /* No comment provided by engineer. */ "You decide who can connect." = "Tu decides quién se conecta."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "¡Ya has solicitado la conexión mediante esta dirección!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ya has solicitado la conexión\n¿Repetir solicitud?"; /* No comment provided by engineer. */ @@ -5901,6 +6228,9 @@ report reason */ /* snd group event chat item */ "you unblocked %@" = "has desbloqueado a %@"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Podrás enviar mensajes **después de que tu solicitud sea aceptada**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o revisa más tarde."; @@ -5916,17 +6246,14 @@ report reason */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Se te pedirá autenticarte cuando inicies la aplicación o sigas usándola tras 30 segundos en segundo plano."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Te conectarás con todos los miembros del grupo."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos."; /* No comment provided by engineer. */ -"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes de este chat. El historial del chat se conserva."; +"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes del chat. El historial del chat se conserva."; /* No comment provided by engineer. */ -"You will stop receiving messages from this group. Chat history will be preserved." = "Dejarás de recibir mensajes de este grupo. El historial del chat se conservará."; +"You will stop receiving messages from this group. Chat history will be preserved." = "Dejarás de recibir mensajes del grupo. El historial del chat se conservará."; /* No comment provided by engineer. */ "You won't lose your contacts if you later delete your address." = "Si lo eliminas más tarde tus contactos no se perderán."; @@ -5940,6 +6267,9 @@ report reason */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Estás usando un perfil incógnito en este grupo. Para evitar descubrir tu perfil principal no se permite invitar contactos"; +/* No comment provided by engineer. */ +"Your business contact" = "Mi contacto empresarial"; + /* No comment provided by engineer. */ "Your calls" = "Llamadas"; @@ -5955,8 +6285,14 @@ report reason */ /* No comment provided by engineer. */ "Your chat profiles" = "Mis perfiles"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Tu chat ha sido movido a %@ pero ha ocurrido un error inesperado mientras se te redirigía al perfil."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Tu conexión ha sido trasladada a %@ pero ha ocurrido un error inesperado al redirigirte al perfil."; +"Your connection was moved to %@ but an error happened when switching profile." = "Tu conexión ha sido trasladada a %@ pero ha ocurrido un error inesperado al redirigirte al perfil."; + +/* No comment provided by engineer. */ +"Your contact" = "Mi contacto"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "El contacto ha enviado un archivo mayor al máximo admitido (%@)."; @@ -5976,6 +6312,9 @@ report reason */ /* No comment provided by engineer. */ "Your current profile" = "Tu perfil actual"; +/* No comment provided by engineer. */ +"Your group" = "Mi grupo"; + /* No comment provided by engineer. */ "Your ICE servers" = "Servidores ICE"; @@ -5992,14 +6331,14 @@ report reason */ "Your profile **%@** will be shared." = "El perfil **%@** será compartido."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil."; +"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; + +/* No comment provided by engineer. */ +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo."; - /* No comment provided by engineer. */ "Your random profile" = "Tu perfil aleatorio"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 4891c7fb26..76e4c1be0d 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -240,6 +240,7 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Hyväksy"; @@ -249,7 +250,7 @@ swipe action */ /* notification body */ "Accept contact request from %@?" = "Hyväksy kontaktipyyntö %@:ltä?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Hyväksy tuntematon"; @@ -509,7 +510,8 @@ swipe action */ "Can't invite contacts!" = "Kontakteja ei voi kutsua!"; /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Peruuta"; /* feature offered item */ @@ -651,16 +653,13 @@ set passcode view */ /* server test step */ "Connect" = "Yhdistä"; -/* No comment provided by engineer. */ -"Connect incognito" = "Yhdistä Incognito"; - /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "ole yhteydessä SimpleX Chat -kehittäjiin."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Yhdistä linkin kautta"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Yhdistä kertalinkillä"; /* No comment provided by engineer. */ @@ -696,7 +695,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection" = "Yhteys"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Yhteysvirhe"; /* No comment provided by engineer. */ @@ -708,7 +707,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection request sent!" = "Yhteyspyyntö lähetetty!"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Yhteyden aikakatkaisu"; /* connection information */ @@ -774,9 +773,6 @@ set passcode view */ /* server test step */ "Create queue" = "Luo jono"; -/* No comment provided by engineer. */ -"Create secret group" = "Luo salainen ryhmä"; - /* No comment provided by engineer. */ "Create SimpleX address" = "Luo SimpleX-osoite"; @@ -1073,7 +1069,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't enable" = "Älä salli"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Älä näytä uudelleen"; /* No comment provided by engineer. */ @@ -1247,7 +1243,7 @@ swipe action */ /* No comment provided by engineer. */ "Error changing role" = "Virhe roolin vaihdossa"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Virhe asetuksen muuttamisessa"; /* No comment provided by engineer. */ @@ -1265,19 +1261,19 @@ swipe action */ /* No comment provided by engineer. */ "Error decrypting file" = "Virhe tiedoston salauksen purussa"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "Virhe keskustelujen tietokannan poistamisessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Virhe keskutelun poistamisessa!"; /* No comment provided by engineer. */ "Error deleting connection" = "Virhe yhteyden poistamisessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Virhe tietokannan poistamisessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Virhe vanhan tietokannan poistamisessa"; /* No comment provided by engineer. */ @@ -1295,10 +1291,10 @@ swipe action */ /* No comment provided by engineer. */ "Error encrypting database" = "Virhe tietokannan salauksessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Virhe vietäessä keskustelujen tietokantaa"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Virhe keskustelujen tietokannan tuonnissa"; /* No comment provided by engineer. */ @@ -1307,7 +1303,7 @@ swipe action */ /* alert title */ "Error receiving file" = "Virhe tiedoston vastaanottamisessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "Virhe poistettaessa jäsentä"; /* No comment provided by engineer. */ @@ -1432,6 +1428,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Löydä keskustelut nopeammin"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen"; + /* No comment provided by engineer. */ "Fix" = "Korjaa"; @@ -1760,9 +1759,9 @@ snd error text */ "Join" = "Liity"; /* No comment provided by engineer. */ -"join as %@" = "Liity %@:nä"; +"Join as %@" = "Liity %@:nä"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Liity ryhmään"; /* No comment provided by engineer. */ @@ -2079,6 +2078,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "pois"; @@ -2091,7 +2091,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "tarjottu %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -2154,7 +2156,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Vain kontaktisi voi lähettää ääniviestejä."; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Avaa keskustelu"; /* authentication reason */ @@ -2208,7 +2210,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Tarkista, että käytit oikeaa linkkiä tai pyydä kontaktiasi lähettämään sinulle uusi linkki."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Tarkista verkkoyhteytesi %@:lla ja yritä uudelleen."; /* No comment provided by engineer. */ @@ -2241,9 +2243,6 @@ time to disappear */ /* No comment provided by engineer. */ "Polish interface" = "Puolalainen käyttöliittymä"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Säilytä viimeinen viestiluonnos liitteineen."; @@ -2382,14 +2381,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Pienempi akun käyttö"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Hylkää"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Hylkää (lähettäjälle EI ilmoiteta)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Hylkää yhteyspyyntö"; /* call status */ @@ -2652,10 +2652,10 @@ chat item action */ "Sent messages will be deleted after set time." = "Lähetetyt viestit poistetaan asetetun ajan kuluttua."; /* server test error */ -"Server requires authorization to create queues, check password" = "Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana"; +"Server requires authorization to create queues, check password." = "Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana"; /* server test error */ -"Server requires authorization to upload, check password" = "Palvelin vaatii valtuutuksen tiedoston lataamiseksi, tarkista salasana"; +"Server requires authorization to upload, check password." = "Palvelin vaatii valtuutuksen tiedoston lataamiseksi, tarkista salasana"; /* No comment provided by engineer. */ "Server test failed!" = "Palvelintesti epäonnistui!"; @@ -2910,13 +2910,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Toinen kuittaus, joka uupui! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Lähettäjälle EI ilmoiteta"; /* No comment provided by engineer. */ @@ -3072,7 +3069,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "Käytä chattia"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Käytä nykyistä profiilia"; /* No comment provided by engineer. */ @@ -3081,7 +3078,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "Käytä iOS:n puhelujen käyttöliittymää"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "Käytä uutta incognito-profiilia"; /* No comment provided by engineer. */ @@ -3219,9 +3216,6 @@ chat item action */ /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Olet yhteydessä palvelimeen, jota käytetään vastaanottamaan viestejä tältä kontaktilta."; -/* No comment provided by engineer. */ -"you are invited to group" = "sinut on kutsuttu ryhmään"; - /* No comment provided by engineer. */ "You are invited to group" = "Sinut on kutsuttu ryhmään"; @@ -3264,7 +3258,7 @@ chat item action */ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Voit käyttää markdownia viestien muotoiluun:"; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "Et voi lähettää viestejä!"; /* chat item text */ @@ -3391,10 +3385,10 @@ chat item action */ "Your profile **%@** will be shared." = "Profiilisi **%@** jaetaan."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi."; +"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi."; /* No comment provided by engineer. */ "Your random profile" = "Satunnainen profiilisi"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 4dd75039dc..2a210cbfa8 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -342,22 +342,35 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Accepter"; +/* alert action */ +"Accept as member" = "Accepter en tant que membre"; + +/* alert action */ +"Accept as observer" = "Accepter en tant qu'observateur"; + /* No comment provided by engineer. */ "Accept conditions" = "Accepter les conditions"; /* No comment provided by engineer. */ "Accept connection request?" = "Accepter la demande de connexion ?"; +/* alert title */ +"Accept contact request" = "Accepter la demande de contact"; + /* notification body */ "Accept contact request from %@?" = "Accepter la demande de contact de %@ ?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Accepter en incognito"; +/* alert title */ +"Accept member" = "Accepter le membre"; + /* call status */ "accepted call" = "appel accepté"; @@ -388,6 +401,9 @@ swipe action */ /* No comment provided by engineer. */ "Add list" = "Ajouter une liste"; +/* placeholder for sending contact request */ +"Add message" = "Ajouter un message"; + /* No comment provided by engineer. */ "Add profile" = "Ajouter un profil"; @@ -505,6 +521,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Tous les rapports seront archivés pour vous."; +/* No comment provided by engineer. */ +"All servers" = "Tous les serveurs"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Tous vos contacts resteront connectés."; @@ -529,6 +548,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Autoriser la rétrogradation"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "Permettre des fichiers et des médias seulement si votre contact les permet."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures)"; @@ -580,16 +602,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Autorise votre contact à envoyer des messages éphémères."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Permettre à vos contacts d'envoyer des fichiers et des médias."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Autorise vos contacts à envoyer des messages vocaux."; /* No comment provided by engineer. */ "Already connected?" = "Déjà connecté ?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Déjà en connexion !"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Groupe déjà rejoint !"; /* pref value */ @@ -727,9 +752,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Images auto-acceptées"; -/* alert title */ -"Auto-accept settings" = "Paramètres de réception automatique"; - /* No comment provided by engineer. */ "Back" = "Retour"; @@ -897,7 +919,8 @@ marked deleted chat item preview text */ "Can't message member" = "Impossible d'envoyer un message à ce membre"; /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Annuler"; /* No comment provided by engineer. */ @@ -979,7 +1002,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "La discussion existe déjà"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "La discussion existe déjà !"; /* No comment provided by engineer. */ @@ -1177,9 +1200,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connect automatically" = "Connexion automatique"; -/* No comment provided by engineer. */ -"Connect incognito" = "Se connecter incognito"; - /* No comment provided by engineer. */ "Connect to desktop" = "Connexion au bureau"; @@ -1189,25 +1209,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Connectez-vous à vos amis plus rapidement."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Se connecter à soi-même ?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Se connecter à soi-même ?\nIl s'agit de votre propre lien unique !"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Se connecter à soi-même ?\nC'est votre propre adresse SimpleX !"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Se connecter via l'adresse de contact"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Se connecter via un lien"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Se connecter via un lien unique"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Se connecter avec %@"; /* No comment provided by engineer. */ @@ -1219,9 +1236,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Bureau connecté"; -/* rcv group event chat item */ -"connected directly" = "s'est connecté.e de manière directe"; - /* No comment provided by engineer. */ "Connected servers" = "Serveurs connectés"; @@ -1273,7 +1287,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "Connexion bloquée"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Erreur de connexion"; /* No comment provided by engineer. */ @@ -1303,7 +1317,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Connexion terminée"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Délai de connexion"; /* No comment provided by engineer. */ @@ -1414,9 +1428,6 @@ set passcode view */ /* server test step */ "Create queue" = "Créer une file d'attente"; -/* No comment provided by engineer. */ -"Create secret group" = "Créer un groupe secret"; - /* No comment provided by engineer. */ "Create SimpleX address" = "Créer une adresse SimpleX"; @@ -1863,7 +1874,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "Ne manquez pas les messages importants."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Ne plus afficher"; /* No comment provided by engineer. */ @@ -2122,7 +2133,7 @@ chat item action */ /* No comment provided by engineer. */ "Error changing role" = "Erreur lors du changement de rôle"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Erreur de changement de paramètre"; /* No comment provided by engineer. */ @@ -2131,7 +2142,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Erreur lors de la vérification de l'état du jeton (token)"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard."; /* No comment provided by engineer. */ @@ -2161,19 +2172,19 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Erreur lors du déchiffrement du fichier"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "Erreur lors de la suppression de la base de données du chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Erreur lors de la suppression du chat !"; /* No comment provided by engineer. */ "Error deleting connection" = "Erreur lors de la suppression de la connexion"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Erreur lors de la suppression de la base de données"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Erreur lors de la suppression de l'ancienne base de données"; /* No comment provided by engineer. */ @@ -2194,13 +2205,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Erreur lors du chiffrement de la base de données"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Erreur lors de l'exportation de la base de données du chat"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Erreur d'exportation du thème : %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Erreur lors de l'importation de la base de données du chat"; /* No comment provided by engineer. */ @@ -2227,7 +2238,7 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Erreur lors de l'inscription aux notifications"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "Erreur lors de la suppression d'un membre"; /* alert title */ @@ -2281,7 +2292,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Erreur lors de l'arrêt du chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Erreur lors du changement de profil"; /* alertTitle */ @@ -2454,6 +2465,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Recherche de message plus rapide"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte"; + /* No comment provided by engineer. */ "Fix" = "Réparer"; @@ -2517,8 +2531,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "Transfert des %lld messages"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Le serveur de redirection %1$@ n'a pas réussi à se connecter au serveur de destination %2$@. Veuillez réessayer plus tard."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "L'adresse du serveur de redirection est incompatible avec les paramètres du réseau : %@."; @@ -2568,7 +2582,7 @@ snd error text */ /* No comment provided by engineer. */ "Group already exists" = "Le groupe existe déjà"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Ce groupe existe déjà !"; /* No comment provided by engineer. */ @@ -2832,7 +2846,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Nom d'affichage invalide !"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Lien invalide"; /* No comment provided by engineer. */ @@ -2932,24 +2946,18 @@ snd error text */ "Join" = "Rejoindre"; /* No comment provided by engineer. */ -"join as %@" = "rejoindre entant que %@"; +"Join as %@" = "rejoindre entant que %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Rejoindre le groupe"; /* No comment provided by engineer. */ "Join group conversations" = "Participez aux conversations de groupe"; -/* No comment provided by engineer. */ -"Join group?" = "Rejoindre le groupe ?"; - /* No comment provided by engineer. */ "Join incognito" = "Rejoindre en incognito"; -/* No comment provided by engineer. */ -"Join with current profile" = "Rejoindre avec le profil actuel"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Rejoindre votre groupe ?\nVoici votre lien pour le groupe %@ !"; /* No comment provided by engineer. */ @@ -3479,6 +3487,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "off"; @@ -3491,7 +3500,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "propose %1$@ : %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3569,7 +3580,7 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Ouvrir les modifications"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Ouvrir le chat"; /* authentication reason */ @@ -3578,7 +3589,7 @@ time to disappear */ /* No comment provided by engineer. */ "Open conditions" = "Ouvrir les conditions"; -/* No comment provided by engineer. */ +/* new chat action */ "Open group" = "Ouvrir le groupe"; /* authentication reason */ @@ -3653,9 +3664,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Mot de passe à entrer"; -/* past/unknown group member */ -"Past member %@" = "Ancien membre %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Coller l'adresse du bureau"; @@ -3704,7 +3712,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Veuillez vérifier que vous avez utilisé le bon lien ou demandez à votre contact de vous en envoyer un autre."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Veuillez vérifier votre connexion réseau avec %@ et réessayer."; /* No comment provided by engineer. */ @@ -3746,9 +3754,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Port"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserver le brouillon du dernier message, avec les pièces jointes."; @@ -3788,7 +3793,7 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Routage privé"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Erreur de routage privé"; /* No comment provided by engineer. */ @@ -3983,14 +3988,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Réduction de la consommation de batterie"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Rejeter"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Rejeter le contact (l'expéditeur N'en est PAS informé)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Rejeter la demande de contact"; /* call status */ @@ -4044,18 +4050,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Renégocier le chiffrement ?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Répéter la demande de connexion ?"; - /* No comment provided by engineer. */ "Repeat download" = "Répéter le téléchargement"; /* No comment provided by engineer. */ "Repeat import" = "Répéter l'importation"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Répéter la requête d'adhésion ?"; - /* No comment provided by engineer. */ "Repeat upload" = "Répéter l'envoi"; @@ -4110,7 +4110,7 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Erreur de restauration de la base de données"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Réessayer"; /* chat item action */ @@ -4300,9 +4300,6 @@ chat item action */ /* No comment provided by engineer. */ "Send delivery receipts to" = "Envoyer les accusés de réception à"; -/* No comment provided by engineer. */ -"send direct message" = "envoyer un message direct"; - /* No comment provided by engineer. */ "Send direct message to connect" = "Envoyer un message direct pour vous connecter"; @@ -4430,10 +4427,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "info sur la file d'attente du serveur : %1$@\n\ndernier message reçu : %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe"; +"Server requires authorization to create queues, check password." = "Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe"; /* server test error */ -"Server requires authorization to upload, check password" = "Le serveur requiert une autorisation pour téléverser, vérifiez le mot de passe"; +"Server requires authorization to upload, check password." = "Le serveur requiert une autorisation pour téléverser, vérifiez le mot de passe"; /* No comment provided by engineer. */ "Server test failed!" = "Échec du test du serveur !"; @@ -4586,6 +4583,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Adresse SimpleX ou lien unique ?"; +/* alert title */ +"SimpleX address settings" = "Paramètres de réception automatique"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat et Flux ont conclu un accord pour inclure les serveurs exploités par Flux dans l'application."; @@ -4883,9 +4883,6 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**."; @@ -4895,7 +4892,7 @@ chat item action */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Le deuxième coche que nous avons manqué ! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "L'expéditeur N'en sera PAS informé"; /* No comment provided by engineer. */ @@ -4952,12 +4949,6 @@ chat item action */ /* No comment provided by engineer. */ "This group no longer exists." = "Ce groupe n'existe plus."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "Voici votre propre lien unique !"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Voici votre propre adresse SimpleX !"; - /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Ce lien a été utilisé avec un autre appareil mobile, veuillez créer un nouveau lien sur le bureau."; @@ -5198,7 +5189,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "Utiliser le chat"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Utiliser le profil actuel"; /* No comment provided by engineer. */ @@ -5216,7 +5207,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "Utiliser l'interface d'appel d'iOS"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "Utiliser un nouveau profil incognito"; /* No comment provided by engineer. */ @@ -5477,33 +5468,27 @@ chat item action */ /* No comment provided by engineer. */ "You are already connected with %@." = "Vous êtes déjà connecté avec %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Vous êtes déjà en train de vous connecter à %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Vous êtes déjà connecté(e) via ce lien unique !"; /* No comment provided by engineer. */ "You are already in group %@." = "Vous êtes déjà dans le groupe %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Vous êtes déjà en train de rejoindre le groupe %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Vous êtes déjà en train de rejoindre le groupe via ce lien !"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Vous êtes déjà en train de rejoindre le groupe via ce lien."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Vous êtes déjà membre de ce groupe !\nRépéter la demande d'adhésion ?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Vous êtes connecté·e au serveur utilisé pour recevoir les messages de ce contact."; -/* No comment provided by engineer. */ -"you are invited to group" = "vous êtes invité·e au groupe"; - /* No comment provided by engineer. */ "You are invited to group" = "Vous êtes invité·e au groupe"; @@ -5576,7 +5561,7 @@ chat item action */ /* alert message */ "You can view invitation link again in connection details." = "Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion."; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "Vous ne pouvez pas envoyer de messages !"; /* chat item text */ @@ -5597,10 +5582,7 @@ chat item action */ /* No comment provided by engineer. */ "You decide who can connect." = "Vous choisissez qui peut se connecter."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Vous avez déjà demandé une connexion via cette adresse !"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Vous avez déjà demandé une connexion !\nRépéter la demande de connexion ?"; /* No comment provided by engineer. */ @@ -5666,9 +5648,6 @@ chat item action */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Il vous sera demandé de vous authentifier lorsque vous démarrez ou reprenez l'application après 30 secondes en arrière-plan."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Vous vous connecterez à tous les membres du groupe."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Vous continuerez à recevoir des appels et des notifications des profils mis en sourdine lorsqu'ils sont actifs."; @@ -5706,7 +5685,7 @@ chat item action */ "Your chat profiles" = "Vos profils de chat"; /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil."; +"Your connection was moved to %@ but an error happened when switching profile." = "Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil."; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@)."; @@ -5741,15 +5720,15 @@ chat item action */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Votre profil **%@** sera partagé."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil."; - /* No comment provided by engineer. */ "Your random profile" = "Votre profil aléatoire"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 5a9b6b4e38..bb4d4b1eca 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -14,7 +14,7 @@ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- partnerek értesítése a törlésről (nem kötelező)\n- profilnevek szóközökkel\n- és még sok más!"; /* No comment provided by engineer. */ -"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése."; +"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni időkorlát beállítása az üzenetek eltűnéséhez.\n- előzmények szerkesztése."; /* No comment provided by engineer. */ "!1 colored!" = "!1 színezett!"; @@ -47,7 +47,7 @@ "**e2e encrypted** video call" = "**e2e titkosított** videóhívás"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat-kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van."; /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; @@ -314,10 +314,10 @@ time interval */ "A new random profile will be shared." = "Egy új, véletlenszerű profil lesz megosztva."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each chat profile you have in the app**." = "**Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva."; +"A separate TCP connection will be used **for each chat profile you have in the app**." = "**Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat lesz használva."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Az összes partneréhez és csoporttaghoz** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.\n**Megjegyzés:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet."; +"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Az összes partneréhez és csoporttaghoz** külön TCP-kapcsolat lesz használva.\n**Megjegyzés:** ha sok kapcsolata van, akkor az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet."; /* No comment provided by engineer. */ "Abort" = "Megszakítás"; @@ -342,22 +342,38 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Elfogadás"; +/* alert action */ +"Accept as member" = "Befogadás tagként"; + +/* alert action */ +"Accept as observer" = "Befogadás megfigyelőként"; + /* No comment provided by engineer. */ "Accept conditions" = "Feltételek elfogadása"; /* No comment provided by engineer. */ -"Accept connection request?" = "Elfogadja a meghívási kérést?"; +"Accept connection request?" = "Elfogadja a kapcsolódási kérést?"; + +/* alert title */ +"Accept contact request" = "Partneri kapcsolatkérés elfogadása"; /* notification body */ -"Accept contact request from %@?" = "Elfogadja %@ meghívási kérését?"; +"Accept contact request from %@?" = "Elfogadja %@ partneri kapcsolatkérését?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Elfogadás inkognitóban"; +/* alert title */ +"Accept member" = "Tag befogadása"; + +/* rcv group event chat item */ +"accepted %@" = "befogadta őt: %@"; + /* call status */ "accepted call" = "fogadott hívás"; @@ -367,6 +383,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "elfogadott meghívó"; +/* rcv group event chat item */ +"accepted you" = "befogadta Önt"; + /* No comment provided by engineer. */ "Acknowledged" = "Visszaigazolt"; @@ -388,6 +407,9 @@ swipe action */ /* No comment provided by engineer. */ "Add list" = "Lista hozzáadása"; +/* placeholder for sending contact request */ +"Add message" = "Üzenet hozzáadása"; + /* No comment provided by engineer. */ "Add profile" = "Profil hozzáadása"; @@ -413,7 +435,7 @@ swipe action */ "Add your team members to the conversations." = "Adja hozzá a munkatársait a beszélgetésekhez."; /* No comment provided by engineer. */ -"Added media & file servers" = "Hozzáadott média- és fájlkiszolgálók"; +"Added media & file servers" = "Hozzáadott fájl- és médiakiszolgálók"; /* No comment provided by engineer. */ "Added message servers" = "Hozzáadott üzenetkiszolgálók"; @@ -431,7 +453,7 @@ swipe action */ "Address" = "Cím"; /* No comment provided by engineer. */ -"Address change will be aborted. Old receiving address will be used." = "A cím módosítása meg fog szakadni. A régi fogadási cím lesz használva."; +"Address change will be aborted. Old receiving address will be used." = "A kliens megszakítja a cím módosítását, és a régi fogadási címet fogja használni."; /* No comment provided by engineer. */ "Address or 1-time link?" = "Cím vagy egyszer használható meghívó?"; @@ -463,6 +485,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "titkosítás elfogadása…"; +/* member criteria value */ +"all" = "összes"; + /* No comment provided by engineer. */ "All" = "Összes"; @@ -532,6 +557,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Visszafejlesztés engedélyezése"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "A fájlok és a médiatartalmak küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra)"; @@ -557,7 +585,7 @@ swipe action */ "Allow to report messsages to moderators." = "Az üzenetek jelentése a moderátorok felé engedélyezve van."; /* No comment provided by engineer. */ -"Allow to send files and media." = "A fájlok- és a médiatartalmak küldése engedélyezve van."; +"Allow to send files and media." = "A fájlok és a médiatartalmak küldése engedélyezve van."; /* No comment provided by engineer. */ "Allow to send SimpleX links." = "A SimpleX-hivatkozások küldése engedélyezve van."; @@ -583,16 +611,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Az eltűnő üzenetek küldésének engedélyezése a partnerei számára."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "A fájlok és a médiatartalmak küldése engedélyezve van a partnerei számára."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "A hangüzenetek küldése engedélyezve van a partnerei számára."; /* No comment provided by engineer. */ "Already connected?" = "Már kapcsolódott?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Kapcsolódás folyamatban!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "A csatlakozás folyamatban van a csoporthoz!"; /* pref value */ @@ -620,7 +651,7 @@ swipe action */ "Anybody can host servers." = "Bárki üzemeltethet kiszolgálókat."; /* No comment provided by engineer. */ -"App build: %@" = "Az alkalmazás összeállítási száma: %@"; +"App build: %@" = "Alkalmazás összeállítási száma: %@"; /* No comment provided by engineer. */ "App data migration" = "Alkalmazásadatok átköltöztetése"; @@ -635,19 +666,19 @@ swipe action */ "App icon" = "Alkalmazásikon"; /* No comment provided by engineer. */ -"App passcode" = "Alkalmazás jelkód"; +"App passcode" = "Alkalmazásjelkód"; /* No comment provided by engineer. */ -"App passcode is replaced with self-destruct passcode." = "Az alkalmazás-jelkód helyettesítve lesz egy önmegsemmisítő-jelkóddal."; +"App passcode is replaced with self-destruct passcode." = "Az alkalmazásjelkód helyettesítve lesz egy önmegsemmisítő jelkóddal."; /* No comment provided by engineer. */ "App session" = "Alkalmazás munkamenete"; /* No comment provided by engineer. */ -"App version" = "Az alkalmazás verziója"; +"App version" = "Alkalmazás verziója"; /* No comment provided by engineer. */ -"App version: v%@" = "Az alkalmazás verziója: v%@"; +"App version: v%@" = "Alkalmazás verziója: v%@"; /* No comment provided by engineer. */ "Appearance" = "Megjelenés"; @@ -656,7 +687,7 @@ swipe action */ "Apply" = "Alkalmaz"; /* No comment provided by engineer. */ -"Apply to" = "Alkalmazás erre"; +"Apply to" = "Használat ehhez"; /* No comment provided by engineer. */ "Archive" = "Archívum"; @@ -695,7 +726,7 @@ swipe action */ "Attach" = "Mellékelés"; /* No comment provided by engineer. */ -"attempts" = "próbálkozások"; +"attempts" = "kísérletek"; /* No comment provided by engineer. */ "Audio & video calls" = "Hang- és videóhívások"; @@ -710,7 +741,7 @@ swipe action */ "Audio/video calls" = "Hang- és videóhívások"; /* No comment provided by engineer. */ -"Audio/video calls are prohibited." = "A hívások kezdeményezése le van tiltva ebben a csevegésben."; +"Audio/video calls are prohibited." = "A hívások kezdeményezése le van tiltva."; /* PIN entry */ "Authentication cancelled" = "Hitelesítés visszavonva"; @@ -731,14 +762,11 @@ swipe action */ "Auto-accept" = "Automatikus elfogadás"; /* No comment provided by engineer. */ -"Auto-accept contact requests" = "Meghívási kérések automatikus elfogadása"; +"Auto-accept contact requests" = "Partneri kapcsolatkérések automatikus elfogadása"; /* No comment provided by engineer. */ "Auto-accept images" = "Képek automatikus elfogadása"; -/* alert title */ -"Auto-accept settings" = "Beállítások automatikus elfogadása"; - /* No comment provided by engineer. */ "Back" = "Vissza"; @@ -749,13 +777,13 @@ swipe action */ "Bad desktop address" = "Érvénytelen számítógépcím"; /* integrity error chat item */ -"bad message hash" = "érvénytelen az üzenet hasítóértéke"; +"bad message hash" = "hibás az üzenet kivonata"; /* No comment provided by engineer. */ -"Bad message hash" = "Érvénytelen az üzenet hasítóértéke"; +"Bad message hash" = "Érvénytelen az üzenet kivonata"; /* integrity error chat item */ -"bad message ID" = "téves üzenet ID"; +"bad message ID" = "hibás az üzenet azonosítója"; /* No comment provided by engineer. */ "Bad message ID" = "Téves üzenet ID"; @@ -790,6 +818,12 @@ swipe action */ /* No comment provided by engineer. */ "Better user experience" = "Továbbfejlesztett felhasználói élmény"; +/* No comment provided by engineer. */ +"Bio" = "Névjegy"; + +/* alert title */ +"Bio too large" = "A névjegy túl hosszú"; + /* No comment provided by engineer. */ "Black" = "Fekete"; @@ -833,6 +867,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "bold" = "félkövér"; +/* No comment provided by engineer. */ +"Bot" = "Bot"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "Mindkét fél hozzáadhat az üzenetekhez reakciókat."; @@ -845,6 +882,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Mindkét fél küldhet eltűnő üzeneteket."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Mindkét fél küldhet fájlokat és médiatartalmakat."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Mindkét fél küldhet hangüzeneteket."; @@ -857,6 +897,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Üzleti csevegések"; +/* No comment provided by engineer. */ +"Business connection" = "Üzleti kapcsolat"; + /* No comment provided by engineer. */ "Businesses" = "Üzleti"; @@ -896,6 +939,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Nem lehet felhívni a tagot"; +/* alert title */ +"Can't change profile" = "Nem lehet módosítani a profilt"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Nem lehet meghívni a partnert!"; @@ -905,8 +951,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Nem lehet üzenetet küldeni a tagnak"; +/* No comment provided by engineer. */ +"can't send messages" = "nem lehet üzeneteket küldeni"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Mégse"; /* No comment provided by engineer. */ @@ -965,7 +1015,7 @@ alert button */ /* authentication reason set passcode view */ -"Change self-destruct passcode" = "Önmegsemmisítő-jelkód módosítása"; +"Change self-destruct passcode" = "Önmegsemmisítő jelkód módosítása"; /* chat item text */ "changed address for you" = "módosította a címet az Ön számára"; @@ -988,7 +1038,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "A csevegés már létezik"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "A csevegés már létezik!"; /* No comment provided by engineer. */ @@ -1016,7 +1066,7 @@ set passcode view */ "Chat is stopped" = "A csevegés megállt"; /* No comment provided by engineer. */ -"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "A csevegés megállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés elindítása előtt."; +"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "A csevegés megállt. Ha ezt az adatbázist már használta egy másik eszközön, akkor a csevegés elindítása előtt vissza kell állítania azt."; /* No comment provided by engineer. */ "Chat list" = "Csevegési lista"; @@ -1042,9 +1092,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "A csevegés törölve lesz az Ön számára – ez a művelet nem vonható vissza!"; +/* chat toolbar */ +"Chat with admins" = "Csevegés az adminisztrátorokkal"; + +/* No comment provided by engineer. */ +"Chat with member" = "Csevegés a taggal"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Csevegés a tagokkal mielőtt csatlakoznának."; + /* No comment provided by engineer. */ "Chats" = "Csevegések"; +/* No comment provided by engineer. */ +"Chats with members" = "Csevegés a tagokkal"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Üzenetek ellenőrzése 20 percenként."; @@ -1187,7 +1249,7 @@ set passcode view */ "Connect automatically" = "Kapcsolódás automatikusan"; /* No comment provided by engineer. */ -"Connect incognito" = "Kapcsolódás inkognitóban"; +"Connect faster! 🚀" = "Gyorsabb kapcsolódás! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Társítás számítógéppel"; @@ -1198,25 +1260,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Kapcsolódjon gyorsabban a partnereihez."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Kapcsolódik saját magához?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódik saját magához?\nEz a saját egyszer használható meghívója!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódik saját magához?\nEz a saját SimpleX-címe!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Kapcsolódás a kapcsolattartási címen keresztül"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Kapcsolódás egyszer használható meghívón keresztül"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Kapcsolódás a következővel: %@"; /* No comment provided by engineer. */ @@ -1228,9 +1287,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Társított számítógép"; -/* rcv group event chat item */ -"connected directly" = "közvetlenül kapcsolódott"; - /* No comment provided by engineer. */ "Connected servers" = "Kapcsolódott kiszolgálók"; @@ -1282,7 +1338,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "A kapcsolat le van tiltva"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Kapcsolódási hiba"; /* No comment provided by engineer. */ @@ -1301,7 +1357,7 @@ set passcode view */ "Connection notifications" = "Kapcsolódási értesítések"; /* No comment provided by engineer. */ -"Connection request sent!" = "Meghívási kérés elküldve!"; +"Connection request sent!" = "Kapcsolódási kérés elküldve!"; /* No comment provided by engineer. */ "Connection requires encryption renegotiation." = "A kapcsolat titkosítása újraegyeztetést igényel."; @@ -1312,7 +1368,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Kapcsolat megszakítva"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Időtúllépés kapcsolódáskor"; /* No comment provided by engineer. */ @@ -1333,9 +1389,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "A partner már létezik"; +/* No comment provided by engineer. */ +"contact deleted" = "partner törölve"; + /* No comment provided by engineer. */ "Contact deleted!" = "Partner törölve!"; +/* No comment provided by engineer. */ +"contact disabled" = "partner letiltva"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "a partner e2e titkosítással rendelkezik"; @@ -1354,9 +1416,18 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Csak név"; +/* No comment provided by engineer. */ +"contact not ready" = "a partner nem áll készen"; + /* No comment provided by engineer. */ "Contact preferences" = "Partnerbeállítások"; +/* No comment provided by engineer. */ +"Contact requests from groups" = "Partneri kapcsolatkérések a csoportokból"; + +/* No comment provided by engineer. */ +"contact should accept…" = "a partnernek el kell fogadnia…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "A partner törölve lesz – ez a művelet nem vonható vissza!"; @@ -1421,16 +1492,16 @@ set passcode view */ "Create profile" = "Profil létrehozása"; /* server test step */ -"Create queue" = "Sorba állítás létrehozása"; - -/* No comment provided by engineer. */ -"Create secret group" = "Titkos csoport létrehozása"; +"Create queue" = "Várólista létrehozása"; /* No comment provided by engineer. */ "Create SimpleX address" = "SimpleX-cím létrehozása"; /* No comment provided by engineer. */ -"Create your profile" = "Saját profil létrehozása"; +"Create your address" = "Saját cím létrehozása"; + +/* No comment provided by engineer. */ +"Create your profile" = "Profil létrehozása"; /* No comment provided by engineer. */ "Created" = "Létrehozva"; @@ -1505,7 +1576,7 @@ set passcode view */ "Database ID: %d" = "Adatbázis-azonosító: %d"; /* No comment provided by engineer. */ -"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitel-izolációs beállítások."; +"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitelelkülönítési beállítások."; /* No comment provided by engineer. */ "Database is encrypted using a random passphrase, you can change it." = "Az adatbázis egy véletlenszerű jelmondattal van titkosítva, amelyet szabadon módosíthat."; @@ -1550,7 +1621,7 @@ set passcode view */ "Decentralized" = "Decentralizált"; /* message decrypt error item */ -"Decryption error" = "Titkosítás visszafejtési hiba"; +"Decryption error" = "Titkosítás-visszafejtési hiba"; /* No comment provided by engineer. */ "decryption errors" = "visszafejtési hibák"; @@ -1594,7 +1665,7 @@ swipe action */ "Delete chat" = "Csevegés törlése"; /* No comment provided by engineer. */ -"Delete chat messages from your device." = "Csevegési üzenetek törlése a saját eszközéről."; +"Delete chat messages from your device." = "Csevegési üzenetek törlése az eszközről."; /* No comment provided by engineer. */ "Delete chat profile" = "Csevegési profil törlése"; @@ -1602,6 +1673,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Törli a csevegési profilt?"; +/* alert title */ +"Delete chat with member?" = "Törli a taggal való csevegést?"; + /* No comment provided by engineer. */ "Delete chat?" = "Törli a csevegést?"; @@ -1624,7 +1698,7 @@ swipe action */ "Delete file" = "Fájl törlése"; /* No comment provided by engineer. */ -"Delete files and media?" = "Törli a fájl- és a médiatartalmakat?"; +"Delete files and media?" = "Törli a fájlokat és a médiatartalmakat?"; /* No comment provided by engineer. */ "Delete files for all chat profiles" = "Fájlok törlése az összes csevegési profilból"; @@ -1675,13 +1749,13 @@ swipe action */ "Delete or moderate up to 200 messages." = "Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása."; /* No comment provided by engineer. */ -"Delete pending connection?" = "Törli a függőben lévő meghívót?"; +"Delete pending connection?" = "Törli a függőben lévő kapcsolatot?"; /* No comment provided by engineer. */ "Delete profile" = "Profil törlése"; /* server test step */ -"Delete queue" = "Sorba állítás törlése"; +"Delete queue" = "Várólista törlése"; /* No comment provided by engineer. */ "Delete report" = "Jelentés törlése"; @@ -1711,7 +1785,7 @@ swipe action */ "deleted contact" = "törölt partner"; /* rcv group event chat item */ -"deleted group" = "törölt csoport"; +"deleted group" = "törölte a csoportot"; /* No comment provided by engineer. */ "Deletion errors" = "Törlési hibák"; @@ -1728,9 +1802,15 @@ swipe action */ /* No comment provided by engineer. */ "Delivery receipts!" = "Kézbesítési jelentések!"; +/* No comment provided by engineer. */ +"Deprecated options" = "Elavult beállítások"; + /* No comment provided by engineer. */ "Description" = "Leírás"; +/* alert title */ +"Description too large" = "A leírás túl hosszú"; + /* No comment provided by engineer. */ "Desktop address" = "Számítógép címe"; @@ -1777,7 +1857,7 @@ swipe action */ "different migration in the app/database: %@ / %@" = "különböző átköltöztetés az alkalmazásban/adatbázisban: %@ / %@"; /* No comment provided by engineer. */ -"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitel-izoláció."; +"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitelizoláció."; /* connection level description */ "direct" = "közvetlen"; @@ -1792,7 +1872,7 @@ swipe action */ "Direct messages between members are prohibited." = "A tagok közötti közvetlen üzenetek le vannak tiltva."; /* No comment provided by engineer. */ -"Disable (keep overrides)" = "Letiltás (felülírások megtartásával)"; +"Disable (keep overrides)" = "Letiltás (egyéni beállítások megtartása)"; /* alert title */ "Disable automatic message deletion?" = "Letiltja az automatikus üzenettörlést?"; @@ -1852,7 +1932,7 @@ swipe action */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; /* No comment provided by engineer. */ -"Do not use credentials with proxy." = "Ne használja a hitelesítőadatokat proxyval."; +"Do not use credentials with proxy." = "Ne használja a hitelesítési adatokat proxyval."; /* No comment provided by engineer. */ "Do NOT use private routing." = "NE használjon privát útválasztást."; @@ -1872,7 +1952,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "Ne maradjon le a fontos üzenetekről."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Ne mutasd újra"; /* No comment provided by engineer. */ @@ -1933,17 +2013,23 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Csoportprofil szerkesztése"; +/* No comment provided by engineer. */ +"Empty message!" = "Üres üzenet!"; + /* No comment provided by engineer. */ "Enable" = "Engedélyezés"; /* No comment provided by engineer. */ -"Enable (keep overrides)" = "Engedélyezés (felülírások megtartásával)"; +"Enable (keep overrides)" = "Engedélyezés (egyéni beállítások megtartása)"; /* alert title */ "Enable automatic message deletion?" = "Engedélyezi az automatikus üzenettörlést?"; /* No comment provided by engineer. */ -"Enable camera access" = "Kamera hozzáférés engedélyezése"; +"Enable camera access" = "Kamera-hozzáférés engedélyezése"; + +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Eltűnő üzenetek engedélyezése alapértelmezetten."; /* No comment provided by engineer. */ "Enable Flux in Network & servers settings for better metadata privacy." = "A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a metaadatok jobb védelme érdekében."; @@ -1970,7 +2056,7 @@ chat item action */ "Enable self-destruct" = "Önmegsemmisítés engedélyezése"; /* set passcode view */ -"Enable self-destruct passcode" = "Önmegsemmisítő-jelkód engedélyezése"; +"Enable self-destruct passcode" = "Önmegsemmisítő jelkód engedélyezése"; /* authentication reason */ "Enable SimpleX Lock" = "SimpleX-zár bekapcsolása"; @@ -2003,7 +2089,7 @@ chat item action */ "Encrypt local files" = "Helyi fájlok titkosítása"; /* No comment provided by engineer. */ -"Encrypt stored files & media" = "A tárolt fájlok- és a médiatartalmak titkosítása"; +"Encrypt stored files & media" = "Tárolt fájlok és médiatartalmak titkosítása"; /* No comment provided by engineer. */ "Encrypted database" = "Titkosított adatbázis"; @@ -2030,13 +2116,13 @@ chat item action */ "Encrypted message: unexpected error" = "Titkosított üzenet: váratlan hiba"; /* chat item text */ -"encryption agreed" = "titkosítás elfogadva"; +"encryption agreed" = "titkosítása elfogadva"; /* chat item text */ "encryption agreed for %@" = "titkosítás elfogadva %@ számára"; /* chat item text */ -"encryption ok" = "titkosítás rendben"; +"encryption ok" = "titkosítása rendben van"; /* chat item text */ "encryption ok for %@" = "titkosítás rendben %@ számára"; @@ -2087,7 +2173,7 @@ chat item action */ "Enter password above to show!" = "Adja meg a jelszót fentebb a megjelenítéshez!"; /* No comment provided by engineer. */ -"Enter server manually" = "Adja meg a kiszolgálót kézzel"; +"Enter server manually" = "Kiszolgáló megadása kézzel"; /* No comment provided by engineer. */ "Enter this device name…" = "Adja meg ennek az eszköznek a nevét…"; @@ -2114,7 +2200,10 @@ chat item action */ "Error accepting conditions" = "Hiba történt a feltételek elfogadásakor"; /* No comment provided by engineer. */ -"Error accepting contact request" = "Hiba történt a meghívási kérés elfogadásakor"; +"Error accepting contact request" = "Hiba történt a partneri kapcsolatkérés elfogadásakor"; + +/* alert title */ +"Error accepting member" = "Hiba a tag befogadásakor"; /* No comment provided by engineer. */ "Error adding member(s)" = "Hiba történt a tag(ok) hozzáadásakor"; @@ -2122,16 +2211,22 @@ chat item action */ /* alert title */ "Error adding server" = "Hiba történt a kiszolgáló hozzáadásakor"; +/* No comment provided by engineer. */ +"Error adding short link" = "Hiba történt a rövid hivatkozás hozzáadásakor"; + /* No comment provided by engineer. */ "Error changing address" = "Hiba történt a cím módosításakor"; +/* alert title */ +"Error changing chat profile" = "Hiba a csevegési profil módosításakor"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Hiba történt a kapcsolati profilra való váltáskor"; /* No comment provided by engineer. */ "Error changing role" = "Hiba történt a szerepkör módosításakor"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Hiba történt a beállítás módosításakor"; /* No comment provided by engineer. */ @@ -2140,7 +2235,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Hiba történt a token állapotának ellenőrzésekor"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Hiba történt a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később."; /* No comment provided by engineer. */ @@ -2170,26 +2265,29 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Hiba történt a fájl visszafejtésekor"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Hiba a taggal való csevegés törlésekor"; + +/* alert title */ "Error deleting chat database" = "Hiba történt a csevegési adatbázis törlésekor"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Hiba történt a csevegés törlésekor!"; /* No comment provided by engineer. */ "Error deleting connection" = "Hiba történt a kapcsolat törlésekor"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Hiba történt az adatbázis törlésekor"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Hiba történt a régi adatbázis törlésekor"; /* No comment provided by engineer. */ "Error deleting token" = "Hiba történt a token törlésekor"; /* No comment provided by engineer. */ -"Error deleting user profile" = "Hiba történt a felhasználó-profil törlésekor"; +"Error deleting user profile" = "Hiba történt a felhasználói profil törlésekor"; /* No comment provided by engineer. */ "Error downloading the archive" = "Hiba történt az archívum letöltésekor"; @@ -2203,13 +2301,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Hiba történt az adatbázis titkosításakor"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Hiba történt a csevegési adatbázis exportálásakor"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Hiba történt a téma exportálásakor: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Hiba történt a csevegési adatbázis importálásakor"; /* No comment provided by engineer. */ @@ -2224,6 +2322,9 @@ chat item action */ /* No comment provided by engineer. */ "Error opening chat" = "Hiba történt a csevegés megnyitásakor"; +/* No comment provided by engineer. */ +"Error opening group" = "Hiba a csoport előkészítésekor"; + /* alert title */ "Error receiving file" = "Hiba történt a fájl fogadásakor"; @@ -2236,7 +2337,10 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Hiba történt az értesítések regisztrálásakor"; -/* No comment provided by engineer. */ +/* alert title */ +"Error rejecting contact request" = "Hiba történt a partneri kapcsolatkérés elutasításakor"; + +/* alert title */ "Error removing member" = "Hiba történt a tag eltávolításakor"; /* alert title */ @@ -2281,6 +2385,9 @@ chat item action */ /* No comment provided by engineer. */ "Error sending message" = "Hiba történt az üzenet elküldésekor"; +/* No comment provided by engineer. */ +"Error setting auto-accept" = "Hiba az automatikus elfogadás beállításakor"; + /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Hiba történt a kézbesítési jelentések beállításakor!"; @@ -2290,7 +2397,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Hiba történt a csevegés megállításakor"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Hiba történt a profilváltáskor"; /* alertTitle */ @@ -2380,7 +2487,7 @@ snd error text */ "Failed to remove passphrase" = "Nem sikerült eltávolítani a jelmondatot"; /* No comment provided by engineer. */ -"Fast and no wait until the sender is online!" = "Gyors és nem kell várni, amíg a feladó online lesz!"; +"Fast and no wait until the sender is online!" = "Gyors és nem kell várni, amíg az üzenetküldő online lesz!"; /* No comment provided by engineer. */ "Faster deletion of groups." = "Gyorsabb csoporttörlés."; @@ -2440,13 +2547,16 @@ snd error text */ "Files and media" = "Fájlok és médiatartalmak"; /* No comment provided by engineer. */ -"Files and media are prohibited." = "A fájlok- és a médiatartalmak küldése le van tiltva."; +"Files and media are prohibited in this chat." = "A fájlok és a médiatartalmak küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Files and media not allowed" = "A fájlok- és médiatartalmak nincsenek engedélyezve"; +"Files and media are prohibited." = "A fájlok és a médiatartalmak küldése le van tiltva."; /* No comment provided by engineer. */ -"Files and media prohibited!" = "A fájlok- és a médiatartalmak küldése le van tiltva!"; +"Files and media not allowed" = "A fájlok és a médiatartalmak nincsenek engedélyezve"; + +/* No comment provided by engineer. */ +"Files and media prohibited!" = "A fájlok és a médiatartalmak küldése le van tiltva!"; /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Olvasatlan és kedvenc csevegésekre való szűrés."; @@ -2463,6 +2573,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Csevegési üzenetek gyorsabb megtalálása"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen"; + /* No comment provided by engineer. */ "Fix" = "Javítás"; @@ -2491,7 +2604,7 @@ snd error text */ "For console" = "Konzolhoz"; /* No comment provided by engineer. */ -"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni."; +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Például, ha a partnere egy SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux kiszolgálón keresztül fogja azokat kézbesíteni."; /* No comment provided by engineer. */ "For me" = "Csak magamnak"; @@ -2532,8 +2645,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "%lld üzenet továbbítása"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "A(z) %@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %@ célkiszolgálóhoz. Próbálja meg később."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "A(z) %1$@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %2$@ célkiszolgálóhoz. Próbálja meg később."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "A továbbítókiszolgáló címe nem kompatibilis a hálózati beállításokkal: %@."; @@ -2580,13 +2693,16 @@ snd error text */ /* message preview */ "Good morning!" = "Jó reggelt!"; +/* shown on group welcome message */ +"group" = "csoport"; + /* No comment provided by engineer. */ "Group" = "Csoport"; /* No comment provided by engineer. */ "Group already exists" = "A csoport már létezik"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "A csoport már létezik!"; /* No comment provided by engineer. */ @@ -2610,6 +2726,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "A csoportmeghívó már nem érvényes, a küldője eltávolította."; +/* No comment provided by engineer. */ +"group is deleted" = "csoport törölve"; + /* No comment provided by engineer. */ "Group link" = "Csoporthivatkozás"; @@ -2629,11 +2748,14 @@ snd error text */ "Group profile" = "Csoportprofil"; /* No comment provided by engineer. */ -"Group profile is stored on members' devices, not on the servers." = "A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon."; +"Group profile is stored on members' devices, not on the servers." = "A csoportprofil a tagok eszközein tárolódik, nem a kiszolgálókon."; /* snd group event chat item */ "group profile updated" = "csoportprofil frissítve"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Csoportprofil módosítva. Ha menti, akkor a frissített profil el lesz küldve a csoporttagoknak."; + /* No comment provided by engineer. */ "Group welcome message" = "A csoport üdvözlőüzenete"; @@ -2716,7 +2838,7 @@ snd error text */ "If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen el lesz távolítva!"; /* No comment provided by engineer. */ -"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő-jelkódot:"; +"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:"; /* No comment provided by engineer. */ "If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése)."; @@ -2880,7 +3002,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Érvénytelen megjelenítendő név!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Érvénytelen hivatkozás"; /* No comment provided by engineer. */ @@ -2905,7 +3027,7 @@ snd error text */ "Invitation expired!" = "A meghívó lejárt!"; /* group name */ -"invitation to group %@" = "meghívás a(z) %@ csoportba"; +"invitation to group %@" = "meghívás a(z) %@ nevű csoportba"; /* No comment provided by engineer. */ "invite" = "meghívás"; @@ -2929,7 +3051,7 @@ snd error text */ "invited %@" = "meghívta őt: %@"; /* chat list item title */ -"invited to connect" = "Függőben lévő meghívó"; +"invited to connect" = "függőben lévő kapcsolat"; /* rcv group event chat item */ "invited via your group link" = "meghíva a saját csoporthivatkozásán keresztül"; @@ -2956,10 +3078,10 @@ snd error text */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegési profilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük."; /* No comment provided by engineer. */ -"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt."; +"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta."; /* No comment provided by engineer. */ -"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült."; +"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.\n3. A kapcsolat sérült."; /* No comment provided by engineer. */ "It protects your IP address and connections." = "Védi az IP-címét és a kapcsolatait."; @@ -2980,24 +3102,18 @@ snd error text */ "Join" = "Csatlakozás"; /* No comment provided by engineer. */ -"join as %@" = "csatlakozás mint %@"; +"Join as %@" = "csatlakozás mint %@"; + +/* new chat sheet title */ +"Join group" = "Csatlakozás a csoporthoz"; /* No comment provided by engineer. */ -"Join group" = "Csatlakozás csoporthoz"; - -/* No comment provided by engineer. */ -"Join group conversations" = "Csatlakozás csoportos beszélgetésekhez"; - -/* No comment provided by engineer. */ -"Join group?" = "Csatlakozik a csoporthoz?"; +"Join group conversations" = "Csatlakozás a csoportbeszélgetésekhez"; /* No comment provided by engineer. */ "Join incognito" = "Csatlakozás inkognitóban"; -/* No comment provided by engineer. */ -"Join with current profile" = "Csatlakozás a jelenlegi profillal"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Csatlakozik a csoportjához?\nEz a saját hivatkozása a(z) %@ nevű csoporthoz!"; /* No comment provided by engineer. */ @@ -3015,6 +3131,9 @@ snd error text */ /* alert title */ "Keep unused invitation?" = "Megtartja a fel nem használt meghívót?"; +/* No comment provided by engineer. */ +"Keep your chats clean" = "Tartsa tisztán a csevegéseit"; + /* No comment provided by engineer. */ "Keep your connections" = "Kapcsolatok megtartása"; @@ -3048,6 +3167,9 @@ snd error text */ /* rcv group event chat item */ "left" = "elhagyta a csoportot"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Kevesebb adatforgalom a mobilhálózatokon."; + /* email subject */ "Let's talk in SimpleX Chat" = "Beszélgessünk a SimpleX Chatben"; @@ -3084,6 +3206,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "Élő üzenetek"; +/* in progress text */ +"Loading profile…" = "Profil betöltése…"; + /* No comment provided by engineer. */ "Local name" = "Helyi név"; @@ -3124,7 +3249,7 @@ snd error text */ "Max 30 seconds, received instantly." = "Max. 30 másodperc, azonnal érkezett."; /* No comment provided by engineer. */ -"Media & file servers" = "Média- és fájlkiszolgálók"; +"Media & file servers" = "Fájl- és médiakiszolgálók"; /* blur media */ "Medium" = "Közepes"; @@ -3135,15 +3260,27 @@ snd error text */ /* No comment provided by engineer. */ "Member" = "Tag"; +/* past/unknown group member */ +"Member %@" = "%@ ismeretlen vagy már nem tag"; + /* profile update event chat item */ "member %@ changed to %@" = "%1$@ a következőre módosította a nevét: %2$@"; +/* No comment provided by engineer. */ +"Member admission" = "Tagbefogadás"; + /* rcv group event chat item */ "member connected" = "kapcsolódott"; +/* No comment provided by engineer. */ +"member has old version" = "a tag régi verziót használ"; + /* item status text */ "Member inactive" = "Inaktív tag"; +/* No comment provided by engineer. */ +"Member is deleted - can't accept request" = "A tag törölve lett – nem lehet elfogadni a kérést"; + /* chat feature */ "Member reports" = "Tagok jelentései"; @@ -3162,6 +3299,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!"; +/* alert message */ +"Member will join the group, accept member?" = "A tag csatlakozni akar a csoporthoz, befogadja a tagot?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "A tagok reakciókat adhatnak hozzá az üzenetekhez."; @@ -3210,11 +3350,14 @@ snd error text */ /* item status text */ "Message forwarded" = "Továbbított üzenet"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Az üzenet azonnal megjelenik, amint a kapcsolódás gombra koppint."; + /* item status description */ "Message may be delivered later if member becomes active." = "Az üzenet később is kézbesíthető, ha a tag aktívvá válik."; /* No comment provided by engineer. */ -"Message queue info" = "Üzenetsorbaállítási információ"; +"Message queue info" = "Üzenet várólista információi"; /* chat feature */ "Message reactions" = "Üzenetreakciók"; @@ -3229,7 +3372,7 @@ snd error text */ "message received" = "üzenet érkezett"; /* No comment provided by engineer. */ -"Message reception" = "Üzenetjelentés"; +"Message reception" = "Üzenetfogadás"; /* No comment provided by engineer. */ "Message servers" = "Üzenetkiszolgálók"; @@ -3258,6 +3401,9 @@ snd error text */ /* No comment provided by engineer. */ "Messages & files" = "Üzenetek és fájlok"; +/* No comment provided by engineer. */ +"Messages are protected by **end-to-end encryption**." = "Az üzenetek **végpontok közötti titkosítással** vannak védve."; + /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "%@ összes üzenete meg fog jelenni!"; @@ -3274,10 +3420,10 @@ snd error text */ "Messages were deleted after you selected them." = "Az üzeneteket törölték miután kijelölte őket."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve."; +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve."; +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve."; /* No comment provided by engineer. */ "Migrate device" = "Eszköz átköltöztetése"; @@ -3334,7 +3480,7 @@ snd error text */ "Moderated at: %@" = "Moderálva: %@"; /* marked deleted chat item preview text */ -"moderated by %@" = "moderálva lett %@ által"; +"moderated by %@" = "%@ moderálta ezt az üzenetet"; /* member role */ "moderator" = "moderátor"; @@ -3409,7 +3555,7 @@ snd error text */ "New chat experience 🎉" = "Új csevegési élmény 🎉"; /* notification */ -"New contact request" = "Új meghívási kérés"; +"New contact request" = "Új partneri kapcsolatkérés"; /* notification */ "New contact:" = "Új kapcsolat:"; @@ -3423,6 +3569,9 @@ snd error text */ /* notification */ "New events" = "Új események"; +/* No comment provided by engineer. */ +"New group role: Moderator" = "Új szerepkör: Moderátor"; + /* No comment provided by engineer. */ "New in %@" = "Újdonságok a(z) %@ verzióban"; @@ -3432,6 +3581,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Új tag szerepköre"; +/* rcv group event chat item */ +"New member wants to join the group." = "Új tag szeretne csatlakozni a csoporthoz."; + /* notification */ "new message" = "új üzenet"; @@ -3448,10 +3600,10 @@ snd error text */ "New server" = "Új kiszolgáló"; /* No comment provided by engineer. */ -"New SOCKS credentials will be used every time you start the app." = "Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni."; +"New SOCKS credentials will be used every time you start the app." = "Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítési adatok lesznek használva."; /* No comment provided by engineer. */ -"New SOCKS credentials will be used for each server." = "Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva."; +"New SOCKS credentials will be used for each server." = "Az összes kiszolgálóhoz új, SOCKS-hitelesítési adatok lesznek használva."; /* pref value */ "no" = "nem"; @@ -3471,6 +3623,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Nincsenek csevegések a(z) %@ nevű listában"; +/* No comment provided by engineer. */ +"No chats with members" = "Nincsenek csevegések a tagokkal"; + /* No comment provided by engineer. */ "No contacts selected" = "Nincs partner kijelölve"; @@ -3502,13 +3657,13 @@ snd error text */ "No info, try to reload" = "Nincs információ, próbálja meg újratölteni"; /* servers error */ -"No media & file servers." = "Nincsenek média- és fájlkiszolgálók."; +"No media & file servers." = "Nincsenek fájl- és médiakiszolgálók."; /* No comment provided by engineer. */ "No message" = "Nincs üzenet"; /* servers error */ -"No message servers." = "Nincsenek üzenet-kiszolgálók."; +"No message servers." = "Nincsenek üzenetkiszolgálók."; /* No comment provided by engineer. */ "No network connection" = "Nincs hálózati kapcsolat"; @@ -3522,6 +3677,9 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Nincs engedély a hangüzenet rögzítésére"; +/* alert title */ +"No private routing session" = "Nincs privát útválasztási munkamenet"; + /* No comment provided by engineer. */ "No push server" = "Helyi"; @@ -3550,11 +3708,14 @@ snd error text */ "No unread chats" = "Nincsenek olvasatlan csevegések"; /* No comment provided by engineer. */ -"No user identifiers." = "Nincsenek felhasználó-azonosítók."; +"No user identifiers." = "Nincsenek felhasználói azonosítók."; /* No comment provided by engineer. */ "Not compatible!" = "Nem kompatibilis!"; +/* No comment provided by engineer. */ +"not synchronized" = "nincs szinkronizálva"; + /* No comment provided by engineer. */ "Notes" = "Jegyzetek"; @@ -3587,6 +3748,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "kikapcsolva"; @@ -3594,12 +3756,14 @@ time to disappear */ "Off" = "Kikapcsolva"; /* feature offered item */ -"offered %@" = "%@ ajánlotta"; +"offered %@" = "felajánlotta a következőt: %@"; /* feature offered item */ -"offered %@: %@" = "ajánlotta: %1$@, ekkor: %2$@"; +"offered %@: %@" = "felajánlotta a következőt: %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Rendben"; /* No comment provided by engineer. */ @@ -3615,13 +3779,13 @@ time to disappear */ "One-time invitation link" = "Egyszer használható meghívó"; /* No comment provided by engineer. */ -"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion-kiszolgálók **szükségesek** a kapcsolódáshoz.\nKompatibilis VPN szükséges."; +"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion kiszolgálók **szükségesek** a kapcsolódáshoz.\nKompatibilis VPN szükséges."; /* No comment provided by engineer. */ -"Onion hosts will be used when available.\nRequires compatible VPN." = "Onion-kiszolgálók használata, ha azok rendelkezésre állnak.\nVPN engedélyezése szükséges."; +"Onion hosts will be used when available.\nRequires compatible VPN." = "Onion kiszolgálók használata, ha azok rendelkezésre állnak.\nVPN engedélyezése szükséges."; /* No comment provided by engineer. */ -"Onion hosts will not be used." = "Az onion-kiszolgálók nem lesznek használva."; +"Onion hosts will not be used." = "Az onion kiszolgálók nem lesznek használva."; /* No comment provided by engineer. */ "Only chat owners can change preferences." = "Csak a csevegés tulajdonosai módosíthatják a csevegési beállításokat."; @@ -3636,13 +3800,13 @@ time to disappear */ "Only group owners can change group preferences." = "Csak a csoport tulajdonosai módosíthatják a csoportbeállításokat."; /* No comment provided by engineer. */ -"Only group owners can enable files and media." = "Csak a csoport tulajdonosai engedélyezhetik a fájlok- és a médiatartalmak küldését."; +"Only group owners can enable files and media." = "Csak a csoport tulajdonosai engedélyezhetik a fájlok és a médiatartalmak küldését."; /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "Csak a csoport tulajdonosai engedélyezhetik a hangüzenetek küldését."; /* No comment provided by engineer. */ -"Only sender and moderators see it" = "Csak a küldő és a moderátorok látják"; +"Only sender and moderators see it" = "Csak az üzenet küldője és a moderátorok látják"; /* No comment provided by engineer. */ "Only you and moderators see it" = "Csak Ön és a moderátorok látják"; @@ -3659,6 +3823,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only you can send disappearing messages." = "Csak Ön tud eltűnő üzeneteket küldeni."; +/* No comment provided by engineer. */ +"Only you can send files and media." = "Csak Ön küldhet fájlokat és médiatartalmakat."; + /* No comment provided by engineer. */ "Only you can send voice messages." = "Csak Ön tud hangüzeneteket küldeni."; @@ -3674,6 +3841,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send disappearing messages." = "Csak a partnere tud eltűnő üzeneteket küldeni."; +/* No comment provided by engineer. */ +"Only your contact can send files and media." = "Csak a partnere küldhet fájlokat és a médiatartalmakat."; + /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Csak a partnere tud hangüzeneteket küldeni."; @@ -3683,24 +3853,51 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Módosítások megtekintése"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Csevegés megnyitása"; /* authentication reason */ "Open chat console" = "Csevegési konzol megnyitása"; +/* alert action */ +"Open clean link" = "Tiszta hivatkozás megnyitása"; + /* No comment provided by engineer. */ "Open conditions" = "Feltételek megnyitása"; -/* No comment provided by engineer. */ +/* alert action */ +"Open full link" = "Teljes hivatkozás megnyitása"; + +/* new chat action */ "Open group" = "Csoport megnyitása"; +/* alert title */ +"Open link?" = "Megnyitja a hivatkozást?"; + /* authentication reason */ "Open migration to another device" = "Átköltöztetés indítása egy másik eszközre"; +/* new chat action */ +"Open new chat" = "Új csevegés megnyitása"; + +/* new chat action */ +"Open new group" = "Új csoport megnyitása"; + /* No comment provided by engineer. */ "Open Settings" = "Beállítások megnyitása"; +/* No comment provided by engineer. */ +"Open to accept" = "Megnyitás az elfogadáshoz"; + +/* No comment provided by engineer. */ +"Open to connect" = "Megnyitás a kapcsolódáshoz"; + +/* No comment provided by engineer. */ +"Open to join" = "Megnyitás a csatlakozáshoz"; + +/* No comment provided by engineer. */ +"Open to use bot" = "Megnyitás a bot használatához"; + /* No comment provided by engineer. */ "Opening app…" = "Az alkalmazás megnyitása…"; @@ -3770,9 +3967,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Jelszó a megjelenítéshez"; -/* past/unknown group member */ -"Past member %@" = "(Már nem tag) %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Számítógép címének beillesztése"; @@ -3797,6 +3991,9 @@ time to disappear */ /* No comment provided by engineer. */ "pending approval" = "jóváhagyásra vár"; +/* No comment provided by engineer. */ +"pending review" = "függőben lévő áttekintés"; + /* No comment provided by engineer. */ "Periodic" = "Időszakos"; @@ -3827,7 +4024,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg a partnerét, hogy küldjön egy másikat."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Ellenőrizze a hálózati kapcsolatát a vele: %@, és próbálja újra."; /* No comment provided by engineer. */ @@ -3866,6 +4063,9 @@ time to disappear */ /* token info */ "Please try to disable and re-enable notfications." = "Próbálja meg letiltani és újra engedélyezni az értesítéseket."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérését."; + /* token info */ "Please wait for token activation to complete." = "Várjon, amíg a token aktiválása befejeződik."; @@ -3878,9 +4078,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Port"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt."; @@ -3909,7 +4106,7 @@ time to disappear */ "Privacy redefined" = "Újraértelmezett adatvédelem"; /* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára."; +"Private chats, groups and your contacts are not accessible to server operators." = "A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára."; /* No comment provided by engineer. */ "Private filenames" = "Privát fájlnevek"; @@ -3929,9 +4126,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Privát útválasztás"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Privát útválasztási hiba"; +/* alert title */ +"Private routing timeout" = "Privát útválasztás időtúllépése"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil és kiszolgálókapcsolatok"; @@ -3972,7 +4172,7 @@ time to disappear */ "Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldése le van tiltva."; /* No comment provided by engineer. */ -"Prohibit sending files and media." = "A fájlok- és a médiatartalmak küldése le van tiltva."; +"Prohibit sending files and media." = "A fájlok és a médiatartalmak küldése le van tiltva."; /* No comment provided by engineer. */ "Prohibit sending SimpleX links." = "A SimpleX-hivatkozások küldése le van tiltva."; @@ -3992,6 +4192,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Védje az IP-címét a partnerei által kiválasztott üzenetváltási továbbítókiszolgálókkal szemben.\nEngedélyezze a *Hálózat és kiszolgálók* menüben."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Protokoll időtúllépése a háttérben"; + /* No comment provided by engineer. */ "Protocol timeout" = "Protokoll időtúllépése"; @@ -4023,7 +4226,7 @@ time to disappear */ "Rate the app" = "Értékelje az alkalmazást"; /* No comment provided by engineer. */ -"Reachable chat toolbar" = "Könnyen elérhető eszköztár"; +"Reachable chat toolbar" = "Könnyen elérhető csevegési eszköztár"; /* chat item menu */ "React…" = "Reagálj…"; @@ -4044,7 +4247,7 @@ time to disappear */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; /* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "További információ a [GitHub tárolóban](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "További információ a [GitHub-tárolónkban](https://github.com/simplex-chat/simplex-chat#readme)."; /* No comment provided by engineer. */ "Receipts are disabled" = "A kézbesítési jelentések le vannak tiltva"; @@ -4080,7 +4283,7 @@ time to disappear */ "Received total" = "Összes fogadott üzenet"; /* No comment provided by engineer. */ -"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "A fogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása a feladó online állapotba kerülése után fejeződik be."; +"Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Az üzenetfogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása akkor fejeződik be, amikor az üzenetküldési kiszolgáló online lesz."; /* No comment provided by engineer. */ "Receiving file will be stopped." = "A fájl fogadása le fog állni."; @@ -4136,15 +4339,19 @@ time to disappear */ /* token status text */ "Registered" = "Regisztrálva"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Elutasítás"; /* No comment provided by engineer. */ -"Reject (sender NOT notified)" = "Elutasítás (a feladó NEM kap értesítést)"; +"Reject (sender NOT notified)" = "Elutasítás (a kérés küldője NEM fog értesítést kapni)"; -/* No comment provided by engineer. */ -"Reject contact request" = "Meghívási kérés elutasítása"; +/* alert title */ +"Reject contact request" = "Partneri kapcsolatkérés elutasítása"; + +/* alert title */ +"Reject member?" = "Elutasítja a tagot?"; /* No comment provided by engineer. */ "rejected" = "elutasítva"; @@ -4153,10 +4360,10 @@ swipe action */ "rejected call" = "elutasított hívás"; /* No comment provided by engineer. */ -"Relay server is only used if necessary. Another party can observe your IP address." = "A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet."; +"Relay server is only used if necessary. Another party can observe your IP address." = "A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét."; /* No comment provided by engineer. */ -"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát."; +"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát."; /* No comment provided by engineer. */ "Remove" = "Eltávolítás"; @@ -4167,6 +4374,9 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Kép eltávolítása"; +/* No comment provided by engineer. */ +"Remove link tracking" = "Nyomonkövetési paraméterek eltávolítása a hivatkozásokból"; + /* No comment provided by engineer. */ "Remove member" = "Eltávolítás"; @@ -4185,12 +4395,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "eltávolította a kapcsolattartási címet"; +/* No comment provided by engineer. */ +"removed from group" = "eltávolítva a csoportból"; + /* profile update event chat item */ "removed profile picture" = "eltávolította a profilképét"; /* rcv group event chat item */ "removed you" = "eltávolította Önt"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Üzenetek eltávolítása és a tagok tiltása."; + /* No comment provided by engineer. */ "Renegotiate" = "Újraegyeztetés"; @@ -4200,18 +4416,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Újraegyezteti a titkosítást?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Megismétli a meghívási kérést?"; - /* No comment provided by engineer. */ "Repeat download" = "Letöltés ismét"; /* No comment provided by engineer. */ "Repeat import" = "Importálás ismét"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Megismétli a meghívási kérést?"; - /* No comment provided by engineer. */ "Repeat upload" = "Feltöltés ismét"; @@ -4233,6 +4443,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Jelentés indoklása?"; +/* alert title */ +"Report sent to moderators" = "A jelentés el lett küldve a moderátoroknak"; + /* report reason */ "Report spam: only group moderators will see it." = "Kéretlen tartalom jelentése: csak a csoport moderátorai látják."; @@ -4248,8 +4461,20 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Jelentések"; +/* No comment provided by engineer. */ +"request is sent" = "kérés elküldve"; + +/* No comment provided by engineer. */ +"request to join rejected" = "csatlakozási kérés elutasítva"; + +/* rcv group event chat item */ +"requested connection" = "partneri kapcsolatot kért"; + +/* rcv direct event chat item */ +"requested connection from group %@" = "a(z) %@ nevű csoportból partneri kapcsolatot kért"; + /* chat list item title */ -"requested to connect" = "Függőben lévő meghívási kérelem"; +"requested to connect" = "függőben lévő kapcsolat"; /* No comment provided by engineer. */ "Required" = "Szükséges"; @@ -4296,15 +4521,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Hiba történt az adatbázis visszaállításakor"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Újrapróbálkozás"; /* chat item action */ "Reveal" = "Felfedés"; +/* No comment provided by engineer. */ +"review" = "áttekintés"; + /* No comment provided by engineer. */ "Review conditions" = "Feltételek felülvizsgálata"; +/* No comment provided by engineer. */ +"Review group members" = "Csoporttagok áttekintése"; + +/* admission stage */ +"Review members" = "Tagok áttekintése"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Tagok áttekintése a befogadás előtt (kopogtatás)."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "áttekintve a moderátorok által"; + /* No comment provided by engineer. */ "Revoke" = "Visszavonás"; @@ -4333,6 +4573,12 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Mentés (és a partnerek értesítése)"; +/* alert button */ +"Save (and notify members)" = "Mentés (és a tagok értesítése)"; + +/* alert title */ +"Save admission settings?" = "Menti a befogadási beállításokat?"; + /* alert button */ "Save and notify contact" = "Mentés és a partner értesítése"; @@ -4348,6 +4594,9 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Csoportprofil mentése"; +/* alert title */ +"Save group profile?" = "Menti a csoportprofilt?"; + /* No comment provided by engineer. */ "Save list" = "Lista mentése"; @@ -4382,10 +4631,10 @@ chat item action */ "Saved" = "Mentett"; /* No comment provided by engineer. */ -"Saved from" = "Elmentve innen"; +"Saved from" = "Mentve innen"; /* No comment provided by engineer. */ -"saved from %@" = "elmentve innen: %@"; +"saved from %@" = "mentve innen: %@"; /* message info title */ "Saved message" = "Mentett üzenet"; @@ -4415,7 +4664,7 @@ chat item action */ "Scan security code from your contact's app." = "Biztonsági kód beolvasása a partnere alkalmazásából."; /* No comment provided by engineer. */ -"Scan server QR code" = "A kiszolgáló QR-kódjának beolvasása"; +"Scan server QR code" = "Kiszolgáló QR-kódjának beolvasása"; /* No comment provided by engineer. */ "search" = "keresés"; @@ -4442,7 +4691,7 @@ chat item action */ "secret" = "titok"; /* server test step */ -"Secure queue" = "Biztonságos sorba állítás"; +"Secure queue" = "Biztonságos várólista"; /* No comment provided by engineer. */ "Secured" = "Biztosítva"; @@ -4454,7 +4703,7 @@ chat item action */ "Security code" = "Biztonsági kód"; /* chat item text */ -"security code changed" = "a biztonsági kód módosult"; +"security code changed" = "biztonsági kódja módosult"; /* chat item action */ "Select" = "Kijelölés"; @@ -4472,13 +4721,13 @@ chat item action */ "Self-destruct" = "Önmegsemmisítés"; /* No comment provided by engineer. */ -"Self-destruct passcode" = "Önmegsemmisítő-jelkód"; +"Self-destruct passcode" = "Önmegsemmisítő jelkód"; /* No comment provided by engineer. */ -"Self-destruct passcode changed!" = "Az önmegsemmisítő-jelkód módosult!"; +"Self-destruct passcode changed!" = "Az önmegsemmisítő jelkód módosult!"; /* No comment provided by engineer. */ -"Self-destruct passcode enabled!" = "Az önmegsemmisítő-jelkód engedélyezve!"; +"Self-destruct passcode enabled!" = "Az önmegsemmisítő jelkód engedélyezve!"; /* No comment provided by engineer. */ "Send" = "Küldés"; @@ -4487,10 +4736,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Élő üzenet küldése – az üzenet a címzett(ek) számára valós időben frissül, ahogy Ön beírja az üzenetet"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "A kézbesítési jelentéseket a következő címre kell küldeni"; +"Send contact request?" = "Elküldi a partneri kapcsolatkérést?"; /* No comment provided by engineer. */ -"send direct message" = "közvetlen üzenet küldése"; +"Send delivery receipts to" = "A kézbesítési jelentéseket a következő címre kell küldeni"; /* No comment provided by engineer. */ "Send direct message to connect" = "Közvetlen üzenet küldése a kapcsolódáshoz"; @@ -4502,7 +4751,7 @@ chat item action */ "Send errors" = "Üzenetküldési hibák"; /* No comment provided by engineer. */ -"Send link previews" = "Hivatkozás előnézete"; +"Send link previews" = "Hivatkozások előnézetének megjelenítése"; /* No comment provided by engineer. */ "Send live message" = "Élő üzenet küldése"; @@ -4528,17 +4777,26 @@ chat item action */ /* No comment provided by engineer. */ "Send receipts" = "Kézbesítési jelentések küldése"; +/* No comment provided by engineer. */ +"Send request" = "Kérés küldése"; + +/* No comment provided by engineer. */ +"Send request without message" = "Kérés küldése üzenet nélkül"; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Küldje el őket a galériából vagy az egyéni billentyűzetekről."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára."; +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Küldjön privát visszajelzést a csoportoknak."; + /* alert message */ "Sender cancelled file transfer." = "A fájl küldője visszavonta az átvitelt."; /* No comment provided by engineer. */ -"Sender may have deleted the connection request." = "A küldője törölhette a meghívási kérést."; +"Sender may have deleted the connection request." = "A kérés küldője törölhette a kapcsolódási kérést."; /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára."; @@ -4616,16 +4874,16 @@ chat item action */ "Server operators" = "Kiszolgálóüzemeltetők"; /* alert title */ -"Server protocol changed." = "A kiszolgáló-protokoll módosult."; +"Server protocol changed." = "A kiszolgálóprotokoll módosult."; /* queue info */ -"server queue info: %@\n\nlast received msg: %@" = "a kiszolgáló sorbaállítási információi: %1$@\n\nutoljára fogadott üzenet: %2$@"; +"server queue info: %@\n\nlast received msg: %@" = "a kiszolgáló várólista információi: %1$@\n\nutoljára fogadott üzenet: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "A kiszolgálónak engedélyre van szüksége a sorba állítás létrehozásához, ellenőrizze a jelszavát"; +"Server requires authorization to create queues, check password." = "A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze a jelszavát"; /* server test error */ -"Server requires authorization to upload, check password" = "A kiszolgálónak hitelesítésre van szüksége a feltöltéshez, ellenőrizze jelszavát"; +"Server requires authorization to upload, check password." = "A kiszolgálónak hitelesítésre van szüksége a feltöltéshez, ellenőrizze jelszavát"; /* No comment provided by engineer. */ "Server test failed!" = "Sikertelen kiszolgáló teszt!"; @@ -4669,6 +4927,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Beállítás a rendszer-hitelesítés helyett."; +/* No comment provided by engineer. */ +"Set member admission" = "Tagbefogadás beállítása"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Üzenetek eltűnési idejének módosítása a csevegésekben."; @@ -4687,6 +4948,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Jelmondat beállítása az exportáláshoz"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Névjegy és üdvözlőüzenet beállítása a profilokhoz."; + /* No comment provided by engineer. */ "Set the message shown to new members!" = "Megjelenítendő üzenet beállítása az új tagok számára!"; @@ -4727,6 +4991,12 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Megosztás"; +/* alert button */ +"Share old address" = "Régi cím megosztása"; + +/* alert button */ +"Share old link" = "Teljes hivatkozás megosztása"; + /* No comment provided by engineer. */ "Share profile" = "Profil megosztása"; @@ -4742,9 +5012,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Megosztás a partnerekkel"; +/* No comment provided by engineer. */ +"Share your address" = "Saját cím megosztása"; + +/* No comment provided by engineer. */ +"Short description" = "Rövid leírás"; + /* No comment provided by engineer. */ "Short link" = "Rövid hivatkozás"; +/* No comment provided by engineer. */ +"Short SimpleX address" = "Rövid SimpleX-cím"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Egy „→” jel megjelenítése a privát útválasztáson keresztül küldött üzeneteknél."; @@ -4755,7 +5034,7 @@ chat item action */ "Show developer options" = "Fejlesztői beállítások megjelenítése"; /* No comment provided by engineer. */ -"Show last messages" = "Legutóbbi üzenet előnézetének megjelenítése"; +"Show last messages" = "Legutóbbi üzenetek előnézetének megjelenítése"; /* No comment provided by engineer. */ "Show message status" = "Üzenet állapotának megjelenítése"; @@ -4764,7 +5043,7 @@ chat item action */ "Show percentage" = "Százalék megjelenítése"; /* No comment provided by engineer. */ -"Show preview" = "Értesítés előnézete"; +"Show preview" = "Értesítésekben megjelenő információk"; /* No comment provided by engineer. */ "Show QR code" = "QR-kód megjelenítése"; @@ -4787,6 +5066,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?"; +/* alert title */ +"SimpleX address settings" = "Beállítások automatikus elfogadása"; + /* simplex link type */ "SimpleX channel link" = "SimpleX-csatornahivatkozás"; @@ -4827,10 +5109,13 @@ chat item action */ "SimpleX Lock turned on" = "SimpleX-zár bekapcsolva"; /* simplex link type */ -"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó"; +"SimpleX one-time invitation" = "Egyszer használható SimpleX meghívó"; /* No comment provided by engineer. */ -"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva."; +"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX protokollokat a Trail of Bits auditálta."; + +/* simplex link type */ +"SimpleX relay link" = "SimpleX továbbítókiszolgáló-hivatkozás"; /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognitómód"; @@ -4851,7 +5136,7 @@ chat item action */ "SMP server" = "SMP-kiszolgáló"; /* No comment provided by engineer. */ -"SOCKS proxy" = "SOCKS-proxy"; +"SOCKS proxy" = "SOCKS proxy"; /* blur media */ "Soft" = "Enyhe"; @@ -4980,11 +5265,23 @@ report reason */ /* No comment provided by engineer. */ "Tap button " = "Koppintson a "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Koppintson a „Kapcsolódás” gombra a csevegéshez"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Koppintson a „Kapcsolódás” gombra a kérés elküldéséhez"; + +/* No comment provided by engineer. */ +"Tap Connect to use bot" = "Koppintson a „Kapcsolódás” gombra a bot használatához"; + /* No comment provided by engineer. */ "Tap Create SimpleX address in the menu to create it later." = "Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz."; /* No comment provided by engineer. */ -"Tap to activate profile." = "A profil aktiválásához koppintson az ikonra."; +"Tap Join group" = "Koppintson a „Csatlakozás a csoporthoz” gombra"; + +/* No comment provided by engineer. */ +"Tap to activate profile." = "Koppintson ide a profil aktiválásához."; /* No comment provided by engineer. */ "Tap to Connect" = "Koppintson ide a kapcsolódáshoz"; @@ -5004,6 +5301,9 @@ report reason */ /* No comment provided by engineer. */ "TCP connection" = "TCP-kapcsolat"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "TCP-kapcsolat időtúllépése a háttérben"; + /* No comment provided by engineer. */ "TCP connection timeout" = "TCP-kapcsolat időtúllépése"; @@ -5046,8 +5346,11 @@ report reason */ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak a Weblate-en való közreműködésért!"; +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "A cím rövid lesz és a profil meg lesz osztva a címen keresztül."; + /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy meghívási kéréseket kap – ezt a beállítások menüben engedélyezheti."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kéréseket kap – ezt a beállítások menüben engedélyezheti."; /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ."; @@ -5059,13 +5362,13 @@ report reason */ "The attempt to change database passphrase was not completed." = "Az adatbázis jelmondatának módosítására tett kísérlet nem fejeződött be."; /* No comment provided by engineer. */ -"The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás."; +"The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX-hivatkozás."; /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van."; /* No comment provided by engineer. */ -"The connection you accepted will be cancelled!" = "Az Ön által elfogadott kérelem vissza lesz vonva!"; +"The connection you accepted will be cancelled!" = "Az Ön által elfogadott kapcsolat vissza lesz vonva!"; /* No comment provided by engineer. */ "The contact you shared this link with will NOT be able to connect!" = "A partnere, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!"; @@ -5080,11 +5383,14 @@ report reason */ "The future of messaging" = "Az üzenetváltás jövője"; /* No comment provided by engineer. */ -"The hash of the previous message is different." = "Az előző üzenet hasítóértéke különbözik."; +"The hash of the previous message is different." = "Az előző üzenet kivonata különbözik."; /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója érvénytelen (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "A hivatkozás rövid lesz és a csoportprofil meg lesz osztva a hivatkozáson keresztül."; + /* No comment provided by engineer. */ "The message will be deleted for all members." = "Az üzenet az összes tag számára törölve lesz."; @@ -5100,9 +5406,6 @@ report reason */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "A profilja csak a partnereivel van megosztva."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**."; @@ -5112,8 +5415,8 @@ report reason */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅"; -/* No comment provided by engineer. */ -"The sender will NOT be notified" = "A feladó NEM fog értesítést kapni"; +/* alert message */ +"The sender will NOT be notified" = "A kérés küldője NEM fog értesítést kapni"; /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "A jelenlegi **%@** nevű csevegési profiljához tartozó új kapcsolatok kiszolgálói."; @@ -5172,12 +5475,6 @@ report reason */ /* No comment provided by engineer. */ "This group no longer exists." = "Ez a csoport már nem létezik."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez a saját egyszer használható meghívója!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Ez a saját SimpleX-címe!"; - /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől."; @@ -5190,6 +5487,12 @@ report reason */ /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás csak az Ön jelenlegi **%@** nevű csevegési profiljában lévő üzenetekre vonatkozik."; +/* No comment provided by engineer. */ +"This setting is for your current profile **%@**." = "Ez a beállítás csak a jelenlegi **%@** nevű csevegési profiljára vonatkozik."; + +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Az üzeneteltűnési idő csak az új partnerekre vonatkozik."; + /* No comment provided by engineer. */ "Title" = "Cím"; @@ -5218,7 +5521,7 @@ report reason */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez."; /* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Adatainak védelme érdekében a SimpleX külön azonosítókat használ minden egyes kapcsolatához."; /* No comment provided by engineer. */ "To receive" = "A fogadáshoz"; @@ -5238,9 +5541,15 @@ report reason */ /* No comment provided by engineer. */ "To send" = "A küldéshez"; +/* alert message */ +"To send commands you must be connected." = "A parancsok küldéséhez kapcsolódva kell lennie."; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Másik profil használatához a kapcsolatfelvételi kísérlet után törölje a csevegést, és használja újra a hivatkozást."; + /* No comment provided by engineer. */ "To use the servers of **%@**, accept conditions of use." = "A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket."; @@ -5248,10 +5557,10 @@ report reason */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal."; /* No comment provided by engineer. */ -"Toggle chat list:" = "Csevegési lista átváltása:"; +"Toggle chat list:" = "Csevegési lista ki/be:"; /* No comment provided by engineer. */ -"Toggle incognito when connecting." = "Inkognitóra váltás kapcsolódáskor."; +"Toggle incognito when connecting." = "Inkognitó profil használata kapcsolódáskor ki/be."; /* token status */ "Token status: %@." = "Token állapota: %@."; @@ -5263,7 +5572,7 @@ report reason */ "Total" = "Összes kapcsolat"; /* No comment provided by engineer. */ -"Transport isolation" = "Átvitel-izoláció"; +"Transport isolation" = "Átvitelelkülönítés"; /* No comment provided by engineer. */ "Transport sessions" = "Munkamenetek átvitele"; @@ -5347,7 +5656,7 @@ report reason */ "unknown status" = "ismeretlen állapot"; /* No comment provided by engineer. */ -"Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében."; +"Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakadások elkerülése érdekében."; /* No comment provided by engineer. */ "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak a partnere nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát.\nA kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e."; @@ -5395,17 +5704,35 @@ report reason */ "Updated conditions" = "Frissített feltételek"; /* rcv group event chat item */ -"updated group profile" = "frissítette a csoport profilját"; +"updated group profile" = "frissítette a csoportprofilt"; /* profile update event chat item */ -"updated profile" = "frissített profil"; +"updated profile" = "frissítette a profilját"; /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár."; +/* alert button */ +"Upgrade" = "Frissítés"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Cím frissítése"; + +/* alert message */ +"Upgrade address?" = "Frissíti a címet?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Fejlesztés és a csevegés megnyitása"; +/* alert message */ +"Upgrade group link?" = "Frissíti a csoporthivatkozást?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Hivatkozás frissítése"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Cím frissítése"; + /* No comment provided by engineer. */ "Upload errors" = "Feltöltési hibák"; @@ -5425,7 +5752,7 @@ report reason */ "Uploading archive" = "Archívum feltöltése"; /* No comment provided by engineer. */ -"Use .onion hosts" = "Onion-kiszolgálók használata"; +"Use .onion hosts" = "Onion kiszolgálók használata"; /* No comment provided by engineer. */ "Use %@" = "%@ használata"; @@ -5433,7 +5760,7 @@ report reason */ /* No comment provided by engineer. */ "Use chat" = "SimpleX Chat használata"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Jelenlegi profil használata"; /* No comment provided by engineer. */ @@ -5443,25 +5770,28 @@ report reason */ "Use for messages" = "Használat az üzenetekhez"; /* No comment provided by engineer. */ -"Use for new connections" = "Alkalmazás új kapcsolatokhoz"; +"Use for new connections" = "Használat új kapcsolatokhoz"; /* No comment provided by engineer. */ "Use from desktop" = "Társítás számítógéppel"; /* No comment provided by engineer. */ -"Use iOS call interface" = "Az iOS hívási felületét használata"; +"Use incognito profile" = "Inkognitóprofil használata"; /* No comment provided by engineer. */ +"Use iOS call interface" = "iOS hívási felület használata"; + +/* new chat action */ "Use new incognito profile" = "Új inkognitóprofil használata"; /* No comment provided by engineer. */ "Use only local notifications?" = "Csak helyi értesítések használata?"; /* No comment provided by engineer. */ -"Use private routing with unknown servers when IP address is not protected." = "Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett."; +"Use private routing with unknown servers when IP address is not protected." = "Privát útválasztás használata az ismeretlen kiszolgálókkal, ha az IP-cím nem védett."; /* No comment provided by engineer. */ -"Use private routing with unknown servers." = "Használjon privát útválasztást ismeretlen kiszolgálókkal."; +"Use private routing with unknown servers." = "Privát útválasztás használata ismeretlen kiszolgálókkal."; /* No comment provided by engineer. */ "Use server" = "Kiszolgáló használata"; @@ -5470,13 +5800,10 @@ report reason */ "Use servers" = "Kiszolgálók használata"; /* No comment provided by engineer. */ -"Use short links (BETA)" = "Rövid hivatkozások használata (béta)"; +"Use SimpleX Chat servers?" = "SimpleX Chat kiszolgálók használata?"; /* No comment provided by engineer. */ -"Use SimpleX Chat servers?" = "SimpleX Chat-kiszolgálók használata?"; - -/* No comment provided by engineer. */ -"Use SOCKS proxy" = "SOCKS-proxy használata"; +"Use SOCKS proxy" = "SOCKS proxy használata"; /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "A következő TCP-port használata, amikor nincs port megadva: %@."; @@ -5485,10 +5812,10 @@ report reason */ "Use TCP port 443 for preset servers only." = "A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz."; /* No comment provided by engineer. */ -"Use the app while in the call." = "Használja az alkalmazást hívás közben."; +"Use the app while in the call." = "Alkalmazás használata hívás közben."; /* No comment provided by engineer. */ -"Use the app with one hand." = "Használja az alkalmazást egy kézzel."; +"Use the app with one hand." = "Alkalmazás egy kézzel való használata."; /* No comment provided by engineer. */ "Use web port" = "Webport használata"; @@ -5500,7 +5827,7 @@ report reason */ "Username" = "Felhasználónév"; /* No comment provided by engineer. */ -"Using SimpleX Chat servers." = "SimpleX Chat-kiszolgálók használatban."; +"Using SimpleX Chat servers." = "SimpleX Chat kiszolgálók használatban."; /* No comment provided by engineer. */ "v%@" = "v%@"; @@ -5542,7 +5869,7 @@ report reason */ "via one-time link" = "egy egyszer használható meghívón keresztül"; /* No comment provided by engineer. */ -"via relay" = "egy továbbítókiszolgálón keresztül"; +"via relay" = "továbbítókiszolgálón keresztül"; /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "Biztonságos kvantumbiztos protokollon keresztül."; @@ -5643,6 +5970,9 @@ report reason */ /* No comment provided by engineer. */ "Welcome message is too long" = "Az üdvözlőüzenet túl hosszú"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Üdvözölje a partnereit 👋"; + /* No comment provided by engineer. */ "What's new" = "Újdonságok"; @@ -5659,7 +5989,7 @@ report reason */ "When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Amikor egynél több üzemeltető van engedélyezve, akkor egyik sem rendelkezik olyan metaadatokkal, amelyekből megtudható, hogy ki kivel kommunikál."; /* No comment provided by engineer. */ -"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; +"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt."; /* No comment provided by engineer. */ "WiFi" = "Wi-Fi"; @@ -5680,10 +6010,10 @@ report reason */ "With reduced battery usage." = "Csökkentett akkumulátor-használattal."; /* No comment provided by engineer. */ -"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára."; +"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára."; /* alert message */ -"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@."; +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára: %@."; /* No comment provided by engineer. */ "Wrong database passphrase" = "Érvénytelen adatbázis-jelmondat"; @@ -5710,7 +6040,10 @@ report reason */ "You **must not** use the same database on two devices." = "**Nem szabad** ugyanazt az adatbázist használni egyszerre két eszközön."; /* No comment provided by engineer. */ -"You accepted connection" = "Kapcsolat létrehozása"; +"You accepted connection" = "Ön elfogadta a kapcsolatot"; + +/* snd group event chat item */ +"you accepted this member" = "Ön befogadta ezt a tagot"; /* No comment provided by engineer. */ "You allow" = "Ön engedélyezi"; @@ -5724,33 +6057,27 @@ report reason */ /* No comment provided by engineer. */ "You are already connected with %@." = "Ön már kapcsolódva van vele: %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "A kapcsolódás már folyamatban van a következőhöz: %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható meghívón keresztül!"; /* No comment provided by engineer. */ "You are already in group %@." = "Ön már a(z) %@ nevű csoport tagja."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "A csatlakozás már folyamatban van a(z) %@ nevű csoporthoz."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül."; -/* No comment provided by engineer. */ -"You are already joining the group!\nRepeat join request?" = "A csatlakozás már folyamatban van a csoporthoz!\nMegismétli a meghívási kérést?"; +/* new chat sheet title */ +"You are already joining the group!\nRepeat join request?" = "A csatlakozás már folyamatban van a csoporthoz!\nMegismétli a csatlakozási kérést?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál."; -/* No comment provided by engineer. */ -"you are invited to group" = "Ön meghívást kapott a csoportba"; - /* No comment provided by engineer. */ "You are invited to group" = "Ön meghívást kapott a csoportba"; @@ -5764,7 +6091,7 @@ report reason */ "you blocked %@" = "Ön letiltotta őt: %@"; /* No comment provided by engineer. */ -"You can accept calls from lock screen, without device and app authentication." = "Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazás-hitelesítés nélkül."; +"You can accept calls from lock screen, without device and app authentication." = "A lezárási képernyőről is fogadhat hívásokat, eszköz- és alkalmazáshitelesítés nélkül."; /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Ezt a „Megjelenés” menüben módosíthatja."; @@ -5776,7 +6103,7 @@ report reason */ "You can create it later" = "Létrehozás később"; /* No comment provided by engineer. */ -"You can enable later via Settings" = "Később engedélyezheti a „Beállításokban”"; +"You can enable later via Settings" = "Később engedélyezheti a beállításokban"; /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az „Adatvédelem és biztonság” menüben."; @@ -5788,7 +6115,7 @@ report reason */ "You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenémíthatja a felhasználó -profiljait – csúsztassa jobbra a profilt."; /* No comment provided by engineer. */ -"You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti a SimpleXbeli partnerei számára a „Beállításokban”."; +"You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti a SimpleXbeli partnerei számára a beállításokban."; /* notification body */ "You can now chat with %@" = "Mostantól küldhet üzeneteket %@ számára"; @@ -5803,7 +6130,7 @@ report reason */ "You can set lock screen notification preview via settings." = "A lezárási képernyő értesítési előnézetét az „Értesítések” menüben állíthatja be."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoportot Ön később törli, akkor nem fogja elveszíteni annak tagjait."; +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoporthivatkozást később törli, akkor nem fogja elveszíteni a csoport meglévő tagjait."; /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a SimpleX-címet a partnereivel, hogy kapcsolatba léphessenek vele: **%@**."; @@ -5823,7 +6150,10 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben."; + +/* alert title */ "You can't send messages!" = "Nem lehet üzeneteket küldeni!"; /* chat item text */ @@ -5844,11 +6174,8 @@ report reason */ /* No comment provided by engineer. */ "You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Már küldött egy meghívási kérést ezen a címen keresztül!"; - -/* No comment provided by engineer. */ -"You have already requested connection!\nRepeat connection request?" = "Ön már küldött egy meghívási kérést!\nMegismétli a meghívási kérést?"; +/* new chat sheet title */ +"You have already requested connection!\nRepeat connection request?" = "Ön már küldött egy kapcsolódási kérést!\nMegismétli a kapcsolódási kérést?"; /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul – nem az eszközön van tárolva."; @@ -5869,7 +6196,7 @@ report reason */ "You may migrate the exported database." = "Az exportált adatbázist átköltöztetheti."; /* No comment provided by engineer. */ -"You may save the exported archive." = "Az exportált archívumot elmentheti."; +"You may save the exported archive." = "Mentheti az exportált archívumot."; /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi partnerétől."; @@ -5901,6 +6228,9 @@ report reason */ /* snd group event chat item */ "you unblocked %@" = "Ön feloldotta %@ letiltását"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Csak azután tud üzeneteket küldeni, **miután a kérését elfogadták**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; @@ -5908,7 +6238,7 @@ report reason */ "You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoporthivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ -"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később!"; +"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolódási kérését elfogadják, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ "You will be connected when your contact's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a partnerének az eszköze online lesz, várjon, vagy ellenőrizze később!"; @@ -5916,9 +6246,6 @@ report reason */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Az alkalmazás elindításához vagy 30 másodpercnyi háttérben töltött idő után, az alkalmazáshoz való visszatéréshez hitelesítésre lesz szükség."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Kapcsolódni fog a csoport összes tagjához."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak."; @@ -5940,6 +6267,9 @@ report reason */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva"; +/* No comment provided by engineer. */ +"Your business contact" = "Üzleti partner"; + /* No comment provided by engineer. */ "Your calls" = "Hívások"; @@ -5955,8 +6285,14 @@ report reason */ /* No comment provided by engineer. */ "Your chat profiles" = "Csevegési profilok"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "A csevegés át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányításkor."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "A kapcsolata át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányításkor."; +"Your connection was moved to %@ but an error happened when switching profile." = "A kapcsolata át lett helyezve ide: %@, de egy váratlan hiba történt a profilra való átirányításkor."; + +/* No comment provided by engineer. */ +"Your contact" = "Partner"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "A partnere a jelenleg megengedett maximális méretű (%@) fájlnál nagyobbat küldött."; @@ -5968,7 +6304,7 @@ report reason */ "Your contacts will remain connected." = "A partnerei továbbra is kapcsolódva maradnak."; /* No comment provided by engineer. */ -"Your credentials may be sent unencrypted." = "A hitelesítőadatai titkosítatlanul is elküldhetők."; +"Your credentials may be sent unencrypted." = "A hitelesítési adati titkosítatlanul is elküldhetők."; /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "A jelenlegi csevegési adatbázis TÖRÖLVE és CSERÉLVE lesz az importáltra."; @@ -5976,6 +6312,9 @@ report reason */ /* No comment provided by engineer. */ "Your current profile" = "Jelenlegi profil"; +/* No comment provided by engineer. */ +"Your group" = "Saját csoport"; + /* No comment provided by engineer. */ "Your ICE servers" = "Saját ICE-kiszolgálók"; @@ -5992,13 +6331,13 @@ report reason */ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja meg lesz osztva."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját."; - -/* alert message */ -"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára."; +"Your profile is stored on your device and only shared with your contacts." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva."; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX kiszolgálók nem láthatják a profilját."; + +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha menti, akkor a profilfrissítés el lesz küldve a partnerei számára."; /* No comment provided by engineer. */ "Your random profile" = "Véletlenszerű profil"; diff --git a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings index f389e41458..8b56c51595 100644 --- a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings @@ -14,5 +14,5 @@ "NSMicrophoneUsageDescription" = "A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez."; /* Privacy - Photo Library Additions Usage Description */ -"NSPhotoLibraryAddUsageDescription" = "A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez"; +"NSPhotoLibraryAddUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a galériához a rögzített és fogadott média mentéséhez"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index b914a06079..2eba58ab47 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -342,22 +342,38 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Accetta"; +/* alert action */ +"Accept as member" = "Accetta come membro"; + +/* alert action */ +"Accept as observer" = "Accetta come osservatore"; + /* No comment provided by engineer. */ "Accept conditions" = "Accetta le condizioni"; /* No comment provided by engineer. */ "Accept connection request?" = "Accettare la richiesta di connessione?"; +/* alert title */ +"Accept contact request" = "Accetta la richiesta di contatto"; + /* notification body */ "Accept contact request from %@?" = "Accettare la richiesta di contatto da %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Accetta in incognito"; +/* alert title */ +"Accept member" = "Accetta membro"; + +/* rcv group event chat item */ +"accepted %@" = "%@ accettato"; + /* call status */ "accepted call" = "chiamata accettata"; @@ -367,6 +383,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "invito accettato"; +/* rcv group event chat item */ +"accepted you" = "ti ha accettato/a"; + /* No comment provided by engineer. */ "Acknowledged" = "Riconosciuto"; @@ -388,6 +407,9 @@ swipe action */ /* No comment provided by engineer. */ "Add list" = "Aggiungi elenco"; +/* placeholder for sending contact request */ +"Add message" = "Aggiungi un messaggio"; + /* No comment provided by engineer. */ "Add profile" = "Aggiungi profilo"; @@ -463,6 +485,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "concordando la crittografia…"; +/* member criteria value */ +"all" = "tutti"; + /* No comment provided by engineer. */ "All" = "Tutte"; @@ -532,6 +557,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Consenti downgrade"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "Consenti file e contenuti multimediali solo se il tuo contatto li consente."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore)"; @@ -583,16 +611,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Permetti ai tuoi contatti di inviare messaggi a tempo."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Consenti ai tuoi contatti di inviare file e contenuti multimediali."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Permetti ai tuoi contatti di inviare messaggi vocali."; /* No comment provided by engineer. */ "Already connected?" = "Già connesso/a?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Già in connessione!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Già in ingresso nel gruppo!"; /* pref value */ @@ -736,9 +767,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Auto-accetta le immagini"; -/* alert title */ -"Auto-accept settings" = "Accetta automaticamente le impostazioni"; - /* No comment provided by engineer. */ "Back" = "Indietro"; @@ -790,6 +818,12 @@ swipe action */ /* No comment provided by engineer. */ "Better user experience" = "Esperienza utente migliorata"; +/* No comment provided by engineer. */ +"Bio" = "Bio"; + +/* alert title */ +"Bio too large" = "Bio troppo lunga"; + /* No comment provided by engineer. */ "Black" = "Nero"; @@ -833,6 +867,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "bold" = "grassetto"; +/* No comment provided by engineer. */ +"Bot" = "Bot"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "Sia tu che il tuo contatto potete aggiungere reazioni ai messaggi."; @@ -845,6 +882,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Sia tu che il tuo contatto potete inviare messaggi a tempo."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Sia tu che il tuo contatto potete inviare file e contenuti multimediali."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Sia tu che il tuo contatto potete inviare messaggi vocali."; @@ -857,6 +897,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Chat di lavoro"; +/* No comment provided by engineer. */ +"Business connection" = "Connessione lavorativa"; + /* No comment provided by engineer. */ "Businesses" = "Lavorative"; @@ -896,6 +939,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Impossibile chiamare il membro"; +/* alert title */ +"Can't change profile" = "Impossibile cambiare profilo"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Impossibile invitare il contatto!"; @@ -905,8 +951,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Impossibile inviare un messaggio al membro"; +/* No comment provided by engineer. */ +"can't send messages" = "impossibile inviare messaggi"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Annulla"; /* No comment provided by engineer. */ @@ -988,7 +1038,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "La chat esiste già"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "La chat esiste già!"; /* No comment provided by engineer. */ @@ -1042,9 +1092,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!"; +/* chat toolbar */ +"Chat with admins" = "Chat con amministratori"; + +/* No comment provided by engineer. */ +"Chat with member" = "Chatta con il membro"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Chatta con i membri prima che si uniscano."; + /* No comment provided by engineer. */ "Chats" = "Chat"; +/* No comment provided by engineer. */ +"Chats with members" = "Chat con membri"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Controlla i messaggi ogni 20 min."; @@ -1187,7 +1249,7 @@ set passcode view */ "Connect automatically" = "Connetti automaticamente"; /* No comment provided by engineer. */ -"Connect incognito" = "Connetti in incognito"; +"Connect faster! 🚀" = "Connettiti più velocemente! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Connetti al desktop"; @@ -1198,25 +1260,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Connettiti più velocemente ai tuoi amici."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Connettersi a te stesso?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Connettersi a te stesso?\nQuesto è il tuo link una tantum!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Connettersi a te stesso?\nQuesto è il tuo indirizzo SimpleX!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Connettere via indirizzo del contatto"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Connetti via link"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Connetti via link una tantum"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Connettersi con %@"; /* No comment provided by engineer. */ @@ -1228,9 +1287,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Desktop connesso"; -/* rcv group event chat item */ -"connected directly" = "si è connesso/a direttamente"; - /* No comment provided by engineer. */ "Connected servers" = "Server connessi"; @@ -1282,7 +1338,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "Connessione bloccata"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Errore di connessione"; /* No comment provided by engineer. */ @@ -1312,7 +1368,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Connessione terminata"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Connessione scaduta"; /* No comment provided by engineer. */ @@ -1333,9 +1389,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Il contatto esiste già"; +/* No comment provided by engineer. */ +"contact deleted" = "contatto eliminato"; + /* No comment provided by engineer. */ "Contact deleted!" = "Contatto eliminato!"; +/* No comment provided by engineer. */ +"contact disabled" = "contatto disattivato"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "il contatto ha la crittografia e2e"; @@ -1354,9 +1416,18 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Nome del contatto"; +/* No comment provided by engineer. */ +"contact not ready" = "contatto non pronto"; + /* No comment provided by engineer. */ "Contact preferences" = "Preferenze del contatto"; +/* No comment provided by engineer. */ +"Contact requests from groups" = "Richieste di contatto dai gruppi"; + +/* No comment provided by engineer. */ +"contact should accept…" = "il contatto dovrebbe accettare…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "Il contatto verrà eliminato - non è reversibile!"; @@ -1424,10 +1495,10 @@ set passcode view */ "Create queue" = "Crea coda"; /* No comment provided by engineer. */ -"Create secret group" = "Crea gruppo segreto"; +"Create SimpleX address" = "Crea indirizzo SimpleX"; /* No comment provided by engineer. */ -"Create SimpleX address" = "Crea indirizzo SimpleX"; +"Create your address" = "Crea il tuo indirizzo"; /* No comment provided by engineer. */ "Create your profile" = "Crea il tuo profilo"; @@ -1602,6 +1673,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Eliminare il profilo di chat?"; +/* alert title */ +"Delete chat with member?" = "Eliminare la chat con il membro?"; + /* No comment provided by engineer. */ "Delete chat?" = "Eliminare la chat?"; @@ -1728,9 +1802,15 @@ swipe action */ /* No comment provided by engineer. */ "Delivery receipts!" = "Ricevute di consegna!"; +/* No comment provided by engineer. */ +"Deprecated options" = "Opzioni deprecate"; + /* No comment provided by engineer. */ "Description" = "Descrizione"; +/* alert title */ +"Description too large" = "Descrizione troppo lunga"; + /* No comment provided by engineer. */ "Desktop address" = "Indirizzo desktop"; @@ -1872,7 +1952,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "Non perdere messaggi importanti."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Non mostrare più"; /* No comment provided by engineer. */ @@ -1933,6 +2013,9 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Modifica il profilo del gruppo"; +/* No comment provided by engineer. */ +"Empty message!" = "Messaggio vuoto!"; + /* No comment provided by engineer. */ "Enable" = "Attiva"; @@ -1945,6 +2028,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Attiva l'accesso alla fotocamera"; +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Attiva i messaggi a tempo in modo predefinito."; + /* No comment provided by engineer. */ "Enable Flux in Network & servers settings for better metadata privacy." = "Attiva Flux nelle impostazioni \"Rete e server\" per una migliore privacy dei metadati."; @@ -2116,22 +2202,31 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Errore nell'accettazione della richiesta di contatto"; +/* alert title */ +"Error accepting member" = "Errore di accettazione del membro"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; /* alert title */ "Error adding server" = "Errore di aggiunta del server"; +/* No comment provided by engineer. */ +"Error adding short link" = "Errore di aggiunta link breve"; + /* No comment provided by engineer. */ "Error changing address" = "Errore nella modifica dell'indirizzo"; +/* alert title */ +"Error changing chat profile" = "Errore cambiando il profilo di chat"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Errore nel cambio di profilo di connessione"; /* No comment provided by engineer. */ "Error changing role" = "Errore nel cambio di ruolo"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Errore nella modifica dell'impostazione"; /* No comment provided by engineer. */ @@ -2140,7 +2235,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Errore di controllo dello stato del token"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Errore di connessione al server di inoltro %@. Riprova più tardi."; /* No comment provided by engineer. */ @@ -2170,19 +2265,22 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Errore decifrando il file"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Errore di eliminazione della chat con il membro"; + +/* alert title */ "Error deleting chat database" = "Errore nell'eliminazione del database della chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Errore nell'eliminazione della chat!"; /* No comment provided by engineer. */ "Error deleting connection" = "Errore nell'eliminazione della connessione"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Errore nell'eliminazione del database"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Errore nell'eliminazione del database vecchio"; /* No comment provided by engineer. */ @@ -2203,13 +2301,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Errore nella crittografia del database"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Errore nell'esportazione del database della chat"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Errore di esportazione del tema: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Errore nell'importazione del database della chat"; /* No comment provided by engineer. */ @@ -2224,6 +2322,9 @@ chat item action */ /* No comment provided by engineer. */ "Error opening chat" = "Errore di apertura della chat"; +/* No comment provided by engineer. */ +"Error opening group" = "Errore di preparazione del gruppo"; + /* alert title */ "Error receiving file" = "Errore nella ricezione del file"; @@ -2236,7 +2337,10 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Errore di registrazione per le notifiche"; -/* No comment provided by engineer. */ +/* alert title */ +"Error rejecting contact request" = "Errore nel rifiuto della richiesta di contatto"; + +/* alert title */ "Error removing member" = "Errore nella rimozione del membro"; /* alert title */ @@ -2281,6 +2385,9 @@ chat item action */ /* No comment provided by engineer. */ "Error sending message" = "Errore nell'invio del messaggio"; +/* No comment provided by engineer. */ +"Error setting auto-accept" = "Errore impostando l'accettazione automatica"; + /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Errore nell'impostazione delle ricevute di consegna!"; @@ -2290,7 +2397,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Errore nell'interruzione della chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Errore nel cambio di profilo"; /* alertTitle */ @@ -2439,6 +2546,9 @@ snd error text */ /* chat feature */ "Files and media" = "File e multimediali"; +/* No comment provided by engineer. */ +"Files and media are prohibited in this chat." = "File e contenuti multimediali sono vietati in questa chat."; + /* No comment provided by engineer. */ "Files and media are prohibited." = "File e contenuti multimediali sono vietati in questo gruppo."; @@ -2463,6 +2573,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Trova le chat più velocemente"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata"; + /* No comment provided by engineer. */ "Fix" = "Correggi"; @@ -2532,8 +2645,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "Inoltro di %lld messaggi"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Il server di inoltro %@ non è riuscito a connettersi al server di destinazione %@. Riprova più tardi."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Il server di inoltro %1$@ non è riuscito a connettersi al server di destinazione %2$@. Riprova più tardi."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "L'indirizzo del server di inoltro è incompatibile con le impostazioni di rete: %@."; @@ -2580,13 +2693,16 @@ snd error text */ /* message preview */ "Good morning!" = "Buongiorno!"; +/* shown on group welcome message */ +"group" = "gruppo"; + /* No comment provided by engineer. */ "Group" = "Gruppo"; /* No comment provided by engineer. */ "Group already exists" = "Il gruppo esiste già"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Il gruppo esiste già!"; /* No comment provided by engineer. */ @@ -2610,6 +2726,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "L'invito al gruppo non è più valido, è stato rimosso dal mittente."; +/* No comment provided by engineer. */ +"group is deleted" = "il gruppo è eliminato"; + /* No comment provided by engineer. */ "Group link" = "Link del gruppo"; @@ -2634,6 +2753,9 @@ snd error text */ /* snd group event chat item */ "group profile updated" = "profilo del gruppo aggiornato"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Il profilo del gruppo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato ai membri del gruppo."; + /* No comment provided by engineer. */ "Group welcome message" = "Messaggio di benvenuto del gruppo"; @@ -2880,7 +3002,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Nome da mostrare non valido!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Link non valido"; /* No comment provided by engineer. */ @@ -2980,24 +3102,18 @@ snd error text */ "Join" = "Entra"; /* No comment provided by engineer. */ -"join as %@" = "entra come %@"; +"Join as %@" = "entra come %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Entra nel gruppo"; /* No comment provided by engineer. */ "Join group conversations" = "Entra in conversazioni di gruppo"; -/* No comment provided by engineer. */ -"Join group?" = "Entrare nel gruppo?"; - /* No comment provided by engineer. */ "Join incognito" = "Entra in incognito"; -/* No comment provided by engineer. */ -"Join with current profile" = "Entra con il profilo attuale"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Entrare nel tuo gruppo?\nQuesto è il tuo link per il gruppo %@!"; /* No comment provided by engineer. */ @@ -3015,6 +3131,9 @@ snd error text */ /* alert title */ "Keep unused invitation?" = "Tenere l'invito inutilizzato?"; +/* No comment provided by engineer. */ +"Keep your chats clean" = "Mantieni le chat pulite"; + /* No comment provided by engineer. */ "Keep your connections" = "Mantieni le tue connessioni"; @@ -3048,6 +3167,9 @@ snd error text */ /* rcv group event chat item */ "left" = "è uscito/a"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Meno traffico sulle reti mobili."; + /* email subject */ "Let's talk in SimpleX Chat" = "Parliamo in SimpleX Chat"; @@ -3084,6 +3206,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "Messaggi in diretta"; +/* in progress text */ +"Loading profile…" = "Caricamento del profilo…"; + /* No comment provided by engineer. */ "Local name" = "Nome locale"; @@ -3135,15 +3260,27 @@ snd error text */ /* No comment provided by engineer. */ "Member" = "Membro"; +/* past/unknown group member */ +"Member %@" = "Membro %@"; + /* profile update event chat item */ "member %@ changed to %@" = "il membro %1$@ è diventato %2$@"; +/* No comment provided by engineer. */ +"Member admission" = "Ammissione dei membri"; + /* rcv group event chat item */ "member connected" = "si è connesso/a"; +/* No comment provided by engineer. */ +"member has old version" = "il membro ha una versione vecchia"; + /* item status text */ "Member inactive" = "Membro inattivo"; +/* No comment provided by engineer. */ +"Member is deleted - can't accept request" = "Il membro è eliminato - impossibile accettare la richiesta"; + /* chat feature */ "Member reports" = "Segnalazioni dei membri"; @@ -3162,6 +3299,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!"; +/* alert message */ +"Member will join the group, accept member?" = "Il membro entrerà nel gruppo, accettarlo?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; @@ -3210,6 +3350,9 @@ snd error text */ /* item status text */ "Message forwarded" = "Messaggio inoltrato"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Parla immediatamente appena tocchi Connetti."; + /* item status description */ "Message may be delivered later if member becomes active." = "Il messaggio può essere consegnato più tardi se il membro diventa attivo."; @@ -3258,6 +3401,9 @@ snd error text */ /* No comment provided by engineer. */ "Messages & files" = "Messaggi"; +/* No comment provided by engineer. */ +"Messages are protected by **end-to-end encryption**." = "I messaggi sono protetti da **crittografia end-to-end**."; + /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "I messaggi da %@ verranno mostrati!"; @@ -3423,6 +3569,9 @@ snd error text */ /* notification */ "New events" = "Nuovi eventi"; +/* No comment provided by engineer. */ +"New group role: Moderator" = "Nuovo ruolo nei gruppi: Moderatore"; + /* No comment provided by engineer. */ "New in %@" = "Novità nella %@"; @@ -3432,6 +3581,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Nuovo ruolo del membro"; +/* rcv group event chat item */ +"New member wants to join the group." = "Un nuovo membro vuole entrare nel gruppo."; + /* notification */ "new message" = "messaggio nuovo"; @@ -3471,6 +3623,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Nessuna chat nell'elenco %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Nessuna chat con membri"; + /* No comment provided by engineer. */ "No contacts selected" = "Nessun contatto selezionato"; @@ -3522,6 +3677,9 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Nessuna autorizzazione per registrare messaggi vocali"; +/* alert title */ +"No private routing session" = "Nessuna sessione di instradamento privato"; + /* No comment provided by engineer. */ "No push server" = "Locale"; @@ -3555,6 +3713,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Non compatibile!"; +/* No comment provided by engineer. */ +"not synchronized" = "non sincronizzato"; + /* No comment provided by engineer. */ "Notes" = "Note"; @@ -3587,6 +3748,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "off"; @@ -3599,7 +3761,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "offerto %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3659,6 +3823,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only you can send disappearing messages." = "Solo tu puoi inviare messaggi a tempo."; +/* No comment provided by engineer. */ +"Only you can send files and media." = "Solo tu puoi inviare file e contenuti multimediali."; + /* No comment provided by engineer. */ "Only you can send voice messages." = "Solo tu puoi inviare messaggi vocali."; @@ -3674,6 +3841,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send disappearing messages." = "Solo il tuo contatto può inviare messaggi a tempo."; +/* No comment provided by engineer. */ +"Only your contact can send files and media." = "Solo il tuo contatto può inviare file e contenuti multimediali."; + /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Solo il tuo contatto può inviare messaggi vocali."; @@ -3683,24 +3853,51 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Apri le modifiche"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Apri chat"; /* authentication reason */ "Open chat console" = "Apri la console della chat"; +/* alert action */ +"Open clean link" = "Apri link pulito"; + /* No comment provided by engineer. */ "Open conditions" = "Apri le condizioni"; -/* No comment provided by engineer. */ +/* alert action */ +"Open full link" = "Apri link completo"; + +/* new chat action */ "Open group" = "Apri gruppo"; +/* alert title */ +"Open link?" = "Aprire il link?"; + /* authentication reason */ "Open migration to another device" = "Apri migrazione ad un altro dispositivo"; +/* new chat action */ +"Open new chat" = "Apri una chat nuova"; + +/* new chat action */ +"Open new group" = "Apri un gruppo nuovo"; + /* No comment provided by engineer. */ "Open Settings" = "Apri le impostazioni"; +/* No comment provided by engineer. */ +"Open to accept" = "Apri per accettare"; + +/* No comment provided by engineer. */ +"Open to connect" = "Apri per connettere"; + +/* No comment provided by engineer. */ +"Open to join" = "Apri per entrare"; + +/* No comment provided by engineer. */ +"Open to use bot" = "Apri per usare il bot"; + /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; @@ -3770,9 +3967,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Password per mostrare"; -/* past/unknown group member */ -"Past member %@" = "Membro passato %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Incolla l'indirizzo desktop"; @@ -3797,6 +3991,9 @@ time to disappear */ /* No comment provided by engineer. */ "pending approval" = "in attesa di approvazione"; +/* No comment provided by engineer. */ +"pending review" = "in attesa di revisione"; + /* No comment provided by engineer. */ "Periodic" = "Periodicamente"; @@ -3827,7 +4024,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Controlla di aver usato il link giusto o chiedi al tuo contatto di inviartene un altro."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Controlla la tua connessione di rete con %@ e riprova."; /* No comment provided by engineer. */ @@ -3866,6 +4063,9 @@ time to disappear */ /* token info */ "Please try to disable and re-enable notfications." = "Prova a disattivare e riattivare le notifiche."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo."; + /* token info */ "Please wait for token activation to complete." = "Attendi il completamento dell'attivazione del token."; @@ -3878,9 +4078,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Porta"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva la bozza dell'ultimo messaggio, con gli allegati."; @@ -3929,9 +4126,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Instradamento privato"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Errore di instradamento privato"; +/* alert title */ +"Private routing timeout" = "Scadenza dell'instradamento privato"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profilo e connessioni al server"; @@ -3992,6 +4192,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Proteggi il tuo indirizzo IP dai relay di messaggistica scelti dai tuoi contatti.\nAttivalo nelle impostazioni *Rete e server*."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Scadenza del protocollo in sec. piano"; + /* No comment provided by engineer. */ "Protocol timeout" = "Scadenza del protocollo"; @@ -4136,16 +4339,20 @@ time to disappear */ /* token status text */ "Registered" = "Registrato"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Rifiuta"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Rifiuta contatto (mittente NON avvisato)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Rifiuta la richiesta di contatto"; +/* alert title */ +"Reject member?" = "Rifiutare il membro?"; + /* No comment provided by engineer. */ "rejected" = "rifiutato"; @@ -4167,6 +4374,9 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Rimuovi immagine"; +/* No comment provided by engineer. */ +"Remove link tracking" = "Rimuovi il tracciamento del link"; + /* No comment provided by engineer. */ "Remove member" = "Rimuovi membro"; @@ -4185,12 +4395,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "indirizzo di contatto rimosso"; +/* No comment provided by engineer. */ +"removed from group" = "rimosso dal gruppo"; + /* profile update event chat item */ "removed profile picture" = "immagine del profilo rimossa"; /* rcv group event chat item */ "removed you" = "ti ha rimosso/a"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Rimuove i messaggi e blocca i membri."; + /* No comment provided by engineer. */ "Renegotiate" = "Rinegoziare"; @@ -4200,18 +4416,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Rinegoziare la crittografia?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Ripetere la richiesta di connessione?"; - /* No comment provided by engineer. */ "Repeat download" = "Ripeti scaricamento"; /* No comment provided by engineer. */ "Repeat import" = "Ripeti importazione"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Ripetere la richiesta di ingresso?"; - /* No comment provided by engineer. */ "Repeat upload" = "Ripeti caricamento"; @@ -4233,6 +4443,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Motivo della segnalazione?"; +/* alert title */ +"Report sent to moderators" = "Segnalazione inviata ai moderatori"; + /* report reason */ "Report spam: only group moderators will see it." = "Segnala spam: solo i moderatori del gruppo lo vedranno."; @@ -4248,6 +4461,18 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Segnalazioni"; +/* No comment provided by engineer. */ +"request is sent" = "richiesta inviata"; + +/* No comment provided by engineer. */ +"request to join rejected" = "richiesta di entrare rifiutata"; + +/* rcv group event chat item */ +"requested connection" = "connessione richiesta"; + +/* rcv direct event chat item */ +"requested connection from group %@" = "connessione richiesta dal gruppo %@"; + /* chat list item title */ "requested to connect" = "richiesto di connettersi"; @@ -4296,15 +4521,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Errore di ripristino del database"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Riprova"; /* chat item action */ "Reveal" = "Rivela"; +/* No comment provided by engineer. */ +"review" = "revisiona"; + /* No comment provided by engineer. */ "Review conditions" = "Leggi le condizioni"; +/* No comment provided by engineer. */ +"Review group members" = "Revisiona i membri del gruppo"; + +/* admission stage */ +"Review members" = "Revisiona i membri"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Revisiona i membri prima di ammetterli (\"bussare\")."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "revisionato dagli amministratori"; + /* No comment provided by engineer. */ "Revoke" = "Revoca"; @@ -4333,6 +4573,12 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; +/* alert button */ +"Save (and notify members)" = "Salva (e informa i membri)"; + +/* alert title */ +"Save admission settings?" = "Salvare le impostazioni di ammissione?"; + /* alert button */ "Save and notify contact" = "Salva e avvisa il contatto"; @@ -4348,6 +4594,9 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Salva il profilo del gruppo"; +/* alert title */ +"Save group profile?" = "Salvare il profilo del gruppo?"; + /* No comment provided by engineer. */ "Save list" = "Salva elenco"; @@ -4487,10 +4736,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Invia un messaggio in diretta: si aggiornerà per i destinatari mentre lo digiti"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "Invia ricevute di consegna a"; +"Send contact request?" = "Inviare una richiesta di contatto?"; /* No comment provided by engineer. */ -"send direct message" = "invia messaggio diretto"; +"Send delivery receipts to" = "Invia ricevute di consegna a"; /* No comment provided by engineer. */ "Send direct message to connect" = "Invia messaggio diretto per connetterti"; @@ -4528,12 +4777,21 @@ chat item action */ /* No comment provided by engineer. */ "Send receipts" = "Invia ricevute"; +/* No comment provided by engineer. */ +"Send request" = "Invia richiesta"; + +/* No comment provided by engineer. */ +"Send request without message" = "Invia richiesta senza messaggio"; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Inviali dalla galleria o dalle tastiere personalizzate."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Invia fino a 100 ultimi messaggi ai nuovi membri."; +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Invia i tuoi commenti privati ai gruppi."; + /* alert message */ "Sender cancelled file transfer." = "Il mittente ha annullato il trasferimento del file."; @@ -4622,10 +4880,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "info coda server: %1$@\n\nultimo msg ricevuto: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Il server richiede l'autorizzazione di creare code, controlla la password"; +"Server requires authorization to create queues, check password." = "Il server richiede l'autorizzazione di creare code, controlla la password"; /* server test error */ -"Server requires authorization to upload, check password" = "Il server richiede l'autorizzazione per il caricamento, controllare la password"; +"Server requires authorization to upload, check password." = "Il server richiede l'autorizzazione per il caricamento, controllare la password"; /* No comment provided by engineer. */ "Server test failed!" = "Test del server fallito!"; @@ -4669,6 +4927,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Impostalo al posto dell'autenticazione di sistema."; +/* No comment provided by engineer. */ +"Set member admission" = "Imposta l'ammissione dei membri"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Imposta la scadenza dei messaggi nelle chat."; @@ -4687,6 +4948,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Imposta la password per esportare"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Imposta la bio del profilo e il messaggio di benvenuto."; + /* No comment provided by engineer. */ "Set the message shown to new members!" = "Imposta il messaggio mostrato ai nuovi membri!"; @@ -4727,6 +4991,12 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Condividi link"; +/* alert button */ +"Share old address" = "Condividi l'indirizzo vecchio"; + +/* alert button */ +"Share old link" = "Condividi il link completo"; + /* No comment provided by engineer. */ "Share profile" = "Condividi il profilo"; @@ -4742,9 +5012,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Condividi con i contatti"; +/* No comment provided by engineer. */ +"Share your address" = "Condividi il tuo indirizzo"; + +/* No comment provided by engineer. */ +"Short description" = "Descrizione breve"; + /* No comment provided by engineer. */ "Short link" = "Link breve"; +/* No comment provided by engineer. */ +"Short SimpleX address" = "Indirizzo breve di SimpleX"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Mostra → nei messaggi inviati via instradamento privato."; @@ -4787,6 +5066,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Indirizzo SimpleX o link una tantum?"; +/* alert title */ +"SimpleX address settings" = "Accetta automaticamente le impostazioni"; + /* simplex link type */ "SimpleX channel link" = "Link del canale SimpleX"; @@ -4832,6 +5114,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Protocolli di SimpleX esaminati da Trail of Bits."; +/* simplex link type */ +"SimpleX relay link" = "Link del relay SimpleX"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modalità incognito semplificata"; @@ -4980,9 +5265,21 @@ report reason */ /* No comment provided by engineer. */ "Tap button " = "Tocca il pulsante "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Tocca Connetti per chattare"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Tocca Connetti per inviare la richiesta"; + +/* No comment provided by engineer. */ +"Tap Connect to use bot" = "Tocca Connetti per usare il bot"; + /* No comment provided by engineer. */ "Tap Create SimpleX address in the menu to create it later." = "Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi."; +/* No comment provided by engineer. */ +"Tap Join group" = "Tocca Entra nel gruppo"; + /* No comment provided by engineer. */ "Tap to activate profile." = "Tocca per attivare il profilo."; @@ -5004,6 +5301,9 @@ report reason */ /* No comment provided by engineer. */ "TCP connection" = "Connessione TCP"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "Scadenza conness. TCP in sec. piano"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Scadenza connessione TCP"; @@ -5046,6 +5346,9 @@ report reason */ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Grazie agli utenti – contribuite via Weblate!"; +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "L'indirizzo sarà breve e il tuo profilo verrà condiviso attraverso l'indirizzo."; + /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare."; @@ -5085,6 +5388,9 @@ report reason */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "L'ID del messaggio successivo non è corretto (inferiore o uguale al precedente).\nPuò accadere a causa di qualche bug o quando la connessione è compromessa."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "Il link sarà breve e il profilo del gruppo verrà condiviso attraverso il link."; + /* No comment provided by engineer. */ "The message will be deleted for all members." = "Il messaggio verrà eliminato per tutti i membri."; @@ -5100,9 +5406,6 @@ report reason */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; @@ -5112,7 +5415,7 @@ report reason */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Il secondo segno di spunta che ci mancava! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Il mittente NON verrà avvisato"; /* No comment provided by engineer. */ @@ -5172,12 +5475,6 @@ report reason */ /* No comment provided by engineer. */ "This group no longer exists." = "Questo gruppo non esiste più."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "Questo è il tuo link una tantum!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Questo è il tuo indirizzo SimpleX!"; - /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Questo link richiede una versione più recente dell'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile."; @@ -5190,6 +5487,12 @@ report reason */ /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Questa impostazione si applica ai messaggi del profilo di chat attuale **%@**."; +/* No comment provided by engineer. */ +"This setting is for your current profile **%@**." = "Questa impostazione è per il tuo profilo attuale **%@**."; + +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Il tempo di scomparsa è impostato solo per i contatti nuovi."; + /* No comment provided by engineer. */ "Title" = "Titoli"; @@ -5238,9 +5541,15 @@ report reason */ /* No comment provided by engineer. */ "To send" = "Per inviare"; +/* alert message */ +"To send commands you must be connected." = "Per inviare comandi devi essere connesso/a."; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Per supportare le notifiche push istantanee, il database della chat deve essere migrato."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Per usare un altro profilo dopo il tentativo di connessione, elimina la chat e usa di nuovo il link."; + /* No comment provided by engineer. */ "To use the servers of **%@**, accept conditions of use." = "Per usare i server di **%@**, accetta le condizioni d'uso."; @@ -5403,9 +5712,27 @@ report reason */ /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "L'aggiornamento delle impostazioni riconnetterà il client a tutti i server."; +/* alert button */ +"Upgrade" = "Aggiorna"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Aggiorna l'indirizzo"; + +/* alert message */ +"Upgrade address?" = "Aggiornare l'indirizzo?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Aggiorna e apri chat"; +/* alert message */ +"Upgrade group link?" = "Aggiornare il link del gruppo?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Aggiungi link"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Aggiorna il tuo indirizzo"; + /* No comment provided by engineer. */ "Upload errors" = "Errori di invio"; @@ -5433,7 +5760,7 @@ report reason */ /* No comment provided by engineer. */ "Use chat" = "Usa la chat"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Usa il profilo attuale"; /* No comment provided by engineer. */ @@ -5449,9 +5776,12 @@ report reason */ "Use from desktop" = "Usa dal desktop"; /* No comment provided by engineer. */ -"Use iOS call interface" = "Usa interfaccia di chiamata iOS"; +"Use incognito profile" = "Usa profilo in incognito"; /* No comment provided by engineer. */ +"Use iOS call interface" = "Usa interfaccia di chiamata iOS"; + +/* new chat action */ "Use new incognito profile" = "Usa nuovo profilo in incognito"; /* No comment provided by engineer. */ @@ -5469,9 +5799,6 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Usa i server"; -/* No comment provided by engineer. */ -"Use short links (BETA)" = "Usa link brevi (BETA)"; - /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Usare i server di SimpleX Chat?"; @@ -5643,6 +5970,9 @@ report reason */ /* No comment provided by engineer. */ "Welcome message is too long" = "Il messaggio di benvenuto è troppo lungo"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Dai il benvenuto ai tuoi contatti 👋"; + /* No comment provided by engineer. */ "What's new" = "Novità"; @@ -5712,6 +6042,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Hai accettato la connessione"; +/* snd group event chat item */ +"you accepted this member" = "hai accettato questo membro"; + /* No comment provided by engineer. */ "You allow" = "Lo consenti"; @@ -5724,33 +6057,27 @@ report reason */ /* No comment provided by engineer. */ "You are already connected with %@." = "Sei già connesso/a con %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Ti stai già connettendo a %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Ti stai già connettendo tramite questo link una tantum!"; /* No comment provided by engineer. */ "You are already in group %@." = "Sei già nel gruppo %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Stai già entrando nel gruppo %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Stai già entrando nel gruppo tramite questo link!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Stai già entrando nel gruppo tramite questo link."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Stai già entrando nel gruppo!\nRipetere la richiesta di ingresso?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Sei connesso/a al server usato per ricevere messaggi da questo contatto."; -/* No comment provided by engineer. */ -"you are invited to group" = "sei stato/a invitato/a al gruppo"; - /* No comment provided by engineer. */ "You are invited to group" = "Sei stato/a invitato/a al gruppo"; @@ -5823,7 +6150,10 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "Puoi vedere di nuovo il link di invito nei dettagli di connessione."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "Puoi vedere le tue segnalazioni nella chat con gli amministratori."; + +/* alert title */ "You can't send messages!" = "Non puoi inviare messaggi!"; /* chat item text */ @@ -5844,10 +6174,7 @@ report reason */ /* No comment provided by engineer. */ "You decide who can connect." = "Sei tu a decidere chi può connettersi."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Hai già richiesto la connessione tramite questo indirizzo!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Hai già richiesto la connessione!\nRipetere la richiesta di connessione?"; /* No comment provided by engineer. */ @@ -5901,6 +6228,9 @@ report reason */ /* snd group event chat item */ "you unblocked %@" = "hai sbloccato %@"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Potrai inviare messaggi **solo dopo che la tua richiesta verrà accettata**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Verrai connesso/a al gruppo quando il dispositivo dell'host del gruppo sarà in linea, attendi o controlla più tardi!"; @@ -5916,9 +6246,6 @@ report reason */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Dovrai autenticarti quando avvii o riapri l'app dopo 30 secondi in secondo piano."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Ti connetterai a tutti i membri del gruppo."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi."; @@ -5940,6 +6267,9 @@ report reason */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Stai usando un profilo in incognito per questo gruppo: per impedire la condivisione del tuo profilo principale non è consentito invitare contatti"; +/* No comment provided by engineer. */ +"Your business contact" = "Il tuo contatto lavorativo"; + /* No comment provided by engineer. */ "Your calls" = "Le tue chiamate"; @@ -5955,8 +6285,14 @@ report reason */ /* No comment provided by engineer. */ "Your chat profiles" = "I tuoi profili di chat"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "La tua chat è stata spostata su %@ , ma si è verificato un errore imprevisto mentre venivi reindirizzato/a al profilo."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "La tua connessione è stata spostata a %@, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo."; +"Your connection was moved to %@ but an error happened when switching profile." = "La tua connessione è stata spostata a %@, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo."; + +/* No comment provided by engineer. */ +"Your contact" = "Il tuo contatto"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@)."; @@ -5976,6 +6312,9 @@ report reason */ /* No comment provided by engineer. */ "Your current profile" = "Il tuo profilo attuale"; +/* No comment provided by engineer. */ +"Your group" = "Il tuo gruppo"; + /* No comment provided by engineer. */ "Your ICE servers" = "I tuoi server ICE"; @@ -5991,15 +6330,15 @@ report reason */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo."; - /* No comment provided by engineer. */ "Your random profile" = "Il tuo profilo casuale"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index d214f88e1c..de2f23ccce 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -178,15 +178,15 @@ /* time interval */ "%d sec" = "%d 秒"; +/* delete after time */ +"%d seconds(s)" = "%d 秒"; + /* integrity error chat item */ "%d skipped message(s)" = "%d 件のスキップされたメッセージ"; /* time interval */ "%d weeks" = "%d 週"; -/* No comment provided by engineer. */ -"%lld" = ""; - /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; @@ -283,6 +283,9 @@ time interval */ time interval */ "1 week" = "1週間"; +/* delete after time */ +"1 year" = "1年"; + /* No comment provided by engineer. */ "1-time link" = "使い捨てリンク"; @@ -322,6 +325,9 @@ time interval */ /* No comment provided by engineer. */ "Abort changing address?" = "アドレス変更を中止しますか?"; +/* No comment provided by engineer. */ +"About operators" = "オペレーターについて"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "SimpleX Chat について"; @@ -330,22 +336,44 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "承諾"; +/* alert action */ +"Accept as member" = "メンバーとして承認する"; + +/* alert action */ +"Accept as observer" = "オブザーバーとして承認する"; + +/* No comment provided by engineer. */ +"Accept conditions" = "条件に同意する"; + /* No comment provided by engineer. */ "Accept connection request?" = "接続要求を承認?"; +/* alert title */ +"Accept contact request" = "連絡先リクエストを受け入れる"; + /* notification body */ "Accept contact request from %@?" = "%@ からの連絡要求を受け入れますか?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "シークレットモードで承諾"; +/* alert title */ +"Accept member" = "メンバーを承認する"; + /* call status */ "accepted call" = "受けた通話"; +/* No comment provided by engineer. */ +"Accepted conditions" = "承諾された条件"; + +/* No comment provided by engineer. */ +"Acknowledged" = "了承済み"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。"; @@ -484,10 +512,10 @@ swipe action */ /* No comment provided by engineer. */ "Already connected?" = "すでに接続済みですか?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "既に接続中です!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "すでにグループに参加しています!"; /* pref value */ @@ -647,7 +675,8 @@ swipe action */ "Can't invite contacts!" = "連絡先を招待できません!"; /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "中止"; /* feature offered item */ @@ -807,9 +836,6 @@ set passcode view */ /* server test step */ "Connect" = "接続"; -/* No comment provided by engineer. */ -"Connect incognito" = "シークレットモードで接続"; - /* No comment provided by engineer. */ "Connect to desktop" = "デスクトップに接続"; @@ -819,10 +845,10 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "友達ともっと速くつながりましょう。"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "リンク経由で接続"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "ワンタイムリンクで接続"; /* No comment provided by engineer. */ @@ -882,7 +908,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection and servers status." = "接続とサーバーのステータス。"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "接続エラー"; /* No comment provided by engineer. */ @@ -897,7 +923,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "接続停止"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "接続タイムアウト"; /* connection information */ @@ -963,9 +989,6 @@ set passcode view */ /* server test step */ "Create queue" = "キューの作成"; -/* No comment provided by engineer. */ -"Create secret group" = "シークレットグループを作成する"; - /* No comment provided by engineer. */ "Create SimpleX address" = "SimpleXアドレスの作成"; @@ -1277,7 +1300,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't enable" = "有効にしない"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "次から表示しない"; /* No comment provided by engineer. */ @@ -1454,7 +1477,7 @@ swipe action */ /* No comment provided by engineer. */ "Error changing role" = "役割変更にエラー発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "設定変更にエラー発生"; /* No comment provided by engineer. */ @@ -1475,19 +1498,19 @@ swipe action */ /* No comment provided by engineer. */ "Error decrypting file" = "ファイルの復号エラー"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "チャットデータベース削除にエラー発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "チャット削除にエラー発生!"; /* No comment provided by engineer. */ "Error deleting connection" = "接続の削除エラー"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "データベースの削除にエラー発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "古いデータベースを削除にエラー発生"; /* No comment provided by engineer. */ @@ -1502,10 +1525,10 @@ swipe action */ /* No comment provided by engineer. */ "Error encrypting database" = "データベース暗号化ににエラー発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "チャットデータベースのエキスポートにエラー発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "チャットデータベースのインポートにエラー発生"; /* No comment provided by engineer. */ @@ -1514,7 +1537,7 @@ swipe action */ /* alert title */ "Error receiving file" = "ファイル受信にエラー発生"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "メンバー除名にエラー発生"; /* No comment provided by engineer. */ @@ -1639,6 +1662,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "チャットを素早く検索"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "サーバアドレスの証明証IDが正しくないかもしれません"; + /* No comment provided by engineer. */ "Fix" = "修正"; @@ -1967,9 +1993,9 @@ snd error text */ "Join" = "参加"; /* No comment provided by engineer. */ -"join as %@" = "%@ として参加"; +"Join as %@" = "%@ として参加"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "グループに参加"; /* No comment provided by engineer. */ @@ -2295,6 +2321,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "オフ"; @@ -2307,7 +2334,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "提供された %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "OK"; /* No comment provided by engineer. */ @@ -2373,7 +2402,7 @@ time to disappear */ /* alert action */ "Open" = "開く"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "チャットを開く"; /* authentication reason */ @@ -2427,7 +2456,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "リンクが正しいかどうかご確認ください。または、連絡相手にもう一度リンクをお求めください。"; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "%@ を使用してネットワーク接続を確認し、再試行してください。"; /* No comment provided by engineer. */ @@ -2460,9 +2489,6 @@ time to disappear */ /* No comment provided by engineer. */ "Polish interface" = "ポーランド語UI"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "サーバアドレスの証明証IDが正しくないかもしれません"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "添付を含めて、下書きを保存する。"; @@ -2601,14 +2627,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "電池使用量低減"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "拒否"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "連絡を拒否(送信者には通知されません)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "連絡要求を拒否する"; /* call status */ @@ -2850,10 +2877,10 @@ chat item action */ "Sent messages will be deleted after set time." = "一定時間が経ったら送信されたメッセージが削除されます。"; /* server test error */ -"Server requires authorization to create queues, check password" = "キューを作成するにはサーバーの認証が必要です。パスワードを確認してください"; +"Server requires authorization to create queues, check password." = "キューを作成するにはサーバーの認証が必要です。パスワードを確認してください"; /* server test error */ -"Server requires authorization to upload, check password" = "アップロードにはサーバーの認証が必要です。パスワードを確認してください"; +"Server requires authorization to upload, check password." = "アップロードにはサーバーの認証が必要です。パスワードを確認してください"; /* No comment provided by engineer. */ "Server test failed!" = "サーバテスト失敗!"; @@ -3111,13 +3138,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。"; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "長らくお待たせしました! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "送信者には通知されません"; /* No comment provided by engineer. */ @@ -3270,7 +3294,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "チャット"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "現在のプロファイルを使用する"; /* No comment provided by engineer. */ @@ -3279,7 +3303,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "iOS通話インターフェースを使用する"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "新しいシークレットプロファイルを使用する"; /* No comment provided by engineer. */ @@ -3417,9 +3441,6 @@ chat item action */ /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "この連絡先から受信するメッセージのサーバに既に接続してます。"; -/* No comment provided by engineer. */ -"you are invited to group" = "グループ招待が届きました"; - /* No comment provided by engineer. */ "You are invited to group" = "グループ招待が届きました"; @@ -3465,7 +3486,7 @@ chat item action */ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "メッセージの書式にmarkdownを使用することができます:"; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "メッセージを送信できませんでした!"; /* chat item text */ @@ -3592,10 +3613,10 @@ chat item action */ "Your profile **%@** will be shared." = "あなたのプロファイル **%@** が共有されます。"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。"; +"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。"; /* No comment provided by engineer. */ "Your random profile" = "あなたのランダム・プロフィール"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 232de56641..1d5fef3fe3 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -5,7 +5,7 @@ "_italic_" = "\\_cursief_"; /* No comment provided by engineer. */ -"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- verbinding maken met [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! \n- ontvangst bevestiging(tot 20 leden). \n- sneller en stabieler."; +"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- verbinding maken met [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- ontvangst bevestiging(tot 20 leden).\n- sneller en stabieler."; /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabielere berichtbezorging.\n- een beetje betere groepen.\n- en meer!"; @@ -342,9 +342,16 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Accepteer"; +/* alert action */ +"Accept as member" = "Accepteren als lid"; + +/* alert action */ +"Accept as observer" = "Accepteren als waarnemer"; + /* No comment provided by engineer. */ "Accept conditions" = "Accepteer voorwaarden"; @@ -354,10 +361,16 @@ swipe action */ /* notification body */ "Accept contact request from %@?" = "Accepteer contactverzoek van %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Accepteer incognito"; +/* alert title */ +"Accept member" = "Lid accepteren"; + +/* rcv group event chat item */ +"accepted %@" = "geaccepteerd %@"; + /* call status */ "accepted call" = "geaccepteerde oproep"; @@ -367,6 +380,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "geaccepteerde uitnodiging"; +/* rcv group event chat item */ +"accepted you" = "heb je geaccepteerd"; + /* No comment provided by engineer. */ "Acknowledged" = "Erkend"; @@ -463,6 +479,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "versleuteling overeenkomen…"; +/* member criteria value */ +"all" = "alle"; + /* No comment provided by engineer. */ "All" = "alle"; @@ -505,6 +524,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Alle rapporten worden voor u gearchiveerd."; +/* No comment provided by engineer. */ +"All servers" = "Alle servers"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Al uw contacten blijven verbonden."; @@ -586,10 +608,10 @@ swipe action */ /* No comment provided by engineer. */ "Already connected?" = "Al verbonden?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Al bezig met verbinden!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Al lid van de groep!"; /* pref value */ @@ -733,9 +755,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Afbeeldingen automatisch accepteren"; -/* alert title */ -"Auto-accept settings" = "Instellingen automatisch accepteren"; - /* No comment provided by engineer. */ "Back" = "Terug"; @@ -902,8 +921,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Kan geen bericht sturen naar lid"; +/* No comment provided by engineer. */ +"can't send messages" = "kan geen berichten versturen"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Annuleren"; /* No comment provided by engineer. */ @@ -985,7 +1008,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "Chat bestaat al"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "Chat bestaat al!"; /* No comment provided by engineer. */ @@ -1039,9 +1062,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!"; +/* chat toolbar */ +"Chat with admins" = "Chat met beheerders"; + +/* No comment provided by engineer. */ +"Chat with member" = "Chat met lid"; + /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Chats with members" = "Chats met leden"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Controleer uw berichten elke 20 minuten."; @@ -1183,9 +1215,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connect automatically" = "Automatisch verbinden"; -/* No comment provided by engineer. */ -"Connect incognito" = "Verbind incognito"; - /* No comment provided by engineer. */ "Connect to desktop" = "Verbinden met desktop"; @@ -1195,25 +1224,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Maak sneller verbinding met je vrienden."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Verbinding maken met jezelf?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Verbinding maken met jezelf?\nDit is uw eigen eenmalige link!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Verbinding maken met jezelf?\nDit is uw eigen SimpleX adres!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Verbinding maken via contactadres"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Maak verbinding via link"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Verbinden via een eenmalige link?"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Verbonden met %@"; /* No comment provided by engineer. */ @@ -1225,9 +1251,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Verbonden desktop"; -/* rcv group event chat item */ -"connected directly" = "direct verbonden"; - /* No comment provided by engineer. */ "Connected servers" = "Verbonden servers"; @@ -1279,7 +1302,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "Verbinding geblokkeerd"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Verbindingsfout"; /* No comment provided by engineer. */ @@ -1309,7 +1332,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Verbinding beëindigd"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Timeout verbinding"; /* No comment provided by engineer. */ @@ -1330,9 +1353,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Contact bestaat al"; +/* No comment provided by engineer. */ +"contact deleted" = "contact verwijderd"; + /* No comment provided by engineer. */ "Contact deleted!" = "Contact verwijderd!"; +/* No comment provided by engineer. */ +"contact disabled" = "contact uitgeschakeld"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "contact heeft e2e-codering"; @@ -1351,6 +1380,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Contact naam"; +/* No comment provided by engineer. */ +"contact not ready" = "contact niet klaar"; + /* No comment provided by engineer. */ "Contact preferences" = "Contact voorkeuren"; @@ -1420,9 +1452,6 @@ set passcode view */ /* server test step */ "Create queue" = "Maak een wachtrij"; -/* No comment provided by engineer. */ -"Create secret group" = "Maak een geheime groep aan"; - /* No comment provided by engineer. */ "Create SimpleX address" = "Maak een SimpleX adres aan"; @@ -1599,6 +1628,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Chatprofiel verwijderen?"; +/* alert title */ +"Delete chat with member?" = "Chat met lid verwijderen?"; + /* No comment provided by engineer. */ "Delete chat?" = "Chat verwijderen?"; @@ -1869,7 +1901,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "Mis geen belangrijke berichten."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Niet meer weergeven"; /* No comment provided by engineer. */ @@ -2113,6 +2145,9 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Fout bij het accepteren van een contactverzoek"; +/* alert title */ +"Error accepting member" = "Fout bij het accepteren van lid"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Fout bij het toevoegen van leden"; @@ -2128,7 +2163,7 @@ chat item action */ /* No comment provided by engineer. */ "Error changing role" = "Fout bij wisselen van rol"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Fout bij wijzigen van instelling"; /* No comment provided by engineer. */ @@ -2137,7 +2172,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Fout bij het controleren van de tokenstatus"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw."; /* No comment provided by engineer. */ @@ -2167,19 +2202,22 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Fout bij het ontsleutelen van bestand"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Fout bij het verwijderen van chat met lid"; + +/* alert title */ "Error deleting chat database" = "Fout bij het verwijderen van de chat database"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Fout bij verwijderen gesprek!"; /* No comment provided by engineer. */ "Error deleting connection" = "Fout bij verwijderen van verbinding"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Fout bij het verwijderen van de database"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Fout bij het verwijderen van de oude database"; /* No comment provided by engineer. */ @@ -2200,13 +2238,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Fout bij het versleutelen van de database"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Fout bij het exporteren van de chat database"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Fout bij exporteren van thema: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Fout bij het importeren van de chat database"; /* No comment provided by engineer. */ @@ -2233,7 +2271,7 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Fout bij registreren voor meldingen"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "Fout bij verwijderen van lid"; /* alert title */ @@ -2287,7 +2325,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Fout bij het stoppen van de chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Fout bij wisselen van profiel"; /* alertTitle */ @@ -2460,6 +2498,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Vind chats sneller"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Mogelijk is de certificaat vingerafdruk in het server adres onjuist"; + /* No comment provided by engineer. */ "Fix" = "Herstel"; @@ -2529,8 +2570,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "%lld berichten doorsturen"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "De doorstuurserver %@ kon geen verbinding maken met de bestemmingsserver %@. Probeer het later opnieuw."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "De doorstuurserver %1$@ kon geen verbinding maken met de bestemmingsserver %2$@. Probeer het later opnieuw."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "Het adres van de doorstuurserver is niet compatibel met de netwerkinstellingen: %@."; @@ -2583,7 +2624,7 @@ snd error text */ /* No comment provided by engineer. */ "Group already exists" = "Groep bestaat al"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Groep bestaat al!"; /* No comment provided by engineer. */ @@ -2607,6 +2648,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Groep uitnodiging is niet meer geldig, deze is verwijderd door de afzender."; +/* No comment provided by engineer. */ +"group is deleted" = "groep is verwijderd"; + /* No comment provided by engineer. */ "Group link" = "Groep link"; @@ -2877,7 +2921,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Ongeldige weergavenaam!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Ongeldige link"; /* No comment provided by engineer. */ @@ -2977,24 +3021,18 @@ snd error text */ "Join" = "Word lid"; /* No comment provided by engineer. */ -"join as %@" = "deelnemen als %@"; +"Join as %@" = "deelnemen als %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Word lid van groep"; /* No comment provided by engineer. */ "Join group conversations" = "Neem deel aan groepsgesprekken"; -/* No comment provided by engineer. */ -"Join group?" = "Deelnemen aan groep?"; - /* No comment provided by engineer. */ "Join incognito" = "Doe incognito mee"; -/* No comment provided by engineer. */ -"Join with current profile" = "Word lid met huidig profiel"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Sluit u aan bij uw groep?\nDit is jouw link voor groep %@!"; /* No comment provided by engineer. */ @@ -3135,9 +3173,15 @@ snd error text */ /* profile update event chat item */ "member %@ changed to %@" = "lid %1$@ gewijzigd in %2$@"; +/* No comment provided by engineer. */ +"Member admission" = "Toelating van leden"; + /* rcv group event chat item */ "member connected" = "is toegetreden"; +/* No comment provided by engineer. */ +"member has old version" = "lid heeft oude versie"; + /* item status text */ "Member inactive" = "Lid inactief"; @@ -3159,6 +3203,9 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!"; +/* alert message */ +"Member will join the group, accept member?" = "Lid zal toetreden tot de groep, lid accepteren?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "Groepsleden kunnen bericht reacties toevoegen."; @@ -3429,6 +3476,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Nieuwe leden rol"; +/* rcv group event chat item */ +"New member wants to join the group." = "Nieuw lid wil zich bij de groep aansluiten."; + /* notification */ "new message" = "nieuw bericht"; @@ -3468,6 +3518,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Geen chats in lijst %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Geen chats met leden"; + /* No comment provided by engineer. */ "No contacts selected" = "Geen contacten geselecteerd"; @@ -3552,6 +3605,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Niet compatibel!"; +/* No comment provided by engineer. */ +"not synchronized" = "niet gesynchroniseerd"; + /* No comment provided by engineer. */ "Notes" = "Notities"; @@ -3577,13 +3633,14 @@ snd error text */ "Notifications status" = "Meldingsstatus"; /* No comment provided by engineer. */ -"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Nu kunnen beheerders: \n- berichten van leden verwijderen.\n- schakel leden uit (\"waarnemer\" rol)"; +"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Nu kunnen beheerders:\n- berichten van leden verwijderen.\n- schakel leden uit (\"waarnemer\" rol)"; /* member role */ "observer" = "Waarnemer"; /* enabled status group pref value +member criteria value time to disappear */ "off" = "uit"; @@ -3596,7 +3653,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "voorgesteld %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "OK"; /* No comment provided by engineer. */ @@ -3680,7 +3739,7 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Wijzigingen openen"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Chat openen"; /* authentication reason */ @@ -3689,9 +3748,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open conditions" = "Open voorwaarden"; -/* No comment provided by engineer. */ +/* new chat action */ "Open group" = "Open groep"; +/* alert title */ +"Open link?" = "Link openen?"; + /* authentication reason */ "Open migration to another device" = "Open de migratie naar een ander apparaat"; @@ -3767,9 +3829,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Wachtwoord om weer te geven"; -/* past/unknown group member */ -"Past member %@" = "Voormalig lid %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Desktopadres plakken"; @@ -3794,6 +3853,9 @@ time to disappear */ /* No comment provided by engineer. */ "pending approval" = "in afwachting van goedkeuring"; +/* No comment provided by engineer. */ +"pending review" = "in afwachting van beoordeling"; + /* No comment provided by engineer. */ "Periodic" = "Periodiek"; @@ -3824,7 +3886,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Controleer of u de juiste link heeft gebruikt of vraag uw contact om u een andere te sturen."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Controleer uw netwerkverbinding met %@ en probeer het opnieuw."; /* No comment provided by engineer. */ @@ -3863,6 +3925,9 @@ time to disappear */ /* token info */ "Please try to disable and re-enable notfications." = "Probeer meldingen uit en weer in te schakelen."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Wacht totdat de moderators van de groep uw verzoek tot lidmaatschap van de groep hebben beoordeeld."; + /* token info */ "Please wait for token activation to complete." = "Wacht tot de tokenactivering voltooid is."; @@ -3875,9 +3940,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Poort"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Mogelijk is de certificaat vingerafdruk in het server adres onjuist"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Bewaar het laatste berichtconcept, met bijlagen."; @@ -3926,7 +3988,7 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Privéroutering"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Fout in privéroutering"; /* No comment provided by engineer. */ @@ -4133,16 +4195,20 @@ time to disappear */ /* token status text */ "Registered" = "Geregistreerd"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Afwijzen"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Contact afwijzen (afzender NIET op de hoogte)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Contactverzoek afwijzen"; +/* alert title */ +"Reject member?" = "Lid afwijzen?"; + /* No comment provided by engineer. */ "rejected" = "afgewezen"; @@ -4182,6 +4248,9 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "contactadres verwijderd"; +/* No comment provided by engineer. */ +"removed from group" = "verwijderd uit de groep"; + /* profile update event chat item */ "removed profile picture" = "profielfoto verwijderd"; @@ -4197,18 +4266,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Heronderhandelen over versleuteling?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Verbindingsverzoek herhalen?"; - /* No comment provided by engineer. */ "Repeat download" = "Herhaal het downloaden"; /* No comment provided by engineer. */ "Repeat import" = "Herhaal import"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Deelnameverzoek herhalen?"; - /* No comment provided by engineer. */ "Repeat upload" = "Herhaal het uploaden"; @@ -4230,6 +4293,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Reden melding?"; +/* alert title */ +"Report sent to moderators" = "Rapport verzonden naar moderators"; + /* report reason */ "Report spam: only group moderators will see it." = "Spam melden: alleen groepsmoderators kunnen het zien."; @@ -4245,6 +4311,9 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Rapporten"; +/* No comment provided by engineer. */ +"request to join rejected" = "verzoek tot toetreding afgewezen"; + /* chat list item title */ "requested to connect" = "verzocht om verbinding te maken"; @@ -4293,15 +4362,27 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Database fout herstellen"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Opnieuw proberen"; /* chat item action */ "Reveal" = "Onthullen"; +/* No comment provided by engineer. */ +"review" = "beoordeling"; + /* No comment provided by engineer. */ "Review conditions" = "Voorwaarden bekijken"; +/* admission stage */ +"Review members" = "Leden beoordelen"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Controleer de leden voordat u ze toelaat ('knocking')."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "beoordeeld door beheerders"; + /* No comment provided by engineer. */ "Revoke" = "Intrekken"; @@ -4330,6 +4411,9 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Bewaar (en informeer contacten)"; +/* alert title */ +"Save admission settings?" = "Toegangsinstellingen opslaan?"; + /* alert button */ "Save and notify contact" = "Opslaan en Contact melden"; @@ -4486,9 +4570,6 @@ chat item action */ /* No comment provided by engineer. */ "Send delivery receipts to" = "Stuur ontvangstbewijzen naar"; -/* No comment provided by engineer. */ -"send direct message" = "stuur een direct bericht"; - /* No comment provided by engineer. */ "Send direct message to connect" = "Stuur een direct bericht om verbinding te maken"; @@ -4619,10 +4700,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "informatie over serverwachtrij: %1$@\n\nlaatst ontvangen bericht: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord"; +"Server requires authorization to create queues, check password." = "Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord"; /* server test error */ -"Server requires authorization to upload, check password" = "Server vereist autorisatie om te uploaden, wachtwoord controleren"; +"Server requires authorization to upload, check password." = "Server vereist autorisatie om te uploaden, wachtwoord controleren"; /* No comment provided by engineer. */ "Server test failed!" = "Servertest mislukt!"; @@ -4666,6 +4747,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Stel het in in plaats van systeemverificatie."; +/* No comment provided by engineer. */ +"Set member admission" = "Toegang voor leden instellen"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Stel de berichtvervaldatum in chats in."; @@ -4739,6 +4823,9 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Delen met contacten"; +/* No comment provided by engineer. */ +"Short link" = "Korte link"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Toon → bij berichten verzonden via privéroutering."; @@ -4781,6 +4868,12 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX adres of eenmalige link?"; +/* alert title */ +"SimpleX address settings" = "Instellingen automatisch accepteren"; + +/* simplex link type */ +"SimpleX channel link" = "SimpleX channel link"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "Simplex-chat en flux hebben een overeenkomst gemaakt om door flux geëxploiteerde servers in de app op te nemen."; @@ -5091,9 +5184,6 @@ report reason */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; @@ -5103,7 +5193,7 @@ report reason */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "De tweede vink die we gemist hebben! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "De afzender wordt NIET op de hoogte gebracht"; /* No comment provided by engineer. */ @@ -5164,10 +5254,7 @@ report reason */ "This group no longer exists." = "Deze groep bestaat niet meer."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Dit is uw eigen eenmalige link!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Dit is uw eigen SimpleX adres!"; +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Voor deze link is een nieuwere app-versie vereist. Werk de app bij of vraag je contactpersoon om een compatibele link te sturen."; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Deze link is gebruikt met een ander mobiel apparaat. Maak een nieuwe link op de desktop."; @@ -5361,6 +5448,9 @@ report reason */ /* swipe action */ "Unread" = "Ongelezen"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Niet-ondersteunde verbindingslink"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden."; @@ -5418,7 +5508,7 @@ report reason */ /* No comment provided by engineer. */ "Use chat" = "Gebruik chat"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Gebruik het huidige profiel"; /* No comment provided by engineer. */ @@ -5436,7 +5526,7 @@ report reason */ /* No comment provided by engineer. */ "Use iOS call interface" = "De iOS-oproepinterface gebruiken"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "Gebruik een nieuw incognitoprofiel"; /* No comment provided by engineer. */ @@ -5463,6 +5553,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Gebruik TCP-poort %@ als er geen poort is opgegeven."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Gebruik TCP-poort 443 alleen voor vooraf ingestelde servers."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Gebruik de app tijdens het gesprek."; @@ -5691,6 +5784,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Je hebt de verbinding geaccepteerd"; +/* snd group event chat item */ +"you accepted this member" = "je hebt dit lid geaccepteerd"; + /* No comment provided by engineer. */ "You allow" = "Jij staat toe"; @@ -5703,33 +5799,27 @@ report reason */ /* No comment provided by engineer. */ "You are already connected with %@." = "U bent al verbonden met %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "U maakt al verbinding met %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Je maakt al verbinding via deze eenmalige link!"; /* No comment provided by engineer. */ "You are already in group %@." = "Je zit al in groep %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Je bent al lid van de groep %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Je wordt al lid van de groep via deze link!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Je wordt al lid van de groep via deze link."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Je sluit je al aan bij de groep!\nDeelnameverzoek herhalen?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "U bent verbonden met de server die wordt gebruikt om berichten van dit contact te ontvangen."; -/* No comment provided by engineer. */ -"you are invited to group" = "je bent uitgenodigd voor de groep"; - /* No comment provided by engineer. */ "You are invited to group" = "Je bent uitgenodigd voor de groep"; @@ -5802,7 +5892,10 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "U kunt uw rapporten bekijken in Chat met beheerders."; + +/* alert title */ "You can't send messages!" = "Je kunt geen berichten versturen!"; /* chat item text */ @@ -5823,10 +5916,7 @@ report reason */ /* No comment provided by engineer. */ "You decide who can connect." = "Jij bepaalt wie er verbinding mag maken."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "U heeft al een verbinding aangevraagd via dit adres!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Je hebt al verbinding aangevraagd!\nVerbindingsverzoek herhalen?"; /* No comment provided by engineer. */ @@ -5895,9 +5985,6 @@ report reason */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "U moet zich authenticeren wanneer u de app na 30 seconden op de achtergrond start of hervat."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Je maakt verbinding met alle leden."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn."; @@ -5935,7 +6022,7 @@ report reason */ "Your chat profiles" = "Uw chat profielen"; /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel."; +"Your connection was moved to %@ but an error happened when switching profile." = "Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel."; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@)."; @@ -5970,15 +6057,15 @@ report reason */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Uw profiel **%@** wordt gedeeld."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen."; - /* No comment provided by engineer. */ "Your random profile" = "Je willekeurige profiel"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 31a9b87662..cf08c2b40a 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -342,6 +342,7 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Akceptuj"; @@ -354,7 +355,7 @@ swipe action */ /* notification body */ "Accept contact request from %@?" = "Zaakceptuj prośbę o kontakt od %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Akceptuj incognito"; @@ -583,10 +584,10 @@ swipe action */ /* No comment provided by engineer. */ "Already connected?" = "Już połączony?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Już połączony!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Już dołączono do grupy!"; /* pref value */ @@ -727,9 +728,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Automatyczne akceptowanie obrazów"; -/* alert title */ -"Auto-accept settings" = "Ustawienia automatycznej akceptacji"; - /* No comment provided by engineer. */ "Back" = "Wstecz"; @@ -888,7 +886,8 @@ marked deleted chat item preview text */ "Can't message member" = "Nie można wysłać wiadomości do członka"; /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Anuluj"; /* No comment provided by engineer. */ @@ -967,7 +966,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "Czat już istnieje"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "Czat już istnieje!"; /* No comment provided by engineer. */ @@ -1141,9 +1140,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connect automatically" = "Łącz automatycznie"; -/* No comment provided by engineer. */ -"Connect incognito" = "Połącz incognito"; - /* No comment provided by engineer. */ "Connect to desktop" = "Połącz do komputera"; @@ -1153,25 +1149,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Szybciej łącz się ze znajomymi."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Połączyć się ze sobą?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Połączyć się ze sobą?\nTo jest twój jednorazowy link!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Połączyć się ze sobą?\nTo jest twój własny adres SimpleX!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Połącz przez adres kontaktowy"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Połącz się przez link"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Połącz przez jednorazowy link"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Połącz z %@"; /* No comment provided by engineer. */ @@ -1183,9 +1176,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Połączony komputer"; -/* rcv group event chat item */ -"connected directly" = "połącz bezpośrednio"; - /* No comment provided by engineer. */ "Connected servers" = "Połączone serwery"; @@ -1234,7 +1224,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection and servers status." = "Stan połączenia i serwerów."; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Błąd połączenia"; /* No comment provided by engineer. */ @@ -1252,7 +1242,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Połączenie zakończone"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Czas połączenia minął"; /* No comment provided by engineer. */ @@ -1354,9 +1344,6 @@ set passcode view */ /* server test step */ "Create queue" = "Utwórz kolejkę"; -/* No comment provided by engineer. */ -"Create secret group" = "Utwórz tajną grupę"; - /* No comment provided by engineer. */ "Create SimpleX address" = "Utwórz adres SimpleX"; @@ -1761,7 +1748,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't enable" = "Nie włączaj"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Nie pokazuj ponownie"; /* No comment provided by engineer. */ @@ -2002,13 +1989,13 @@ chat item action */ /* No comment provided by engineer. */ "Error changing role" = "Błąd zmiany roli"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Błąd zmiany ustawienia"; /* No comment provided by engineer. */ "Error changing to incognito!" = "Błąd zmiany na incognito!"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później."; /* No comment provided by engineer. */ @@ -2032,19 +2019,19 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Błąd odszyfrowania pliku"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "Błąd usuwania bazy danych czatu"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Błąd usuwania czatu!"; /* No comment provided by engineer. */ "Error deleting connection" = "Błąd usuwania połączenia"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Błąd usuwania bazy danych"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Błąd usuwania starej bazy danych"; /* No comment provided by engineer. */ @@ -2065,13 +2052,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Błąd szyfrowania bazy danych"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Błąd eksportu bazy danych czatu"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Błąd eksportowania motywu: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Błąd importu bazy danych czatu"; /* No comment provided by engineer. */ @@ -2092,7 +2079,7 @@ chat item action */ /* No comment provided by engineer. */ "Error reconnecting servers" = "Błąd ponownego łączenia serwerów"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "Błąd usuwania członka"; /* No comment provided by engineer. */ @@ -2137,7 +2124,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Błąd zatrzymania czatu"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Błąd zmiany profilu"; /* alertTitle */ @@ -2286,6 +2273,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Szybciej znajduj czaty"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy"; + /* No comment provided by engineer. */ "Fix" = "Napraw"; @@ -2334,8 +2324,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "Przekazywanie %lld wiadomości"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Serwer przekazujący %1$@ nie mógł połączyć się z serwerem docelowym %2$@. Spróbuj ponownie później."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "Adres serwera przekierowującego jest niekompatybilny z ustawieniami sieciowymi: %@."; @@ -2385,7 +2375,7 @@ snd error text */ /* No comment provided by engineer. */ "Group already exists" = "Grupa już istnieje"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Grupa już istnieje!"; /* No comment provided by engineer. */ @@ -2640,7 +2630,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Nieprawidłowa nazwa wyświetlana!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Nieprawidłowy link"; /* No comment provided by engineer. */ @@ -2737,24 +2727,18 @@ snd error text */ "Join" = "Dołącz"; /* No comment provided by engineer. */ -"join as %@" = "dołącz jako %@"; +"Join as %@" = "dołącz jako %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Dołącz do grupy"; /* No comment provided by engineer. */ "Join group conversations" = "Dołącz do grupowej rozmowy"; -/* No comment provided by engineer. */ -"Join group?" = "Dołączyć do grupy?"; - /* No comment provided by engineer. */ "Join incognito" = "Dołącz incognito"; -/* No comment provided by engineer. */ -"Join with current profile" = "Dołącz z obecnym profilem"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Dołączyć do twojej grupy?\nTo jest twój link do grupy %@!"; /* No comment provided by engineer. */ @@ -3236,6 +3220,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "wyłączony"; @@ -3248,7 +3233,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "zaoferował %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3320,13 +3307,13 @@ time to disappear */ /* alert action */ "Open" = "Otwórz"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Otwórz czat"; /* authentication reason */ "Open chat console" = "Otwórz konsolę czatu"; -/* No comment provided by engineer. */ +/* new chat action */ "Open group" = "Grupa otwarta"; /* authentication reason */ @@ -3389,9 +3376,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Hasło do wyświetlenia"; -/* past/unknown group member */ -"Past member %@" = "Były członek %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Wklej adres komputera"; @@ -3440,7 +3424,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Sprawdź, czy użyłeś prawidłowego linku lub poproś Twój kontakt o przesłanie innego."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Sprawdzić połączenie sieciowe z %@ i spróbować ponownie."; /* No comment provided by engineer. */ @@ -3482,9 +3466,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Port"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami."; @@ -3518,7 +3499,7 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Prywatne trasowanie"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Błąd prywatnego trasowania"; /* No comment provided by engineer. */ @@ -3713,14 +3694,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Zmniejszone zużycie baterii"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Odrzuć"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Odrzuć kontakt (nadawca NIE został powiadomiony)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Odrzuć prośbę kontaktu"; /* call status */ @@ -3774,18 +3756,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Renegocjować szyfrowanie?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Powtórzyć prośbę połączenia?"; - /* No comment provided by engineer. */ "Repeat download" = "Powtórz pobieranie"; /* No comment provided by engineer. */ "Repeat import" = "Powtórz importowanie"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Powtórzyć prośbę dołączenia?"; - /* No comment provided by engineer. */ "Repeat upload" = "Powtórz wgrywanie"; @@ -3837,7 +3813,7 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Błąd przywracania bazy danych"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Ponów"; /* chat item action */ @@ -4024,9 +4000,6 @@ chat item action */ /* No comment provided by engineer. */ "Send delivery receipts to" = "Wyślij potwierdzenia dostawy do"; -/* No comment provided by engineer. */ -"send direct message" = "wyślij wiadomość bezpośrednią"; - /* No comment provided by engineer. */ "Send direct message to connect" = "Wyślij wiadomość bezpośrednią aby połączyć"; @@ -4142,10 +4115,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "Informacje kolejki serwera: %1$@\n\nostatnia otrzymana wiadomość: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło"; +"Server requires authorization to create queues, check password." = "Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło"; /* server test error */ -"Server requires authorization to upload, check password" = "Serwer wymaga autoryzacji do przesłania, sprawdź hasło"; +"Server requires authorization to upload, check password." = "Serwer wymaga autoryzacji do przesłania, sprawdź hasło"; /* No comment provided by engineer. */ "Server test failed!" = "Test serwera nie powiódł się!"; @@ -4283,6 +4256,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX Address" = "Adres SimpleX"; +/* alert title */ +"SimpleX address settings" = "Ustawienia automatycznej akceptacji"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Bezpieczeństwo SimpleX Chat zostało zaudytowane przez Trail of Bits."; @@ -4556,13 +4532,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom."; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Drugi tik, który przegapiliśmy! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Nadawca NIE zostanie powiadomiony"; /* No comment provided by engineer. */ @@ -4613,12 +4586,6 @@ chat item action */ /* No comment provided by engineer. */ "This group no longer exists." = "Ta grupa już nie istnieje."; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "To jest twój jednorazowy link!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "To jest twój własny adres SimpleX!"; - /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Ten link dostał użyty z innym urządzeniem mobilnym, proszę stworzyć nowy link na komputerze."; @@ -4841,7 +4808,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "Użyj czatu"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Użyj obecnego profilu"; /* No comment provided by engineer. */ @@ -4853,7 +4820,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "Użyj interfejsu połączeń iOS"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "Użyj nowego profilu incognito"; /* No comment provided by engineer. */ @@ -5099,33 +5066,27 @@ chat item action */ /* No comment provided by engineer. */ "You are already connected to %@." = "Jesteś już połączony z %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Już się łączysz z %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Już jesteś połączony z tym jednorazowym linkiem!"; /* No comment provided by engineer. */ "You are already in group %@." = "Już jesteś w grupie %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Już dołączasz do grupy %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Już dołączasz do grupy przez ten link!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Już dołączasz do grupy przez ten link."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Już dołączasz do grupy!\nPowtórzyć prośbę dołączenia?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Jesteś połączony z serwerem używanym do odbierania wiadomości od tego kontaktu."; -/* No comment provided by engineer. */ -"you are invited to group" = "jesteś zaproszony do grupy"; - /* No comment provided by engineer. */ "You are invited to group" = "Jesteś zaproszony do grupy"; @@ -5192,7 +5153,7 @@ chat item action */ /* alert message */ "You can view invitation link again in connection details." = "Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia."; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "Nie możesz wysyłać wiadomości!"; /* chat item text */ @@ -5213,10 +5174,7 @@ chat item action */ /* No comment provided by engineer. */ "You decide who can connect." = "Ty decydujesz, kto może się połączyć."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Już prosiłeś o połączenie na ten adres!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Już prosiłeś o połączenie!\nPowtórzyć prośbę połączenia?"; /* No comment provided by engineer. */ @@ -5282,9 +5240,6 @@ chat item action */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Uwierzytelnienie będzie wymagane przy uruchamianiu lub wznawianiu aplikacji po 30 sekundach w tle."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Zostaniesz połączony ze wszystkimi członkami grupy."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Nadal będziesz otrzymywać połączenia i powiadomienia z wyciszonych profili, gdy są one aktywne."; @@ -5319,7 +5274,7 @@ chat item action */ "Your chat profiles" = "Twoje profile czatu"; /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd."; +"Your connection was moved to %@ but an error happened when switching profile." = "Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd."; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@)."; @@ -5354,15 +5309,15 @@ chat item action */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Twój profil **%@** zostanie udostępniony."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu."; - /* No comment provided by engineer. */ "Your random profile" = "Twój losowy profil"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index cb837836ff..b2bdf39086 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -164,7 +164,7 @@ "%d file(s) were not downloaded." = "%d файлов не было загружено."; /* time interval */ -"%d hours" = "%d ч."; +"%d hours" = "%d час."; /* alert title */ "%d messages not forwarded" = "%d сообщений не переслано"; @@ -342,22 +342,38 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Принять"; +/* alert action */ +"Accept as member" = "Принять в группу"; + +/* alert action */ +"Accept as observer" = "Принять как читателя"; + /* No comment provided by engineer. */ "Accept conditions" = "Принять условия"; /* No comment provided by engineer. */ "Accept connection request?" = "Принять запрос?"; +/* alert title */ +"Accept contact request" = "Принять запрос на соединение"; + /* notification body */ "Accept contact request from %@?" = "Принять запрос на соединение от %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Принять инкогнито"; +/* alert title */ +"Accept member" = "Принять члена"; + +/* rcv group event chat item */ +"accepted %@" = "принят %@"; + /* call status */ "accepted call" = "принятый звонок"; @@ -367,6 +383,9 @@ swipe action */ /* chat list item title */ "accepted invitation" = "принятое приглашение"; +/* rcv group event chat item */ +"accepted you" = "Вы приняты"; + /* No comment provided by engineer. */ "Acknowledged" = "Подтверждено"; @@ -388,6 +407,9 @@ swipe action */ /* No comment provided by engineer. */ "Add list" = "Добавить список"; +/* placeholder for sending contact request */ +"Add message" = "Добавить cообщение"; + /* No comment provided by engineer. */ "Add profile" = "Добавить профиль"; @@ -463,6 +485,9 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "шифрование согласовывается…"; +/* member criteria value */ +"all" = "все"; + /* No comment provided by engineer. */ "All" = "Все"; @@ -484,6 +509,9 @@ swipe action */ /* No comment provided by engineer. */ "All group members will remain connected." = "Все члены группы останутся соединены."; +/* feature role */ +"all members" = "все члены"; + /* No comment provided by engineer. */ "All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; @@ -502,6 +530,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Все сообщения о нарушениях будут заархивированы для вас."; +/* No comment provided by engineer. */ +"All servers" = "Все серверы"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся."; @@ -526,6 +557,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Разрешить прямую доставку"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "Разрешить файлы и медиа, только если их разрешает Ваш контакт."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа)"; @@ -577,16 +611,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Разрешить Вашим контактам отправлять исчезающие сообщения."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Разрешить Вашим контактам отправлять файлы и медиа."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Разрешить Вашим контактам отправлять голосовые сообщения."; /* No comment provided by engineer. */ "Already connected?" = "Соединение уже установлено?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Уже соединяется!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Вступление в группу уже начато!"; /* pref value */ @@ -730,9 +767,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Автоприем изображений"; -/* alert title */ -"Auto-accept settings" = "Настройки автоприема"; - /* No comment provided by engineer. */ "Back" = "Назад"; @@ -784,6 +818,12 @@ swipe action */ /* No comment provided by engineer. */ "Better user experience" = "Улучшенный интерфейс"; +/* No comment provided by engineer. */ +"Bio" = "О себе"; + +/* alert title */ +"Bio too large" = "Описание слишком длинное"; + /* No comment provided by engineer. */ "Black" = "Черная"; @@ -827,6 +867,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "bold" = "жирный"; +/* No comment provided by engineer. */ +"Bot" = "Бот"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "И Вы, и Ваш контакт можете добавлять реакции на сообщения."; @@ -839,6 +882,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Вы и Ваш контакт можете отправлять исчезающие сообщения."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Вы и Ваш контакт можете отправлять файлы и медиа."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Вы и Ваш контакт можете отправлять голосовые сообщения."; @@ -851,6 +897,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Бизнес разговоры"; +/* No comment provided by engineer. */ +"Business connection" = "Бизнес контакт"; + /* No comment provided by engineer. */ "Businesses" = "Бизнесы"; @@ -890,6 +939,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Не удаётся позвонить члену группы"; +/* alert title */ +"Can't change profile" = "Нельзя поменять профиль"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Нельзя пригласить контакт!"; @@ -899,8 +951,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Не удаётся отправить сообщение члену группы"; +/* No comment provided by engineer. */ +"can't send messages" = "нельзя отправлять"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Отменить"; /* No comment provided by engineer. */ @@ -982,7 +1038,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "Разговор уже существует"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "Разговор уже существует!"; /* No comment provided by engineer. */ @@ -1036,9 +1092,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!"; +/* chat toolbar */ +"Chat with admins" = "Чат с админами"; + +/* No comment provided by engineer. */ +"Chat with member" = "Чат с членом группы"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Общайтесь с членами до того как принять их."; + /* No comment provided by engineer. */ "Chats" = "Чаты"; +/* No comment provided by engineer. */ +"Chats with members" = "Чаты с членами группы"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Проверять сообщения каждые 20 минут."; @@ -1181,7 +1249,7 @@ set passcode view */ "Connect automatically" = "Соединяться автоматически"; /* No comment provided by engineer. */ -"Connect incognito" = "Соединиться Инкогнито"; +"Connect faster! 🚀" = "Соединяйтесь быстрее! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Подключиться к компьютеру"; @@ -1192,25 +1260,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Соединяйтесь с друзьями быстрее."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Соединиться с самим собой?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Соединиться с самим собой?\nЭто ваша собственная одноразовая ссылка!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Соединиться с самим собой?\nЭто ваш собственный адрес SimpleX!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Соединиться через адрес"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Соединиться через ссылку"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Соединиться через одноразовую ссылку"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Соединиться с %@"; /* No comment provided by engineer. */ @@ -1222,9 +1287,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Подключенный компьютер"; -/* rcv group event chat item */ -"connected directly" = "соединен(а) напрямую"; - /* No comment provided by engineer. */ "Connected servers" = "Подключенные серверы"; @@ -1276,7 +1338,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "Соединение заблокировано"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "Ошибка соединения"; /* No comment provided by engineer. */ @@ -1306,7 +1368,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "Подключение прервано"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Превышено время соединения"; /* No comment provided by engineer. */ @@ -1327,9 +1389,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Существующий контакт"; +/* No comment provided by engineer. */ +"contact deleted" = "контакт удален"; + /* No comment provided by engineer. */ "Contact deleted!" = "Контакт удален!"; +/* No comment provided by engineer. */ +"contact disabled" = "контакт выключен"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "у контакта есть e2e шифрование"; @@ -1348,9 +1416,18 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Имена контактов"; +/* No comment provided by engineer. */ +"contact not ready" = "контакт не готов"; + /* No comment provided by engineer. */ "Contact preferences" = "Предпочтения контакта"; +/* No comment provided by engineer. */ +"Contact requests from groups" = "Запросы на соединение из групп"; + +/* No comment provided by engineer. */ +"contact should accept…" = "контакт должен принять…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "Контакт будет удален — это нельзя отменить!"; @@ -1418,10 +1495,10 @@ set passcode view */ "Create queue" = "Создание очереди"; /* No comment provided by engineer. */ -"Create secret group" = "Создать скрытую группу"; +"Create SimpleX address" = "Создать адрес SimpleX"; /* No comment provided by engineer. */ -"Create SimpleX address" = "Создать адрес SimpleX"; +"Create your address" = "Создайте Ваш адрес"; /* No comment provided by engineer. */ "Create your profile" = "Создать профиль"; @@ -1596,6 +1673,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat profile?" = "Удалить профиль?"; +/* alert title */ +"Delete chat with member?" = "Удалить чат с членом группы?"; + /* No comment provided by engineer. */ "Delete chat?" = "Удалить разговор?"; @@ -1722,9 +1802,15 @@ swipe action */ /* No comment provided by engineer. */ "Delivery receipts!" = "Отчёты о доставке!"; +/* No comment provided by engineer. */ +"Deprecated options" = "Удалённые настройки"; + /* No comment provided by engineer. */ "Description" = "Описание"; +/* alert title */ +"Description too large" = "Описание слишком длинное"; + /* No comment provided by engineer. */ "Desktop address" = "Адрес компьютера"; @@ -1782,6 +1868,9 @@ swipe action */ /* No comment provided by engineer. */ "Direct messages between members are prohibited in this chat." = "Личные сообщения запрещены в этой группе."; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited." = "Прямые сообщения между членами запрещены."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Выключить (кроме исключений)"; @@ -1836,6 +1925,9 @@ swipe action */ /* No comment provided by engineer. */ "Do it later" = "Отложить"; +/* No comment provided by engineer. */ +"Do not send history to new members." = "Не отправлять историю новым членам."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку."; @@ -1860,7 +1952,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "Не пропустите важные сообщения."; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "Не показывать"; /* No comment provided by engineer. */ @@ -1921,6 +2013,9 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Редактировать профиль группы"; +/* No comment provided by engineer. */ +"Empty message!" = "Пустое сообщение!"; + /* No comment provided by engineer. */ "Enable" = "Включить"; @@ -1933,6 +2028,12 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Включить доступ к камере"; +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Включите исчезающие сообщения по умолчанию."; + +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных."; + /* No comment provided by engineer. */ "Enable for all" = "Включить для всех"; @@ -2101,19 +2202,31 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Ошибка при принятии запроса на соединение"; +/* alert title */ +"Error accepting member" = "Ошибка вступления члена группы"; + +/* No comment provided by engineer. */ +"Error adding member(s)" = "Ошибка при добавлении членов группы"; + /* alert title */ "Error adding server" = "Ошибка добавления сервера"; +/* No comment provided by engineer. */ +"Error adding short link" = "Ошибка создания короткой ссылки"; + /* No comment provided by engineer. */ "Error changing address" = "Ошибка при изменении адреса"; +/* alert title */ +"Error changing chat profile" = "Ошибка изменения профиля"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Ошибка при изменении профиля соединения"; /* No comment provided by engineer. */ "Error changing role" = "Ошибка при изменении роли"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Ошибка при изменении настройки"; /* No comment provided by engineer. */ @@ -2122,7 +2235,7 @@ chat item action */ /* No comment provided by engineer. */ "Error checking token status" = "Ошибка проверки статуса токена"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Ошибка подключения к пересылающему серверу %@. Попробуйте позже."; /* No comment provided by engineer. */ @@ -2137,6 +2250,9 @@ chat item action */ /* alert title */ "Error creating list" = "Ошибка создания списка"; +/* No comment provided by engineer. */ +"Error creating member contact" = "Ошибка при создании контакта"; + /* No comment provided by engineer. */ "Error creating message" = "Ошибка создания сообщения"; @@ -2149,19 +2265,22 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "Ошибка расшифровки файла"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Ошибка при удалении чата с членом группы"; + +/* alert title */ "Error deleting chat database" = "Ошибка при удалении данных чата"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Ошибка при удалении чата!"; /* No comment provided by engineer. */ "Error deleting connection" = "Ошибка при удалении соединения"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Ошибка при удалении данных чата"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Ошибка при удалении предыдущей версии данных чата"; /* No comment provided by engineer. */ @@ -2182,13 +2301,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Ошибка при шифровании"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Ошибка при экспорте архива чата"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Ошибка экспорта темы: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Ошибка при импорте архива чата"; /* No comment provided by engineer. */ @@ -2201,7 +2320,10 @@ chat item action */ "Error migrating settings" = "Ошибка миграции настроек"; /* No comment provided by engineer. */ -"Error opening chat" = "Ошибка доступа к чату"; +"Error opening chat" = "Ошибка при открытии чата"; + +/* No comment provided by engineer. */ +"Error opening group" = "Ошибка при открытии группы"; /* alert title */ "Error receiving file" = "Ошибка при получении файла"; @@ -2215,6 +2337,12 @@ chat item action */ /* alert title */ "Error registering for notifications" = "Ошибка регистрации для уведомлений"; +/* alert title */ +"Error rejecting contact request" = "Ошибка отклонения запроса"; + +/* alert title */ +"Error removing member" = "Ошибка при удалении члена группы"; + /* alert title */ "Error reordering lists" = "Ошибка сортировки списков"; @@ -2251,9 +2379,15 @@ chat item action */ /* No comment provided by engineer. */ "Error sending email" = "Ошибка отправки email"; +/* No comment provided by engineer. */ +"Error sending member contact invitation" = "Ошибка при отправке приглашения члену"; + /* No comment provided by engineer. */ "Error sending message" = "Ошибка при отправке сообщения"; +/* No comment provided by engineer. */ +"Error setting auto-accept" = "Ошибка при установке автоприёма запросов"; + /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Ошибка настроек отчётов о доставке!"; @@ -2263,7 +2397,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Ошибка при остановке чата"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Ошибка переключения профиля"; /* alertTitle */ @@ -2304,6 +2438,9 @@ file error text snd error text */ "Error: %@" = "Ошибка: %@"; +/* server test error */ +"Error: %@." = "Ошибка: %@."; + /* No comment provided by engineer. */ "Error: no database file" = "Ошибка: данные чата не найдены"; @@ -2412,6 +2549,9 @@ snd error text */ /* chat feature */ "Files and media" = "Файлы и медиа"; +/* No comment provided by engineer. */ +"Files and media are prohibited in this chat." = "Файлы и медиа запрещены в этом чате."; + /* No comment provided by engineer. */ "Files and media are prohibited." = "Файлы и медиа запрещены в этой группе."; @@ -2436,6 +2576,18 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Быстро найти чаты"; +/* No comment provided by engineer. */ +"Fingerprint in destination server address does not match certificate: %@." = "Хэш в адресе сервера назначения не соответствует сертификату: %@."; + +/* No comment provided by engineer. */ +"Fingerprint in forwarding server address does not match certificate: %@." = "Хэш в адресе пересылающего сервера не соответствует сертификату: %@."; + +/* No comment provided by engineer. */ +"Fingerprint in server address does not match certificate: %@." = "Хэш в адресе сервера не соответствует сертификату: %@."; + +/* server test error */ +"Fingerprint in server address does not match certificate." = "Возможно, хэш сертификата в адресе сервера неверный"; + /* No comment provided by engineer. */ "Fix" = "Починить"; @@ -2451,6 +2603,9 @@ snd error text */ /* No comment provided by engineer. */ "Fix not supported by contact" = "Починка не поддерживается контактом"; +/* No comment provided by engineer. */ +"Fix not supported by group member" = "Починка не поддерживается членом группы."; + /* No comment provided by engineer. */ "For all moderators" = "Для всех модераторов"; @@ -2502,8 +2657,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "Пересылка %lld сообщений"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Пересылающий сервер %@ не смог подключиться к серверу назначения %@. Попробуйте позже."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Пересылающий сервер %1$@ не смог подключиться к серверу назначения %2$@. Попробуйте позже."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "Адрес пересылающего сервера несовместим с настройками сети: %@."; @@ -2529,6 +2684,9 @@ snd error text */ /* No comment provided by engineer. */ "Full name (optional)" = "Полное имя (не обязательно)"; +/* No comment provided by engineer. */ +"Fully decentralized – visible only to members." = "Группа полностью децентрализована – она видна только членам."; + /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Полностью обновлены - работают в фоне!"; @@ -2547,13 +2705,16 @@ snd error text */ /* message preview */ "Good morning!" = "Доброе утро!"; +/* shown on group welcome message */ +"group" = "группа"; + /* No comment provided by engineer. */ "Group" = "Группа"; /* No comment provided by engineer. */ "Group already exists" = "Группа уже существует"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Группа уже существует!"; /* No comment provided by engineer. */ @@ -2577,6 +2738,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Приглашение в группу больше не действительно, оно было удалено отправителем."; +/* No comment provided by engineer. */ +"group is deleted" = "группа удалена"; + /* No comment provided by engineer. */ "Group link" = "Ссылка группы"; @@ -2595,12 +2759,21 @@ snd error text */ /* No comment provided by engineer. */ "Group profile" = "Профиль группы"; +/* No comment provided by engineer. */ +"Group profile is stored on members' devices, not on the servers." = "Профиль группы хранится на устройствах членов, а не на серверах."; + /* snd group event chat item */ "group profile updated" = "профиль группы обновлен"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Профиль группы изменен. Если Вы сохраните его, новый профиль будет отправлен членам группы."; + /* No comment provided by engineer. */ "Group welcome message" = "Приветственное сообщение группы"; +/* No comment provided by engineer. */ +"Group will be deleted for all members - this cannot be undone!" = "Группа будет удалена для всех членов - это действие нельзя отменить!"; + /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "Группа будет удалена для Вас - это действие нельзя отменить!"; @@ -2637,6 +2810,9 @@ snd error text */ /* No comment provided by engineer. */ "History" = "История"; +/* No comment provided by engineer. */ +"History is not sent to new members." = "История не отправляется новым членам."; + /* time unit */ "hours" = "часов"; @@ -2838,7 +3014,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Ошибка имени!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Ошибка ссылки"; /* No comment provided by engineer. */ @@ -2871,6 +3047,9 @@ snd error text */ /* No comment provided by engineer. */ "Invite friends" = "Пригласить друзей"; +/* No comment provided by engineer. */ +"Invite members" = "Пригласить членов группы"; + /* No comment provided by engineer. */ "Invite to chat" = "Пригласить в разговор"; @@ -2935,24 +3114,18 @@ snd error text */ "Join" = "Вступить"; /* No comment provided by engineer. */ -"join as %@" = "вступить как %@"; +"Join as %@" = "вступить как %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Вступить в группу"; /* No comment provided by engineer. */ "Join group conversations" = "Присоединяйтесь к разговорам в группах"; -/* No comment provided by engineer. */ -"Join group?" = "Вступить в группу?"; - /* No comment provided by engineer. */ "Join incognito" = "Вступить инкогнито"; -/* No comment provided by engineer. */ -"Join with current profile" = "Вступить с активным профилем"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Вступить в вашу группу?\nЭто ваша ссылка на группу %@!"; /* No comment provided by engineer. */ @@ -2970,6 +3143,9 @@ snd error text */ /* alert title */ "Keep unused invitation?" = "Оставить неиспользованное приглашение?"; +/* No comment provided by engineer. */ +"Keep your chats clean" = "Очищайте Ваши чаты"; + /* No comment provided by engineer. */ "Keep your connections" = "Сохраните Ваши соединения"; @@ -3003,6 +3179,9 @@ snd error text */ /* rcv group event chat item */ "left" = "покинул(а) группу"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Меньше трафик в мобильных сетях."; + /* email subject */ "Let's talk in SimpleX Chat" = "Давайте поговорим в SimpleX Chat"; @@ -3039,6 +3218,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "\"Живые\" сообщения"; +/* in progress text */ +"Loading profile…" = "Загрузка профиля…"; + /* No comment provided by engineer. */ "Local name" = "Локальное имя"; @@ -3084,15 +3266,81 @@ snd error text */ /* blur media */ "Medium" = "Среднее"; +/* member role */ +"member" = "член группы"; + +/* No comment provided by engineer. */ +"Member" = "Член группы"; + +/* past/unknown group member */ +"Member %@" = "Член группы %@"; + +/* profile update event chat item */ +"member %@ changed to %@" = "член %1$@ изменился на %2$@"; + +/* No comment provided by engineer. */ +"Member admission" = "Приём членов в группу"; + /* rcv group event chat item */ "member connected" = "соединен(а)"; +/* No comment provided by engineer. */ +"member has old version" = "член имеет старую версию"; + +/* item status text */ +"Member inactive" = "Член неактивен"; + +/* No comment provided by engineer. */ +"Member is deleted - can't accept request" = "Член группы удалён - невозможно принять запрос"; + /* chat feature */ "Member reports" = "Сообщения о нарушениях"; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление."; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All group members will be notified." = "Роль члена будет изменена на \"%@\". Все члены группы получат уведомление."; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение."; + +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!"; + +/* No comment provided by engineer. */ +"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!"; + +/* alert message */ +"Member will join the group, accept member?" = "Участник хочет присоединиться к группе. Принять?"; + +/* No comment provided by engineer. */ +"Members can add message reactions." = "Члены могут добавлять реакции на сообщения."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Члены могут необратимо удалять отправленные сообщения. (24 часа)"; + +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам."; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Члены могут посылать прямые сообщения."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Члены могут посылать исчезающие сообщения."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Члены могут слать файлы и медиа."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Члены могут отправлять ссылки SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Члены могут отправлять голосовые сообщения."; + +/* No comment provided by engineer. */ +"Mention members 👋" = "Упоминайте участников 👋"; + /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -3114,6 +3362,12 @@ snd error text */ /* item status text */ "Message forwarded" = "Сообщение переслано"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Отправляйте сообщения сразу после соединения."; + +/* item status description */ +"Message may be delivered later if member becomes active." = "Сообщение может быть доставлено позже, если член группы станет активным."; + /* No comment provided by engineer. */ "Message queue info" = "Информация об очереди сообщений"; @@ -3159,6 +3413,9 @@ snd error text */ /* No comment provided by engineer. */ "Messages & files" = "Сообщения"; +/* No comment provided by engineer. */ +"Messages are protected by **end-to-end encryption**." = "Сообщения защищены **end-to-end шифрованием**."; + /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Сообщения от %@ будут показаны!"; @@ -3324,12 +3581,21 @@ snd error text */ /* notification */ "New events" = "Новые события"; +/* No comment provided by engineer. */ +"New group role: Moderator" = "Новая роль в группах: Модератор"; + /* No comment provided by engineer. */ "New in %@" = "Новое в %@"; /* No comment provided by engineer. */ "New media options" = "Новые медиа-опции"; +/* No comment provided by engineer. */ +"New member role" = "Роль члена группы"; + +/* rcv group event chat item */ +"New member wants to join the group." = "Новый участник хочет присоединиться к группе."; + /* notification */ "new message" = "новое сообщение"; @@ -3369,6 +3635,9 @@ snd error text */ /* No comment provided by engineer. */ "No chats in list %@" = "Нет чатов в списке %@"; +/* No comment provided by engineer. */ +"No chats with members" = "Нет чатов с членами группы"; + /* No comment provided by engineer. */ "No contacts selected" = "Контакты не выбраны"; @@ -3420,6 +3689,9 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Нет разрешения для записи голосового сообщения"; +/* alert title */ +"No private routing session" = "Нет сессии конфиденциальной доставки"; + /* No comment provided by engineer. */ "No push server" = "Без сервера нотификаций"; @@ -3453,6 +3725,9 @@ snd error text */ /* No comment provided by engineer. */ "Not compatible!" = "Несовместимая версия!"; +/* No comment provided by engineer. */ +"not synchronized" = "не синхронизирован"; + /* No comment provided by engineer. */ "Notes" = "Заметки"; @@ -3477,11 +3752,15 @@ snd error text */ /* alert title */ "Notifications status" = "Статус уведомлений"; +/* No comment provided by engineer. */ +"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль наблюдатель)"; + /* member role */ "observer" = "читатель"; /* enabled status group pref value +member criteria value time to disappear */ "off" = "нет"; @@ -3494,7 +3773,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "предложил(a) %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Ок"; /* No comment provided by engineer. */ @@ -3554,6 +3835,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only you can send disappearing messages." = "Только Вы можете отправлять исчезающие сообщения."; +/* No comment provided by engineer. */ +"Only you can send files and media." = "Только Вы можете отправлять файлы и медиа."; + /* No comment provided by engineer. */ "Only you can send voice messages." = "Только Вы можете отправлять голосовые сообщения."; @@ -3569,6 +3853,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send disappearing messages." = "Только Ваш контакт может отправлять исчезающие сообщения."; +/* No comment provided by engineer. */ +"Only your contact can send files and media." = "Только Ваш контакт может отправлять файлы и медиа."; + /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Только Ваш контакт может отправлять голосовые сообщения."; @@ -3578,24 +3865,51 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Открыть изменения"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Открыть чат"; /* authentication reason */ "Open chat console" = "Открыть консоль"; +/* alert action */ +"Open clean link" = "Открыть очищенную ссылку"; + /* No comment provided by engineer. */ "Open conditions" = "Открыть условия"; -/* No comment provided by engineer. */ +/* alert action */ +"Open full link" = "Открыть полную ссылку"; + +/* new chat action */ "Open group" = "Открыть группу"; +/* alert title */ +"Open link?" = "Открыть ссылку?"; + /* authentication reason */ "Open migration to another device" = "Открытие миграции на другое устройство"; +/* new chat action */ +"Open new chat" = "Открыть новый чат"; + +/* new chat action */ +"Open new group" = "Открыть новую группу"; + /* No comment provided by engineer. */ "Open Settings" = "Открыть Настройки"; +/* No comment provided by engineer. */ +"Open to accept" = "Откройте чтобы принять"; + +/* No comment provided by engineer. */ +"Open to connect" = "Откройте чтобы соединиться"; + +/* No comment provided by engineer. */ +"Open to join" = "Откройте чтобы вступить"; + +/* No comment provided by engineer. */ +"Open to use bot" = "Откройте чтобы использовать бот"; + /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; @@ -3689,6 +4003,9 @@ time to disappear */ /* No comment provided by engineer. */ "pending approval" = "ожидает утверждения"; +/* No comment provided by engineer. */ +"pending review" = "ожидает одобрения"; + /* No comment provided by engineer. */ "Periodic" = "Периодически"; @@ -3719,7 +4036,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Пожалуйста, проверьте, что Вы использовали правильную ссылку или попросите, чтобы Ваш контакт отправил Вам другую ссылку."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Пожалуйста, проверьте Ваше соединение с %@ и попробуйте еще раз."; /* No comment provided by engineer. */ @@ -3758,6 +4075,9 @@ time to disappear */ /* token info */ "Please try to disable and re-enable notfications." = "Попробуйте выключить и снова включить уведомления."; +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление."; + /* token info */ "Please wait for token activation to complete." = "Пожалуйста, дождитесь завершения активации токена."; @@ -3770,9 +4090,6 @@ time to disappear */ /* No comment provided by engineer. */ "Port" = "Порт"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Возможно, хэш сертификата в адресе сервера неверный"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Сохранить последний черновик, вместе с вложениями."; @@ -3821,9 +4138,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Конфиденциальная доставка"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Ошибка конфиденциальной доставки"; +/* alert title */ +"Private routing timeout" = "Таймаут конфиденциальной доставки"; + /* No comment provided by engineer. */ "Profile and server connections" = "Профиль и соединения на сервере"; @@ -3857,6 +4177,9 @@ time to disappear */ /* No comment provided by engineer. */ "Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Запретить посылать исчезающие сообщения."; @@ -3881,6 +4204,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сети и серверов*."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Фоновый таймаут протокола"; + /* No comment provided by engineer. */ "Protocol timeout" = "Таймаут протокола"; @@ -4025,16 +4351,20 @@ time to disappear */ /* token status text */ "Registered" = "Зарегистрирован"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Отклонить"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Отклонить (не уведомляя отправителя)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Отклонить запрос"; +/* alert title */ +"Reject member?" = "Отклонить участника?"; + /* No comment provided by engineer. */ "rejected" = "отклонён"; @@ -4056,6 +4386,15 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Удалить изображение"; +/* No comment provided by engineer. */ +"Remove link tracking" = "Удалять параметры отслеживания"; + +/* No comment provided by engineer. */ +"Remove member" = "Удалить члена группы"; + +/* No comment provided by engineer. */ +"Remove member?" = "Удалить члена группы?"; + /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Удалить пароль из Keychain?"; @@ -4068,12 +4407,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "удалён адрес контакта"; +/* No comment provided by engineer. */ +"removed from group" = "удален из группы"; + /* profile update event chat item */ "removed profile picture" = "удалена картинка профиля"; /* rcv group event chat item */ "removed you" = "удалил(а) Вас из группы"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Может удалять сообщения и блокировать членов."; + /* No comment provided by engineer. */ "Renegotiate" = "Пересогласовать"; @@ -4083,18 +4428,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Пересогласовать шифрование?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Повторить запрос на соединение?"; - /* No comment provided by engineer. */ "Repeat download" = "Повторить загрузку"; /* No comment provided by engineer. */ "Repeat import" = "Повторить импорт"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Повторить запрос на вступление?"; - /* No comment provided by engineer. */ "Repeat upload" = "Повторить загрузку"; @@ -4116,6 +4455,9 @@ swipe action */ /* No comment provided by engineer. */ "Report reason?" = "Причина сообщения?"; +/* alert title */ +"Report sent to moderators" = "Жалоба отправлена модераторам"; + /* report reason */ "Report spam: only group moderators will see it." = "Пожаловаться на спам: увидят только модераторы группы."; @@ -4131,6 +4473,18 @@ swipe action */ /* No comment provided by engineer. */ "Reports" = "Сообщения о нарушениях"; +/* No comment provided by engineer. */ +"request is sent" = "запрос отправлен"; + +/* No comment provided by engineer. */ +"request to join rejected" = "запрос на вступление отклонён"; + +/* rcv group event chat item */ +"requested connection" = "запрос на соединение"; + +/* rcv direct event chat item */ +"requested connection from group %@" = "запрос на соединение из группы %@"; + /* chat list item title */ "requested to connect" = "запрошено соединение"; @@ -4179,15 +4533,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Ошибка при восстановлении базы данных"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Повторить"; /* chat item action */ "Reveal" = "Показать"; +/* No comment provided by engineer. */ +"review" = "рассмотрение"; + /* No comment provided by engineer. */ "Review conditions" = "Посмотреть условия"; +/* No comment provided by engineer. */ +"Review group members" = "Одобрять членов группы"; + +/* admission stage */ +"Review members" = "Одобрять членов"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Одобрять членов для вступления в группу."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "одобрен админами"; + /* No comment provided by engineer. */ "Revoke" = "Отозвать"; @@ -4216,9 +4585,18 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Сохранить (и уведомить контакты)"; +/* alert button */ +"Save (and notify members)" = "Сохранить (и уведомить членов)"; + +/* alert title */ +"Save admission settings?" = "Сохранить настройки вступления?"; + /* alert button */ "Save and notify contact" = "Сохранить и уведомить контакт"; +/* No comment provided by engineer. */ +"Save and notify group members" = "Сохранить и уведомить членов группы"; + /* No comment provided by engineer. */ "Save and reconnect" = "Сохранить и переподключиться"; @@ -4228,6 +4606,9 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Сохранить профиль группы"; +/* alert title */ +"Save group profile?" = "Сохранить профиль группы?"; + /* No comment provided by engineer. */ "Save list" = "Сохранить список"; @@ -4367,10 +4748,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Отправить живое сообщение — оно будет обновляться для получателей по мере того, как Вы его вводите"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "Отправка отчётов о доставке"; +"Send contact request?" = "Отправить запрос на соединение?"; /* No comment provided by engineer. */ -"send direct message" = "отправьте сообщение"; +"Send delivery receipts to" = "Отправка отчётов о доставке"; /* No comment provided by engineer. */ "Send direct message to connect" = "Отправьте сообщение чтобы соединиться"; @@ -4406,11 +4787,23 @@ chat item action */ "Send questions and ideas" = "Отправьте вопросы и идеи"; /* No comment provided by engineer. */ -"Send receipts" = "Отправлять отчёты о доставке"; +"Send receipts" = "Отчёты о доставке"; + +/* No comment provided by engineer. */ +"Send request" = "Отправить запрос"; + +/* No comment provided by engineer. */ +"Send request without message" = "Отправить запрос без сообщения"; /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам."; + +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Отправляйте Ваши конфиденциальные предложения группе."; + /* alert message */ "Sender cancelled file transfer." = "Отправитель отменил передачу файла."; @@ -4499,10 +4892,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "информация сервера об очереди: %1$@\n\nпоследнее полученное сообщение: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Сервер требует авторизации для создания очередей, проверьте пароль"; +"Server requires authorization to create queues, check password." = "Сервер требует авторизации для создания очередей, проверьте пароль"; /* server test error */ -"Server requires authorization to upload, check password" = "Сервер требует авторизации для загрузки, проверьте пароль"; +"Server requires authorization to upload, check password." = "Сервер требует авторизации для загрузки, проверьте пароль"; /* No comment provided by engineer. */ "Server test failed!" = "Ошибка теста сервера!"; @@ -4546,6 +4939,9 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Установите код вместо системной аутентификации."; +/* No comment provided by engineer. */ +"Set member admission" = "Приём членов в группу"; + /* No comment provided by engineer. */ "Set message expiration in chats." = "Установите срок хранения сообщений в чатах."; @@ -4564,6 +4960,12 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Установите пароль"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Добавьте описание и приветственное сообщение."; + +/* No comment provided by engineer. */ +"Set the message shown to new members!" = "Установить сообщение для новых членов группы!"; + /* No comment provided by engineer. */ "Set timeouts for proxy/VPN" = "Установить таймауты для прокси/VPN"; @@ -4601,6 +5003,12 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Поделиться ссылкой"; +/* alert button */ +"Share old address" = "Поделиться старым адресом"; + +/* alert button */ +"Share old link" = "Поделиться старой ссылкой"; + /* No comment provided by engineer. */ "Share profile" = "Поделиться профилем"; @@ -4616,6 +5024,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Поделиться с контактами"; +/* No comment provided by engineer. */ +"Share your address" = "Поделитесь Вашим адресом"; + +/* No comment provided by engineer. */ +"Short description" = "Цель"; + +/* No comment provided by engineer. */ +"Short link" = "Короткая ссылка"; + +/* No comment provided by engineer. */ +"Short SimpleX address" = "Короткий адрес SimpleX"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Показать → на сообщениях доставленных конфиденциально."; @@ -4658,6 +5078,12 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?"; +/* alert title */ +"SimpleX address settings" = "Настройки автоприема"; + +/* simplex link type */ +"SimpleX channel link" = "SimpleX ссылка канала"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение."; @@ -4700,6 +5126,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Аудит SimpleX протоколов от Trail of Bits."; +/* simplex link type */ +"SimpleX relay link" = "Ссылка SimpleX relay"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Упрощенный режим Инкогнито"; @@ -4848,9 +5277,21 @@ report reason */ /* No comment provided by engineer. */ "Tap button " = "Нажмите кнопку "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Нажмите Соединиться"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Нажмите Соединиться, чтобы отправить запрос"; + +/* No comment provided by engineer. */ +"Tap Connect to use bot" = "Нажмите Соединиться, чтобы использовать бот."; + /* No comment provided by engineer. */ "Tap Create SimpleX address in the menu to create it later." = "Нажмите Создать адрес SimpleX в меню, чтобы создать его позже."; +/* No comment provided by engineer. */ +"Tap Join group" = "Нажмите Вступить в группу"; + /* No comment provided by engineer. */ "Tap to activate profile." = "Нажмите, чтобы сделать профиль активным."; @@ -4872,6 +5313,9 @@ report reason */ /* No comment provided by engineer. */ "TCP connection" = "TCP-соединение"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "Фоновый таймаут TCP-соединения"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Таймаут TCP соединения"; @@ -4914,6 +5358,9 @@ report reason */ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Благодаря пользователям – добавьте переводы через Weblate!"; +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "Адрес будет коротким, и Ваш профиль будет добавлен в адрес."; + /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; @@ -4953,11 +5400,23 @@ report reason */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Неправильный ID предыдущего сообщения (меньше или равен предыдущему).\nЭто может произойти из-за ошибки программы, или когда соединение компроментировано."; -/* No comment provided by engineer. */ -"The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "Ссылка будет короткой, и профиль группы будет добавлен в ссылку."; /* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам."; +"The message will be deleted for all members." = "Сообщение будет удалено для всех членов группы."; + +/* No comment provided by engineer. */ +"The message will be marked as moderated for all members." = "Сообщение будет помечено как удаленное для всех членов группы."; + +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "Сообщения будут удалены для всех членов группы."; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы."; + +/* No comment provided by engineer. */ +"The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; @@ -4968,7 +5427,7 @@ report reason */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Вторая галочка - знать, что доставлено! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Отправитель не будет уведомлён"; /* No comment provided by engineer. */ @@ -5022,14 +5481,14 @@ report reason */ /* No comment provided by engineer. */ "This display name is invalid. Please choose another name." = "Ошибка имени профиля. Пожалуйста, выберите другое имя."; +/* No comment provided by engineer. */ +"This group has over %lld members, delivery receipts are not sent." = "В этой группе более %lld членов, отчёты о доставке не отправляются."; + /* No comment provided by engineer. */ "This group no longer exists." = "Эта группа больше не существует."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Это ваша собственная одноразовая ссылка!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Это ваш собственный адрес SimpleX!"; +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку."; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Эта ссылка была использована на другом мобильном, пожалуйста, создайте новую ссылку на компьютере."; @@ -5040,6 +5499,12 @@ report reason */ /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Эта настройка применяется к сообщениям в Вашем текущем профиле чата **%@**."; +/* No comment provided by engineer. */ +"This setting is for your current profile **%@**." = "Эта настройка применяется к Вашему текущему профилю чата **%@**."; + +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Время удаления устанавливается только для новых контактов."; + /* No comment provided by engineer. */ "Title" = "Заголовок"; @@ -5088,9 +5553,15 @@ report reason */ /* No comment provided by engineer. */ "To send" = "Для оправки"; +/* alert message */ +"To send commands you must be connected." = "Вы должны быть соединены, чтобы отправлять команды."; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Для поддержки мгновенный доставки уведомлений данные чата должны быть перемещены."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Чтобы использовать другой профиль после попытки соединения, удалите чат и используйте ссылку снова."; + /* No comment provided by engineer. */ "To use the servers of **%@**, accept conditions of use." = "Чтобы использовать серверы оператора **%@**, примите условия использования."; @@ -5142,6 +5613,15 @@ report reason */ /* No comment provided by engineer. */ "Unblock for all" = "Разблокировать для всех"; +/* No comment provided by engineer. */ +"Unblock member" = "Разблокировать члена группы"; + +/* No comment provided by engineer. */ +"Unblock member for all?" = "Разблокировать члена для всех?"; + +/* No comment provided by engineer. */ +"Unblock member?" = "Разблокировать члена группы?"; + /* rcv group event chat item */ "unblocked %@" = "%@ разблокирован"; @@ -5214,6 +5694,12 @@ report reason */ /* swipe action */ "Unread" = "Не прочитано"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Ссылка не поддерживается"; + +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new members." = "До 100 последних сообщений отправляются новым членам."; + /* No comment provided by engineer. */ "Update" = "Обновить"; @@ -5238,9 +5724,27 @@ report reason */ /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Обновление настроек приведет к сбросу и установке нового соединения со всеми серверами."; +/* alert button */ +"Upgrade" = "Обновить"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Обновить адрес"; + +/* alert message */ +"Upgrade address?" = "Обновить адрес?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Обновить и открыть чат"; +/* alert message */ +"Upgrade group link?" = "Обновить ссылку группы?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Обновить ссылку"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Обновите Ваш адрес"; + /* No comment provided by engineer. */ "Upload errors" = "Ошибки загрузки"; @@ -5268,7 +5772,7 @@ report reason */ /* No comment provided by engineer. */ "Use chat" = "Использовать чат"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Использовать активный профиль"; /* No comment provided by engineer. */ @@ -5284,9 +5788,12 @@ report reason */ "Use from desktop" = "Использовать с компьютера"; /* No comment provided by engineer. */ -"Use iOS call interface" = "Использовать интерфейс iOS для звонков"; +"Use incognito profile" = "Использовать профиль инкогнито"; /* No comment provided by engineer. */ +"Use iOS call interface" = "Использовать интерфейс iOS для звонков"; + +/* new chat action */ "Use new incognito profile" = "Использовать новый Инкогнито профиль"; /* No comment provided by engineer. */ @@ -5313,6 +5820,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда порт не указан."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Использовать TCP-порт 443 только для серверов по умолчанию."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Используйте приложение во время звонка."; @@ -5472,6 +5982,9 @@ report reason */ /* No comment provided by engineer. */ "Welcome message is too long" = "Приветственное сообщение слишком длинное"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Приветствуйте Ваши контакты 👋"; + /* No comment provided by engineer. */ "What's new" = "Новые функции"; @@ -5541,6 +6054,9 @@ report reason */ /* No comment provided by engineer. */ "You accepted connection" = "Вы приняли приглашение соединиться"; +/* snd group event chat item */ +"you accepted this member" = "Вы приняли этого члена"; + /* No comment provided by engineer. */ "You allow" = "Вы разрешаете"; @@ -5553,33 +6069,27 @@ report reason */ /* No comment provided by engineer. */ "You are already connected with %@." = "Вы уже соединены с %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Вы уже соединяетесь с %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Вы уже соединяетесь по этой одноразовой ссылке!"; /* No comment provided by engineer. */ "You are already in group %@." = "Вы уже состоите в группе %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Вы уже вступаете в группу %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Вы уже вступаете в группу по этой ссылке!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Вы уже вступаете в группу по этой ссылке."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Вы уже вступаете в группу!\nПовторить запрос на вступление?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Установлено соединение с сервером, через который Вы получаете сообщения от этого контакта."; -/* No comment provided by engineer. */ -"you are invited to group" = "Вы приглашены в группу"; - /* No comment provided by engineer. */ "You are invited to group" = "Вы приглашены в группу"; @@ -5631,6 +6141,9 @@ report reason */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились."; + /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**."; @@ -5649,7 +6162,10 @@ report reason */ /* alert message */ "You can view invitation link again in connection details." = "Вы можете увидеть ссылку-приглашение снова открыв соединение."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "Вы можете найти Ваши жалобы в Чате с админами."; + +/* alert title */ "You can't send messages!" = "Вы не можете отправлять сообщения!"; /* chat item text */ @@ -5670,10 +6186,7 @@ report reason */ /* No comment provided by engineer. */ "You decide who can connect." = "Вы определяете, кто может соединиться."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Вы уже запросили соединение через этот адрес!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Вы уже запросили соединение!\nПовторить запрос?"; /* No comment provided by engineer. */ @@ -5685,6 +6198,9 @@ report reason */ /* No comment provided by engineer. */ "You joined this group" = "Вы вступили в эту группу"; +/* No comment provided by engineer. */ +"You joined this group. Connecting to inviting group member." = "Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы."; + /* snd group event chat item */ "you left" = "Вы покинули группу"; @@ -5724,6 +6240,9 @@ report reason */ /* snd group event chat item */ "you unblocked %@" = "Вы разблокировали %@"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Вы сможете отправлять сообщения **только после того как Ваш запрос будет принят**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже!"; @@ -5760,6 +6279,9 @@ report reason */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие Вашего основного профиля, приглашать контакты не разрешено"; +/* No comment provided by engineer. */ +"Your business contact" = "Ваш бизнес контакт"; + /* No comment provided by engineer. */ "Your calls" = "Ваши звонки"; @@ -5775,8 +6297,14 @@ report reason */ /* No comment provided by engineer. */ "Your chat profiles" = "Ваши профили чата"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Соединение было перемещено в профиль %@, но при переключении профиля произошла ошибка."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Соединение было перемещено на %@, но при смене профиля произошла неожиданная ошибка."; +"Your connection was moved to %@ but an error happened when switching profile." = "Соединение было перемещено на %@, но при смене профиля произошла неожиданная ошибка."; + +/* No comment provided by engineer. */ +"Your contact" = "Ваш контакт"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ваш контакт отправил файл, размер которого превышает максимальный размер (%@)."; @@ -5796,6 +6324,9 @@ report reason */ /* No comment provided by engineer. */ "Your current profile" = "Ваш активный профиль"; +/* No comment provided by engineer. */ +"Your group" = "Ваша группа"; + /* No comment provided by engineer. */ "Your ICE servers" = "Ваши ICE серверы"; @@ -5811,15 +6342,15 @@ report reason */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Будет отправлен Ваш профиль **%@**."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль хранится на Вашем устройстве и отправляется только контактам."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве."; - /* No comment provided by engineer. */ "Your random profile" = "Случайный профиль"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 57c0466eb9..923c1960c7 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -219,13 +219,14 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "รับ"; /* notification body */ "Accept contact request from %@?" = "รับการขอติดต่อจาก %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "ยอมรับโหมดไม่ระบุตัวตน"; @@ -485,7 +486,8 @@ swipe action */ "Can't invite contacts!" = "ไม่สามารถเชิญผู้ติดต่อได้!"; /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "ยกเลิก"; /* feature offered item */ @@ -630,7 +632,7 @@ set passcode view */ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "เชื่อมต่อกับนักพัฒนา SimpleX Chat"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "เชื่อมต่อผ่านลิงก์"; /* No comment provided by engineer. */ @@ -666,7 +668,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection" = "การเชื่อมต่อ"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "การเชื่อมต่อผิดพลาด"; /* No comment provided by engineer. */ @@ -678,7 +680,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection request sent!" = "ส่งคําขอเชื่อมต่อแล้ว!"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "หมดเวลาการเชื่อมต่อ"; /* connection information */ @@ -738,9 +740,6 @@ set passcode view */ /* server test step */ "Create queue" = "สร้างคิว"; -/* No comment provided by engineer. */ -"Create secret group" = "สร้างกลุ่มลับ"; - /* No comment provided by engineer. */ "Create SimpleX address" = "สร้างที่อยู่ SimpleX"; @@ -1028,7 +1027,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't enable" = "อย่าเปิดใช้งาน"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "ไม่ต้องแสดงอีก"; /* No comment provided by engineer. */ @@ -1199,7 +1198,7 @@ swipe action */ /* No comment provided by engineer. */ "Error changing role" = "เกิดข้อผิดพลาดในการเปลี่ยนบทบาท"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "เกิดข้อผิดพลาดในการเปลี่ยนการตั้งค่า"; /* No comment provided by engineer. */ @@ -1214,19 +1213,19 @@ swipe action */ /* No comment provided by engineer. */ "Error creating profile!" = "เกิดข้อผิดพลาดในการสร้างโปรไฟล์!"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "เกิดข้อผิดพลาดในการลบฐานข้อมูลแชท"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "เกิดข้อผิดพลาดในการลบแชท!"; /* No comment provided by engineer. */ "Error deleting connection" = "เกิดข้อผิดพลาดในการลบการเชื่อมต่อ"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "เกิดข้อผิดพลาดในการลบฐานข้อมูล"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "เกิดข้อผิดพลาดในการลบฐานข้อมูลเก่า"; /* No comment provided by engineer. */ @@ -1244,10 +1243,10 @@ swipe action */ /* No comment provided by engineer. */ "Error encrypting database" = "เกิดข้อผิดพลาดในการ encrypt ฐานข้อมูล"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "เกิดข้อผิดพลาดในการส่งออกฐานข้อมูลแชท"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "เกิดข้อผิดพลาดในการนำเข้าฐานข้อมูลแชท"; /* No comment provided by engineer. */ @@ -1256,7 +1255,7 @@ swipe action */ /* alert title */ "Error receiving file" = "เกิดข้อผิดพลาดในการรับไฟล์"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "เกิดข้อผิดพลาดในการลบสมาชิก"; /* No comment provided by engineer. */ @@ -1381,6 +1380,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "ค้นหาแชทได้เร็วขึ้น"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง"; + /* No comment provided by engineer. */ "Fix" = "แก้ไข"; @@ -1703,9 +1705,9 @@ snd error text */ "Join" = "เข้าร่วม"; /* No comment provided by engineer. */ -"join as %@" = "เข้าร่วมเป็น %@"; +"Join as %@" = "เข้าร่วมเป็น %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "เข้าร่วมกลุ่ม"; /* No comment provided by engineer. */ @@ -2016,6 +2018,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "ปิด"; @@ -2028,7 +2031,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "เสนอแล้ว %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "ตกลง"; /* No comment provided by engineer. */ @@ -2091,7 +2096,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "ผู้ติดต่อของคุณเท่านั้นที่สามารถส่งข้อความเสียงได้"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "เปิดแชท"; /* authentication reason */ @@ -2145,7 +2150,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "โปรดตรวจสอบว่าคุณใช้ลิงก์ที่ถูกต้องหรือขอให้ผู้ติดต่อของคุณส่งลิงก์ใหม่ให้คุณ"; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "โปรดตรวจสอบการเชื่อมต่อเครือข่ายของคุณกับ %@ แล้วลองอีกครั้ง"; /* No comment provided by engineer. */ @@ -2178,9 +2183,6 @@ time to disappear */ /* No comment provided by engineer. */ "Polish interface" = "อินเตอร์เฟซภาษาโปแลนด์"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "เก็บข้อความที่ร่างไว้ล่าสุดพร้อมไฟล์แนบ"; @@ -2316,11 +2318,12 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "ลดการใช้แบตเตอรี่"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "ปฏิเสธ"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "ปฏิเสธคำขอติดต่อ"; /* call status */ @@ -2577,10 +2580,10 @@ chat item action */ "Sent messages will be deleted after set time." = "ข้อความที่ส่งจะถูกลบหลังเกินเวลาที่กําหนด"; /* server test error */ -"Server requires authorization to create queues, check password" = "เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน"; +"Server requires authorization to create queues, check password." = "เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน"; /* server test error */ -"Server requires authorization to upload, check password" = "เซิร์ฟเวอร์ต้องการการอนุญาตในการอัปโหลด โปรดตรวจสอบรหัสผ่าน"; +"Server requires authorization to upload, check password." = "เซิร์ฟเวอร์ต้องการการอนุญาตในการอัปโหลด โปรดตรวจสอบรหัสผ่าน"; /* No comment provided by engineer. */ "Server test failed!" = "การทดสอบเซิร์ฟเวอร์ล้มเหลว!"; @@ -2829,13 +2832,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้"; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น"; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "ขีดที่สองที่เราพลาด! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "ผู้ส่งจะไม่ได้รับแจ้ง"; /* No comment provided by engineer. */ @@ -3126,9 +3126,6 @@ chat item action */ /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "คุณเชื่อมต่อกับเซิร์ฟเวอร์ที่ใช้รับข้อความจากผู้ติดต่อนี้"; -/* No comment provided by engineer. */ -"you are invited to group" = "คุณได้รับเชิญให้เข้าร่วมกลุ่ม"; - /* No comment provided by engineer. */ "You are invited to group" = "คุณได้รับเชิญให้เข้าร่วมกลุ่ม"; @@ -3171,7 +3168,7 @@ chat item action */ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "คุณสามารถใช้มาร์กดาวน์เพื่อจัดรูปแบบข้อความ:"; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "คุณไม่สามารถส่งข้อความได้!"; /* chat item text */ @@ -3292,10 +3289,10 @@ chat item action */ "Your privacy" = "ความเป็นส่วนตัวของคุณ"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้"; +"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น"; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้"; /* No comment provided by engineer. */ "Your random profile" = "โปรไฟล์แบบสุ่มของคุณ"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index e3bb11d1cc..f6d38b0632 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -178,6 +178,9 @@ /* time interval */ "%d sec" = "%d saniye"; +/* delete after time */ +"%d seconds(s)" = "%d saniye(ler)"; + /* integrity error chat item */ "%d skipped message(s)" = "%d okunmamış mesaj(lar)"; @@ -283,6 +286,9 @@ time interval */ time interval */ "1 week" = "1 hafta"; +/* delete after time */ +"1 year" = "1 yıl"; + /* No comment provided by engineer. */ "1-time link" = "tek kullanımlık bağlantı"; @@ -336,34 +342,59 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Kabul et"; +/* alert action */ +"Accept as member" = "Üye olarak kabul et"; + +/* alert action */ +"Accept as observer" = "Gözlemci olarak kabul et"; + /* No comment provided by engineer. */ "Accept conditions" = "Koşulları kabul et"; /* No comment provided by engineer. */ "Accept connection request?" = "Bağlantı isteği kabul edilsin mi?"; +/* alert title */ +"Accept contact request" = "Kişi isteğini kabul et"; + /* notification body */ "Accept contact request from %@?" = "%@ 'den gelen iletişim isteği kabul edilsin mi?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Takma adla kabul et"; +/* alert title */ +"Accept member" = "Üyeyi kabul et"; + +/* rcv group event chat item */ +"accepted %@" = "kabul edildi %@"; + /* call status */ "accepted call" = "kabul edilen arama"; /* No comment provided by engineer. */ "Accepted conditions" = "Kabul edilmiş koşullar"; +/* chat list item title */ +"accepted invitation" = "davetiye kabul edildi"; + +/* rcv group event chat item */ +"accepted you" = "seni kabul etti"; + /* No comment provided by engineer. */ -"Acknowledged" = "Onaylandı"; +"Acknowledged" = "Onaylı"; /* No comment provided by engineer. */ "Acknowledgement errors" = "Onay hataları"; +/* token status text */ +"Active" = "Aktif"; + /* No comment provided by engineer. */ "Active connections" = "Aktif bağlantılar"; @@ -373,6 +404,12 @@ swipe action */ /* No comment provided by engineer. */ "Add friends" = "Arkadaş ekle"; +/* No comment provided by engineer. */ +"Add list" = "Liste ekle"; + +/* placeholder for sending contact request */ +"Add message" = "Mesaj ekle"; + /* No comment provided by engineer. */ "Add profile" = "Profil ekle"; @@ -388,6 +425,9 @@ swipe action */ /* No comment provided by engineer. */ "Add to another device" = "Başka bir cihaza ekle"; +/* No comment provided by engineer. */ +"Add to list" = "Listeye ekle"; + /* No comment provided by engineer. */ "Add welcome message" = "Karşılama mesajı ekleyin"; @@ -445,12 +485,21 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "şifreleme kabul ediliyor…"; +/* member criteria value */ +"all" = "tümü"; + +/* No comment provided by engineer. */ +"All" = "Hepsi"; + /* No comment provided by engineer. */ "All app data is deleted." = "Tüm uygulama verileri silinir."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Tüm konuşmalar ve mesajlar silinecektir. Bu, geri alınamaz!"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Tüm sohbetler %@ listesinden kaldırılacak ve liste silinecek."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Kullanıldığında bütün veriler silinir."; @@ -478,6 +527,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Tüm Profiller"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Tüm raporlar sizin için arşivlenecek."; + +/* No comment provided by engineer. */ +"All servers" = "Tüm sunucular"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Konuştuğun kişilerin tümü bağlı kalacaktır."; @@ -502,6 +557,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow downgrade" = "Sürüm düşürmeye izin ver"; +/* No comment provided by engineer. */ +"Allow files and media only if your contact allows them." = "Dosyalara ve medyaya yalnızca iletişiminiz izin verdiğinde izin verin."; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde)"; @@ -523,6 +581,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Mesajları moderatörlere bildirmeye izin ver."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Dosya ve medya göndermeye izin ver."; @@ -550,16 +611,19 @@ swipe action */ /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Kişilerinizin kaybolan mesajlar göndermesine izin verin."; +/* No comment provided by engineer. */ +"Allow your contacts to send files and media." = "Kişilerinizin dosya ve medya göndermesine izin verin."; + /* No comment provided by engineer. */ "Allow your contacts to send voice messages." = "Kişilerinizin sesli mesajlar göndermesine izin verin."; /* No comment provided by engineer. */ "Already connected?" = "Zaten bağlandı?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Zaten bağlanılıyor!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Zaten gruba bağlanılıyor!"; /* pref value */ @@ -577,6 +641,9 @@ swipe action */ /* No comment provided by engineer. */ "and %lld other events" = "ve %lld diğer etkinlikler"; +/* report reason */ +"Another reason" = "Başka bir sebep"; + /* No comment provided by engineer. */ "Answer call" = "Aramayı cevapla"; @@ -592,6 +659,9 @@ swipe action */ /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Uygulama yerel dosyaları şifreler (videolar dışında)."; +/* No comment provided by engineer. */ +"App group:" = "Uygulama grubu:"; + /* No comment provided by engineer. */ "App icon" = "Uygulama simgesi"; @@ -619,15 +689,36 @@ swipe action */ /* No comment provided by engineer. */ "Apply to" = "Şuna uygula"; +/* No comment provided by engineer. */ +"Archive" = "Arşivle"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "%lld raporu arşivle?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Tüm raporlar arşivlensin mi?"; + /* No comment provided by engineer. */ "Archive and upload" = "Arşivle ve yükle"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Daha sonra görüşmek için kişileri arşivleyin."; +/* No comment provided by engineer. */ +"Archive report" = "Raporu arşivle"; + +/* No comment provided by engineer. */ +"Archive report?" = "Rapor arşivlensin mi?"; + +/* swipe action */ +"Archive reports" = "Raporları arşivle"; + /* No comment provided by engineer. */ "Archived contacts" = "Arşivli kişiler"; +/* No comment provided by engineer. */ +"archived report" = "arşivlenmiş rapor"; + /* No comment provided by engineer. */ "Archiving database" = "Veritabanı arşivleniyor"; @@ -676,9 +767,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Fotoğrafları otomatik kabul et"; -/* alert title */ -"Auto-accept settings" = "Ayarları otomatik olarak kabul et"; - /* No comment provided by engineer. */ "Back" = "Geri"; @@ -706,6 +794,9 @@ swipe action */ /* No comment provided by engineer. */ "Better groups" = "Daha iyi gruplar"; +/* No comment provided by engineer. */ +"Better groups performance" = "Daha iyi grup performansı"; + /* No comment provided by engineer. */ "Better message dates." = "Daha iyi mesaj tarihleri."; @@ -718,12 +809,21 @@ swipe action */ /* No comment provided by engineer. */ "Better notifications" = "Daha iyi bildirimler"; +/* No comment provided by engineer. */ +"Better privacy and security" = "Daha iyi gizlilik ve güvenlik"; + /* No comment provided by engineer. */ "Better security ✅" = "Daha iyi güvenlik ✅"; /* No comment provided by engineer. */ "Better user experience" = "Daha iyi kullanıcı deneyimi"; +/* No comment provided by engineer. */ +"Bio" = "Biy"; + +/* alert title */ +"Bio too large" = "Biyografi çok uzun"; + /* No comment provided by engineer. */ "Black" = "Siyah"; @@ -767,6 +867,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "bold" = "kalın"; +/* No comment provided by engineer. */ +"Bot" = "Bot"; + /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "Sen ve konuştuğun kişi mesaj tepkileri ekleyebilir."; @@ -779,6 +882,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Sen ve konuştuğun kişi kaybolan mesajlar gönderebilir."; +/* No comment provided by engineer. */ +"Both you and your contact can send files and media." = "Sen de kişilerin de medya ve dosya gönderebilir."; + /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Sen ve konuştuğun kişi sesli mesaj gönderebilir."; @@ -791,9 +897,18 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "İş konuşmaları"; +/* No comment provided by engineer. */ +"Business connection" = "İş bağlantısı"; + +/* No comment provided by engineer. */ +"Businesses" = "İşletmeler"; + /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz:\n- herkese açık gruplarda yalnızca yasal içerik göndermek.\n- diğer kullanıcılara saygı göstermek – spam yapmamak."; + /* No comment provided by engineer. */ "call" = "Ara"; @@ -824,6 +939,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Üye aranamaz"; +/* alert title */ +"Can't change profile" = "Profil değiştirilemiyor"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Kişi davet edilemiyor!"; @@ -833,8 +951,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Üyeye mesaj gönderilemiyor"; +/* No comment provided by engineer. */ +"can't send messages" = "mesaj gönderilemiyor"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "İptal et"; /* No comment provided by engineer. */ @@ -861,6 +983,9 @@ alert button */ /* No comment provided by engineer. */ "Change" = "Değiştir"; +/* alert title */ +"Change automatic message deletion?" = "Otomatik mesaj silme değiştirilsin mi?"; + /* authentication reason */ "Change chat profiles" = "Sohbet profillerini değiştir"; @@ -913,7 +1038,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "Sohbet zaten mevcut"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "Sohbet zaten mevcut!"; /* No comment provided by engineer. */ @@ -967,9 +1092,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Sohbet senden silinecek - bu geri alınamaz!"; +/* chat toolbar */ +"Chat with admins" = "Yöneticilerle sohbet et"; + +/* No comment provided by engineer. */ +"Chat with member" = "Üye ile sohbet et"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Üyeler katılmadan önce onlarla sohbet edin."; + /* No comment provided by engineer. */ "Chats" = "Sohbetler"; +/* No comment provided by engineer. */ +"Chats with members" = "Üyelerle sohbetler"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Her 20 dakikada mesajları kontrol et."; @@ -1009,6 +1146,12 @@ set passcode view */ /* No comment provided by engineer. */ "Clear conversation?" = "Sohbet temizlensin mi?"; +/* No comment provided by engineer. */ +"Clear group?" = "Grup temizlensin mi?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "Grup temizlensin veya silinsin mi?"; + /* No comment provided by engineer. */ "Clear private notes?" = "Gizli notlar temizlensin mi?"; @@ -1024,6 +1167,9 @@ set passcode view */ /* No comment provided by engineer. */ "colored" = "renklendirilmiş"; +/* report reason */ +"Community guidelines violation" = "Topluluk kurallarının ihlali"; + /* server test step */ "Compare file" = "Dosya karşılaştır"; @@ -1060,6 +1206,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE sunucularını ayarla"; +/* No comment provided by engineer. */ +"Configure server operators" = "Sunucu operatörlerini yapılandır"; + /* No comment provided by engineer. */ "Confirm" = "Onayla"; @@ -1090,6 +1239,9 @@ set passcode view */ /* No comment provided by engineer. */ "Confirm upload" = "Yüklemeyi onayla"; +/* token status text */ +"Confirmed" = "Onaylandı"; + /* server test step */ "Connect" = "Bağlan"; @@ -1097,7 +1249,7 @@ set passcode view */ "Connect automatically" = "Otomatik olarak bağlan"; /* No comment provided by engineer. */ -"Connect incognito" = "Gizli bağlan"; +"Connect faster! 🚀" = "Daha hızlı bağlanın! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Bilgisayara bağlan"; @@ -1108,25 +1260,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Arkadaşlarınıza daha hızlı bağlanın."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "Kendine mi bağlanacaksın?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "Kendine mi bağlanacaksın?\nBu senin kendi tek kullanımlık bağlantın!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "Kendine mi bağlanacaksın?\nBu senin kendi SimpleX adresin!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Kişi adresi aracılığıyla bağlan"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Bağlantı aracılığıyla bağlan"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Tek kullanımlık bağlantı aracılığıyla bağlan"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "%@ ile bağlan"; /* No comment provided by engineer. */ @@ -1138,9 +1287,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Bilgisayara bağlandı"; -/* rcv group event chat item */ -"connected directly" = "doğrudan bağlandı"; - /* No comment provided by engineer. */ "Connected servers" = "Bağlı sunucular"; @@ -1190,6 +1336,9 @@ set passcode view */ "Connection and servers status." = "Bağlantı ve sunucuların durumu."; /* No comment provided by engineer. */ +"Connection blocked" = "Bağlantı engellendi"; + +/* alert title */ "Connection error" = "Bağlantı hatası"; /* No comment provided by engineer. */ @@ -1198,19 +1347,28 @@ set passcode view */ /* chat list item title (it should not be shown */ "connection established" = "bağlantı kuruldu"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "Bağlantı sunucu operatörü tarafından engellendi:\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "Bağlantı hazır değil."; + /* No comment provided by engineer. */ "Connection notifications" = "Bağlantı bildirimleri"; /* No comment provided by engineer. */ "Connection request sent!" = "Bağlantı daveti gönderildi!"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "Bağlantı için şifreleme yeniden görüşmesi gerekiyor."; + /* No comment provided by engineer. */ "Connection security" = "Bağlantı güvenliği"; /* No comment provided by engineer. */ "Connection terminated" = "Bağlantı sonlandırılmış"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Bağlantı süresi geçmiş"; /* No comment provided by engineer. */ @@ -1231,9 +1389,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Kişi zaten mevcut"; +/* No comment provided by engineer. */ +"contact deleted" = "kişi silindi"; + /* No comment provided by engineer. */ "Contact deleted!" = "Kişiler silindi!"; +/* No comment provided by engineer. */ +"contact disabled" = "kişi devre dışı"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "kişi uçtan uca şifrelemeye sahiptir"; @@ -1252,9 +1416,18 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Kişi adı"; +/* No comment provided by engineer. */ +"contact not ready" = "kişi hazır değil"; + /* No comment provided by engineer. */ "Contact preferences" = "Kişi tercihleri"; +/* No comment provided by engineer. */ +"Contact requests from groups" = "Gruplardan gelen iletişim talepleri"; + +/* No comment provided by engineer. */ +"contact should accept…" = "kişi kabul etmeli…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "Kişiler silinecek - bu geri alınamaz !"; @@ -1264,6 +1437,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "Kişiler silinmesi için mesajları işaretleyebilir; onları görüntüleyebilirsin."; +/* blocking reason */ +"Content violates conditions of use" = "İçerik kullanım koşullarını ihlal ediyor"; + /* No comment provided by engineer. */ "Continue" = "Devam et"; @@ -1306,6 +1482,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create link" = "Bağlantı oluştur"; +/* No comment provided by engineer. */ +"Create list" = "Liste oluştur"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "[bilgisayar uygulaması] nda yeni bir profil oluştur(https://simplex.chat/downloads/). 💻"; @@ -1316,10 +1495,10 @@ set passcode view */ "Create queue" = "Sıra oluştur"; /* No comment provided by engineer. */ -"Create secret group" = "Gizli grup oluştur"; +"Create SimpleX address" = "SimpleX adresi oluştur"; /* No comment provided by engineer. */ -"Create SimpleX address" = "SimpleX adresi oluştur"; +"Create your address" = "Adresinizi oluşturun"; /* No comment provided by engineer. */ "Create your profile" = "Profilini oluştur"; @@ -1485,12 +1664,18 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat" = "Sohbeti sil"; +/* No comment provided by engineer. */ +"Delete chat messages from your device." = "Sohbet mesajlarını cihazınızdan silin."; + /* No comment provided by engineer. */ "Delete chat profile" = "Sohbet profilini sil"; /* No comment provided by engineer. */ "Delete chat profile?" = "Sohbet profili silinsin mi?"; +/* alert title */ +"Delete chat with member?" = "Üye ile sohbet silinsin mi?"; + /* No comment provided by engineer. */ "Delete chat?" = "Sohbet silinsin mi?"; @@ -1539,6 +1724,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete link?" = "Bağlantı silinsin mi?"; +/* alert title */ +"Delete list?" = "Liste silinsin mi?"; + /* No comment provided by engineer. */ "Delete member message?" = "Kişinin mesajı silinsin mi?"; @@ -1569,6 +1757,9 @@ swipe action */ /* server test step */ "Delete queue" = "Sırayı sil"; +/* No comment provided by engineer. */ +"Delete report" = "Raporu sil"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Tek seferde en fazla 20 mesaj silin."; @@ -1599,6 +1790,9 @@ swipe action */ /* No comment provided by engineer. */ "Deletion errors" = "Silme hatası"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Apple tarafından düşürülse bile teslim edilir."; + /* No comment provided by engineer. */ "Delivery" = "Teslimat"; @@ -1608,9 +1802,15 @@ swipe action */ /* No comment provided by engineer. */ "Delivery receipts!" = "Mesaj gönderildi bilgisi!"; +/* No comment provided by engineer. */ +"Deprecated options" = "Kullanımdan kaldırılan seçenekler"; + /* No comment provided by engineer. */ "Description" = "Açıklama"; +/* alert title */ +"Description too large" = "Açıklama çok büyük"; + /* No comment provided by engineer. */ "Desktop address" = "Bilgisayar adresi"; @@ -1674,6 +1874,12 @@ swipe action */ /* No comment provided by engineer. */ "Disable (keep overrides)" = "Devre dışı bırak (geçersiz kılmaları koru)"; +/* alert title */ +"Disable automatic message deletion?" = "Otomatik mesaj silme devre dışı bırakılsın mı?"; + +/* alert button */ +"Disable delete messages" = "Mesaj silmeyi devre dışı bırak"; + /* No comment provided by engineer. */ "Disable for all" = "Herkes için devre dışı bırak"; @@ -1734,6 +1940,9 @@ swipe action */ /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "Acil aramalar için SimpleX'i KULLANMAYIN."; +/* No comment provided by engineer. */ +"Documents:" = "Belgeler:"; + /* No comment provided by engineer. */ "Don't create address" = "Adres oluşturma"; @@ -1741,8 +1950,14 @@ swipe action */ "Don't enable" = "Etkinleştirme"; /* No comment provided by engineer. */ +"Don't miss important messages." = "Önemli mesajları kaçırmayın."; + +/* alert action */ "Don't show again" = "Yeniden gösterme"; +/* No comment provided by engineer. */ +"Done" = "Tamam"; + /* No comment provided by engineer. */ "Downgrade and open chat" = "Sürüm düşür ve sohbeti aç"; @@ -1798,6 +2013,9 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Grup profilini düzenle"; +/* No comment provided by engineer. */ +"Empty message!" = "Boş mesaj!"; + /* No comment provided by engineer. */ "Enable" = "Etkinleştir"; @@ -1810,6 +2028,12 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Kamera erişimini etkinleştir"; +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Varsayılan olarak kaybolan mesajları etkinleştirin."; + +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Daha iyi meta veri gizliliği için Ağ & sunucu ayarlarında Flux'u etkinleştirin."; + /* No comment provided by engineer. */ "Enable for all" = "Herkes için etkinleştir"; @@ -1921,6 +2145,9 @@ chat item action */ /* chat item text */ "encryption re-negotiation required for %@" = "şifrelemenin yeniden anlaşması %@ için gerekiyor"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "Şifreleme yeniden görüşmesi devam ediyor."; + /* No comment provided by engineer. */ "ended" = "bitti"; @@ -1975,28 +2202,40 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Bağlantı isteği kabul edilirken hata oluştu"; +/* alert title */ +"Error accepting member" = "Üyeyi kabul etme hatası"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Üye(ler) eklenirken hata oluştu"; /* alert title */ "Error adding server" = "Sunucu eklenirken hata oluştu"; +/* No comment provided by engineer. */ +"Error adding short link" = "Kısa bağlantı ekleme hatası"; + /* No comment provided by engineer. */ "Error changing address" = "Adres değiştirilirken hata oluştu"; +/* alert title */ +"Error changing chat profile" = "Sohbet profilini değiştirme hatası"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Bağlantı profili değiştirilirken hata oluştu"; /* No comment provided by engineer. */ "Error changing role" = "Rol değiştirilirken hata oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Ayar değiştirilirken hata oluştu"; /* No comment provided by engineer. */ "Error changing to incognito!" = "Gizli moduna geçerken hata oluştu!"; /* No comment provided by engineer. */ +"Error checking token status" = "Jeton durumu kontrol hatası"; + +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Yönlendirme sunucusu %@'ya bağlanırken hata oluştu. Lütfen daha sonra deneyin."; /* No comment provided by engineer. */ @@ -2008,6 +2247,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating group link" = "Grup bağlantısı oluşturulurken hata oluştu"; +/* alert title */ +"Error creating list" = "Liste oluşturma hatası"; + /* No comment provided by engineer. */ "Error creating member contact" = "Kişi iletişimi oluşturulurken hata oluştu"; @@ -2017,22 +2259,28 @@ chat item action */ /* No comment provided by engineer. */ "Error creating profile!" = "Profil oluşturulurken hata oluştu!"; +/* No comment provided by engineer. */ +"Error creating report" = "Rapor oluşturma hatası"; + /* No comment provided by engineer. */ "Error decrypting file" = "Dosya şifresi çözülürken hata oluştu"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Üye ile sohbet silme hatası"; + +/* alert title */ "Error deleting chat database" = "Sohbet veritabanı silinirken sorun oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Sohbet silinirken hata oluştu!"; /* No comment provided by engineer. */ "Error deleting connection" = "Bağlantı silinirken hata oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Veritabanı silinirken hata oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Eski veritabanı silinirken hata oluştu"; /* No comment provided by engineer. */ @@ -2053,13 +2301,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Veritabanı şifrelemesi çözülürken hata oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Sohbet veritabanı dışa aktarılırken hata oluştu"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Tema dışa aktarılırken hata oluştu: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Sohbet veritabanı içe aktarılırken hata oluştu"; /* No comment provided by engineer. */ @@ -2072,7 +2320,10 @@ chat item action */ "Error migrating settings" = "Ayarlar taşınırken hata oluştu"; /* No comment provided by engineer. */ -"Error opening chat" = "Sohbeti açarken sorun oluştu"; +"Error opening chat" = "Kişiyi hazırlama hatası"; + +/* No comment provided by engineer. */ +"Error opening group" = "Grubu hazırlama hatası"; /* alert title */ "Error receiving file" = "Dosya alınırken sorun oluştu"; @@ -2083,12 +2334,24 @@ chat item action */ /* No comment provided by engineer. */ "Error reconnecting servers" = "Hata sunuculara yeniden bağlanılıyor"; -/* No comment provided by engineer. */ +/* alert title */ +"Error registering for notifications" = "Bildirimler için kayıt hatası"; + +/* alert title */ +"Error rejecting contact request" = "Kişi isteğini reddetme hatası"; + +/* alert title */ "Error removing member" = "Kişiyi silerken sorun oluştu"; +/* alert title */ +"Error reordering lists" = "Listeleri yeniden sıralama hatası"; + /* No comment provided by engineer. */ "Error resetting statistics" = "Hata istatistikler sıfırlanıyor"; +/* alert title */ +"Error saving chat list" = "Sohbet listesini kaydetme hatası"; + /* No comment provided by engineer. */ "Error saving group profile" = "Grup profili kaydedilirken sorun oluştu"; @@ -2122,6 +2385,9 @@ chat item action */ /* No comment provided by engineer. */ "Error sending message" = "Mesaj gönderilirken hata oluştu"; +/* No comment provided by engineer. */ +"Error setting auto-accept" = "Otomatik kabul ayarında hata"; + /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Görüldü ayarlanırken hata oluştu!"; @@ -2131,7 +2397,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Sohbet durdurulurken hata oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Profil değiştirme sırasında hata oluştu"; /* alertTitle */ @@ -2140,6 +2406,9 @@ chat item action */ /* No comment provided by engineer. */ "Error synchronizing connection" = "Bağlantı senkronizasyonunda hata oluştu"; +/* No comment provided by engineer. */ +"Error testing server connection" = "Sunucu bağlantısını test etme hatası"; + /* No comment provided by engineer. */ "Error updating group link" = "Grup bağlantısı güncellenirken hata oluştu"; @@ -2191,7 +2460,10 @@ snd error text */ "Expand" = "Genişlet"; /* No comment provided by engineer. */ -"expired" = "Süresi dolmuş"; +"expired" = "süresi dolmuş"; + +/* token status text */ +"Expired" = "Süresi dolmuş"; /* No comment provided by engineer. */ "Export database" = "Veritabanını dışarı aktar"; @@ -2217,18 +2489,30 @@ snd error text */ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "Hızlı ve gönderici çevrimiçi olana kadar beklemek yok!"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "Grupların daha hızlı silinmesi."; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "Daha hızlı katılma ve daha güvenilir mesajlar."; +/* No comment provided by engineer. */ +"Faster sending messages." = "Mesajların daha hızlı gönderilmesi."; + /* swipe action */ "Favorite" = "Favori"; +/* No comment provided by engineer. */ +"Favorites" = "Favoriler"; + /* file error alert title */ "File error" = "Dosya hatası"; /* alert message */ "File errors:\n%@" = "Dosya hataları:\n%@"; +/* file error text */ +"File is blocked by server operator:\n%@." = "Dosya sunucu operatörü tarafından engellendi:\n%@."; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Dosya bulunamadı - muhtemelen dosya silindi veya göderim iptal edildi."; @@ -2262,6 +2546,9 @@ snd error text */ /* chat feature */ "Files and media" = "Dosyalar ve medya"; +/* No comment provided by engineer. */ +"Files and media are prohibited in this chat." = "Bu sohbette dosyalar ve medya yasaktır."; + /* No comment provided by engineer. */ "Files and media are prohibited." = "Dosyalar ve medya bu grupta yasaklandı."; @@ -2286,6 +2573,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Sohbetleri daha hızlı bul"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil"; + /* No comment provided by engineer. */ "Fix" = "Düzelt"; @@ -2304,6 +2594,9 @@ snd error text */ /* No comment provided by engineer. */ "Fix not supported by group member" = "Düzeltme grup üyesi tarafından desteklenmiyor"; +/* No comment provided by engineer. */ +"For all moderators" = "Tüm moderatörler için"; + /* servers error */ "For chat profile %@:" = "Sohbet profili için %@:"; @@ -2313,6 +2606,9 @@ snd error text */ /* No comment provided by engineer. */ "For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Örneğin, eğer kişiniz SimpleX Sohbet sunucusundan mesajları alıyorsa, uygulamanız bu mesajları Flux sunucusundan iletecektir."; +/* No comment provided by engineer. */ +"For me" = "Benim için"; + /* No comment provided by engineer. */ "For private routing" = "Gizli yönlendirme için"; @@ -2349,8 +2645,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "%lld mesajlarını ilet"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Yönlendirme sunucusu %@, hedef sunucu %@'ya bağlanamadı. Lütfen daha sonra deneyin."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Yönlendirme sunucusu %1$@, hedef sunucu %2$@'ya bağlanamadı. Lütfen daha sonra deneyin."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "Yönlendirme sunucusu adresi ağ ayarlarıyla uyumsuz: %@."; @@ -2385,6 +2681,9 @@ snd error text */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Daha da azaltılmış pil kullanımı"; +/* No comment provided by engineer. */ +"Get notified when mentioned." = "Bahsedildiğinde bildirim alın."; + /* No comment provided by engineer. */ "GIFs and stickers" = "GİFler ve çıkartmalar"; @@ -2394,13 +2693,16 @@ snd error text */ /* message preview */ "Good morning!" = "Günaydın!"; +/* shown on group welcome message */ +"group" = "grup"; + /* No comment provided by engineer. */ "Group" = "Grup"; /* No comment provided by engineer. */ "Group already exists" = "Grup çoktan mevcut"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Grup çoktan mevcut!"; /* No comment provided by engineer. */ @@ -2424,6 +2726,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Grup davet artık geçerli değil, gönderici tarafından silindi."; +/* No comment provided by engineer. */ +"group is deleted" = "grup silindi"; + /* No comment provided by engineer. */ "Group link" = "Grup bağlantısı"; @@ -2448,6 +2753,9 @@ snd error text */ /* snd group event chat item */ "group profile updated" = "grup profili güncellendi"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Grup profili değiştirildi. Eğer kaydederseniz, güncellenmiş profil grup üyelerine gönderilecektir."; + /* No comment provided by engineer. */ "Group welcome message" = "Grup hoşgeldin mesajı"; @@ -2457,9 +2765,15 @@ snd error text */ /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "Grup senden silinecektir - bu geri alınamaz!"; +/* No comment provided by engineer. */ +"Groups" = "Gruplar"; + /* No comment provided by engineer. */ "Help" = "Yardım"; +/* No comment provided by engineer. */ +"Help admins moderating their groups." = "Yöneticilere gruplarını yönetmelerinde yardımcı olun."; + /* No comment provided by engineer. */ "Hidden" = "Gizlenmiş"; @@ -2496,6 +2810,9 @@ snd error text */ /* No comment provided by engineer. */ "How it helps privacy" = "Gizliliğinizi nasıl arttırır"; +/* alert button */ +"How it works" = "Nasıl çalışır"; + /* No comment provided by engineer. */ "How SimpleX works" = "SimpleX nasıl çalışır"; @@ -2583,6 +2900,12 @@ snd error text */ /* No comment provided by engineer. */ "inactive" = "inaktif"; +/* report reason */ +"Inappropriate content" = "Uygunsuz içerik"; + +/* report reason */ +"Inappropriate profile" = "Uygunsuz profil"; + /* No comment provided by engineer. */ "Incognito" = "Gizli"; @@ -2649,6 +2972,21 @@ snd error text */ /* No comment provided by engineer. */ "Interface colors" = "Arayüz renkleri"; +/* token status text */ +"Invalid" = "Geçersiz"; + +/* token status text */ +"Invalid (bad token)" = "Geçersiz (kötü jeton)"; + +/* token status text */ +"Invalid (expired)" = "Geçersiz (süresi dolmuş)"; + +/* token status text */ +"Invalid (unregistered)" = "Geçersiz (kayıtlı değil)"; + +/* token status text */ +"Invalid (wrong topic)" = "Geçersiz (yanlış konu)"; + /* invalid chat data */ "invalid chat" = "geçersi̇z sohbet"; @@ -2664,7 +3002,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Geçersiz görünen ad!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Geçersiz bağlantı"; /* No comment provided by engineer. */ @@ -2764,24 +3102,18 @@ snd error text */ "Join" = "Katıl"; /* No comment provided by engineer. */ -"join as %@" = "%@ olarak katıl"; +"Join as %@" = "%@ olarak katıl"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Gruba katıl"; /* No comment provided by engineer. */ "Join group conversations" = "Grup sohbetlerine katıl"; -/* No comment provided by engineer. */ -"Join group?" = "Gruba katılınsın mı?"; - /* No comment provided by engineer. */ "Join incognito" = "Gizli katıl"; -/* No comment provided by engineer. */ -"Join with current profile" = "Şu anki profille katıl"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Bu gruba katılınsın mı?\nBu senin grup için bağlantın %@!"; /* No comment provided by engineer. */ @@ -2799,6 +3131,9 @@ snd error text */ /* alert title */ "Keep unused invitation?" = "Kullanılmamış davet tutulsun mu?"; +/* No comment provided by engineer. */ +"Keep your chats clean" = "Sohbetlerinizi temiz tutun"; + /* No comment provided by engineer. */ "Keep your connections" = "Bağlantılarınızı koruyun"; @@ -2832,6 +3167,9 @@ snd error text */ /* rcv group event chat item */ "left" = "ayrıldı"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Mobil ağlarda daha az trafik."; + /* email subject */ "Let's talk in SimpleX Chat" = "Hadi SimpleX Chat'te konuşalım"; @@ -2850,6 +3188,15 @@ snd error text */ /* No comment provided by engineer. */ "Linked desktops" = "Bağlanmış bilgisayarlar"; +/* swipe action */ +"List" = "Liste"; + +/* No comment provided by engineer. */ +"List name and emoji should be different for all lists." = "Liste adı ve emojisi tüm listeler için farklı olmalıdır."; + +/* No comment provided by engineer. */ +"List name..." = "Liste adı..."; + /* No comment provided by engineer. */ "LIVE" = "CANLI"; @@ -2859,6 +3206,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "Canlı mesajlar"; +/* in progress text */ +"Loading profile…" = "Profil yükleniyor…"; + /* No comment provided by engineer. */ "Local name" = "Yerel isim"; @@ -2910,15 +3260,30 @@ snd error text */ /* No comment provided by engineer. */ "Member" = "Kişi"; +/* past/unknown group member */ +"Member %@" = "Üye%@"; + /* profile update event chat item */ "member %@ changed to %@" = "kişi %1$@ , %2$@ olarak değişti"; +/* No comment provided by engineer. */ +"Member admission" = "Üye kabulü"; + /* rcv group event chat item */ "member connected" = "bağlanıldı"; +/* No comment provided by engineer. */ +"member has old version" = "üye eski sürümde"; + /* item status text */ "Member inactive" = "Üye inaktif"; +/* No comment provided by engineer. */ +"Member is deleted - can't accept request" = "Üye silinmiş - istek kabul edilemez"; + +/* chat feature */ +"Member reports" = "Üye raporları"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "Üye rolü \"%@\" olarak değiştirilecektir. Tüm sohbet üyeleri bilgilendirilecektir."; @@ -2928,15 +3293,24 @@ snd error text */ /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Üye rolü \"%@\" olarak değiştirilecektir. Ve üye yeni bir davetiye alacaktır."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Üye sohbetten kaldırılacak - bu geri alınamaz!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Üye gruptan çıkarılacaktır - bu geri alınamaz!"; +/* alert message */ +"Member will join the group, accept member?" = "Üye gruba katılacak, kabul edilsin mi?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "Grup üyeleri mesaj tepkileri ekleyebilir."; /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde)"; +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "Üyeler mesajları moderatörlere bildirebilir."; + /* No comment provided by engineer. */ "Members can send direct messages." = "Grup üyeleri doğrudan mesajlar gönderebilir."; @@ -2952,6 +3326,9 @@ snd error text */ /* No comment provided by engineer. */ "Members can send voice messages." = "Grup üyeleri sesli mesajlar gönderebilir."; +/* No comment provided by engineer. */ +"Mention members 👋" = "Üyeleri belirtin 👋"; + /* No comment provided by engineer. */ "Menus" = "Menüler"; @@ -2973,6 +3350,9 @@ snd error text */ /* item status text */ "Message forwarded" = "Mesaj iletildi"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Bağlan'a dokunduğunuzda hemen mesaj gönderin."; + /* item status description */ "Message may be delivered later if member becomes active." = "Kullanıcı aktif olursa mesaj iletilebilir."; @@ -3021,9 +3401,15 @@ snd error text */ /* No comment provided by engineer. */ "Messages & files" = "Mesajlar & dosyalar"; +/* No comment provided by engineer. */ +"Messages are protected by **end-to-end encryption**." = "Mesajlar **uçtan uca şifreleme** ile korunmaktadır."; + /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "%@ den gelen mesajlar gösterilecektir!"; +/* alert message */ +"Messages in this chat will never be deleted." = "Bu sohbetteki mesajlar asla silinmeyecek."; + /* No comment provided by engineer. */ "Messages received" = "Mesajlar alındı"; @@ -3096,15 +3482,24 @@ snd error text */ /* marked deleted chat item preview text */ "moderated by %@" = "%@ tarafından yönetilmekte"; +/* member role */ +"moderator" = "moderatör"; + /* time unit */ "months" = "aylar"; +/* swipe action */ +"More" = "Daha fazla"; + /* No comment provided by engineer. */ "More improvements are coming soon!" = "Daha fazla geliştirmeler yakında geliyor!"; /* No comment provided by engineer. */ "More reliable network connection." = "Daha güvenilir ağ bağlantısı."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Daha güvenilir bildirimler"; + /* item status description */ "Most likely this connection is deleted." = "Büyük ihtimalle bu bağlantı silinmiş."; @@ -3114,6 +3509,9 @@ snd error text */ /* notification label action */ "Mute" = "Sustur"; +/* notification label action */ +"Mute all" = "Tümünü sessize al"; + /* No comment provided by engineer. */ "Muted when inactive!" = "Aktif değilken susturuldu!"; @@ -3126,12 +3524,18 @@ snd error text */ /* No comment provided by engineer. */ "Network connection" = "Ağ bağlantısı"; +/* No comment provided by engineer. */ +"Network decentralization" = "Ağ merkeziyetsizliği"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu."; /* No comment provided by engineer. */ "Network management" = "Ağ yönetimi"; +/* No comment provided by engineer. */ +"Network operator" = "Ağ operatörü"; + /* No comment provided by engineer. */ "Network settings" = "Ağ ayarları"; @@ -3141,6 +3545,9 @@ snd error text */ /* delete after time */ "never" = "asla"; +/* token status text */ +"New" = "Yeni"; + /* No comment provided by engineer. */ "New chat" = "Yeni sohbet"; @@ -3159,6 +3566,12 @@ snd error text */ /* No comment provided by engineer. */ "New display name" = "Yeni görünen ad"; +/* notification */ +"New events" = "Yeni etkinlikler"; + +/* No comment provided by engineer. */ +"New group role: Moderator" = "Yeni grup rolü: Moderatör"; + /* No comment provided by engineer. */ "New in %@" = "%@ da yeni"; @@ -3168,6 +3581,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Yeni üye rolü"; +/* rcv group event chat item */ +"New member wants to join the group." = "Yeni üye gruba katılmak istiyor."; + /* notification */ "new message" = "yeni mesaj"; @@ -3180,6 +3596,9 @@ snd error text */ /* No comment provided by engineer. */ "New passphrase…" = "Yeni parola…"; +/* No comment provided by engineer. */ +"New server" = "Yeni sunucu"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Uygulamayı her başlattığınızda yeni SOCKS kimlik bilgileri kullanılacaktır."; @@ -3195,6 +3614,18 @@ snd error text */ /* Authentication unavailable */ "No app password" = "Uygulama şifresi yok"; +/* No comment provided by engineer. */ +"No chats" = "Hiç sohbet yok"; + +/* No comment provided by engineer. */ +"No chats found" = "Hiç sohbet bulunamadı"; + +/* No comment provided by engineer. */ +"No chats in list %@" = "Listede hiç sohbet yok %@"; + +/* No comment provided by engineer. */ +"No chats with members" = "Üyelerle hiç sohbet yok"; + /* No comment provided by engineer. */ "No contacts selected" = "Hiçbir kişi seçilmedi"; @@ -3225,6 +3656,15 @@ snd error text */ /* No comment provided by engineer. */ "No info, try to reload" = "Bilgi yok, yenilemeyi deneyin"; +/* servers error */ +"No media & file servers." = "Hiç medya & dosya sunucusu yok."; + +/* No comment provided by engineer. */ +"No message" = "Mesaj yok"; + +/* servers error */ +"No message servers." = "Hiç mesaj sunucusu yok."; + /* No comment provided by engineer. */ "No network connection" = "Ağ bağlantısı yok"; @@ -3237,21 +3677,48 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Sesli mesaj kaydetmek için izin yok"; +/* alert title */ +"No private routing session" = "Özel yönlendirme oturumu yok"; + /* No comment provided by engineer. */ "No push server" = "Yerel"; /* No comment provided by engineer. */ "No received or sent files" = "Hiç alınmış veya gönderilmiş dosya yok"; +/* servers error */ +"No servers for private message routing." = "Özel mesaj yönlendirmesi için hiç sunucu yok."; + +/* servers error */ +"No servers to receive files." = "Dosya almak için hiç sunucu yok."; + +/* servers error */ +"No servers to receive messages." = "Mesaj almak için hiç sunucu yok."; + +/* servers error */ +"No servers to send files." = "Dosya göndermek için hiç sunucu yok."; + /* copied message info in history */ "no text" = "metin yok"; +/* alert title */ +"No token!" = "Hiç jeton yok!"; + +/* No comment provided by engineer. */ +"No unread chats" = "Okunmamış sohbet yok"; + /* No comment provided by engineer. */ "No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok."; /* No comment provided by engineer. */ "Not compatible!" = "Uyumlu değil!"; +/* No comment provided by engineer. */ +"not synchronized" = "senkronize edilmedi"; + +/* No comment provided by engineer. */ +"Notes" = "Notlar"; + /* No comment provided by engineer. */ "Nothing selected" = "Hiçbir şey seçilmedi"; @@ -3264,6 +3731,15 @@ snd error text */ /* No comment provided by engineer. */ "Notifications are disabled!" = "Bildirimler devre dışı!"; +/* alert title */ +"Notifications error" = "Bildirim hatası"; + +/* No comment provided by engineer. */ +"Notifications privacy" = "Bildirim gizliliği"; + +/* alert title */ +"Notifications status" = "Bildirim durumu"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Şimdi yöneticiler:\n- üyelerin mesajlarını silebilir\n- üyeleri devre dışı bırakabilir (\"gözlemci\" rolü)"; @@ -3272,6 +3748,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "kapalı"; @@ -3284,7 +3761,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "%1$@: %2$@ teklif etti"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Tamam"; /* No comment provided by engineer. */ @@ -3308,6 +3787,9 @@ time to disappear */ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion ana bilgisayarları kullanılmayacaktır."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Yalnızca sohbet sahipleri tercihleri değiştirebilir."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar."; @@ -3323,6 +3805,12 @@ time to disappear */ /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "Yalnızca grup sahipleri sesli mesajları etkinleştirebilir."; +/* No comment provided by engineer. */ +"Only sender and moderators see it" = "Sadece gönderici ve moderatörler görür"; + +/* No comment provided by engineer. */ +"Only you and moderators see it" = "Sadece siz ve moderatörler görür"; + /* No comment provided by engineer. */ "Only you can add message reactions." = "Sadece siz mesaj tepkileri ekleyebilirsiniz."; @@ -3335,6 +3823,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only you can send disappearing messages." = "Sadece sen kaybolan mesajlar gönderebilirsin."; +/* No comment provided by engineer. */ +"Only you can send files and media." = "Sadece sen dosya ve medya gönderebilirsin."; + /* No comment provided by engineer. */ "Only you can send voice messages." = "Sadece sen sesli mesajlar gönderebilirsin."; @@ -3350,6 +3841,9 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send disappearing messages." = "Sadece karşıdaki kişi kaybolan mesajlar gönderebilir."; +/* No comment provided by engineer. */ +"Only your contact can send files and media." = "Sadece kişilerin dosya ve medya gönderebilir."; + /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sadece karşıdaki kişi sesli mesajlar gönderebilir."; @@ -3357,23 +3851,65 @@ time to disappear */ "Open" = "Aç"; /* No comment provided by engineer. */ +"Open changes" = "Açık değişiklikler"; + +/* new chat action */ "Open chat" = "Sohbeti aç"; /* authentication reason */ "Open chat console" = "Sohbet konsolunu aç"; +/* alert action */ +"Open clean link" = "Temiz linki aç"; + /* No comment provided by engineer. */ +"Open conditions" = "Açık koşullar"; + +/* alert action */ +"Open full link" = "Tam linki aç"; + +/* new chat action */ "Open group" = "Grubu aç"; +/* alert title */ +"Open link?" = "Bağlantıyı aç?"; + /* authentication reason */ "Open migration to another device" = "Başka bir cihaza açık geçiş"; +/* new chat action */ +"Open new chat" = "Yeni sohbet aç"; + +/* new chat action */ +"Open new group" = "Yeni grup aç"; + /* No comment provided by engineer. */ "Open Settings" = "Ayarları aç"; +/* No comment provided by engineer. */ +"Open to accept" = "Kabul etmek için aç"; + +/* No comment provided by engineer. */ +"Open to connect" = "Bağlanmak için aç"; + +/* No comment provided by engineer. */ +"Open to join" = "Katılmak için aç"; + +/* No comment provided by engineer. */ +"Open to use bot" = "Botu kullanmak için aç"; + /* No comment provided by engineer. */ "Opening app…" = "Uygulama açılıyor…"; +/* No comment provided by engineer. */ +"Operator" = "Operatör"; + +/* alert title */ +"Operator server" = "Operatör sunucusu"; + +/* No comment provided by engineer. */ +"Or import archive file" = "Veya arşiv dosyasını içe aktar"; + /* No comment provided by engineer. */ "Or paste archive link" = "Veya arşiv bağlantısını yapıştırın"; @@ -3386,6 +3922,12 @@ time to disappear */ /* No comment provided by engineer. */ "Or show this code" = "Veya bu kodu göster"; +/* No comment provided by engineer. */ +"Or to share privately" = "Veya özel olarak paylaşmak için"; + +/* No comment provided by engineer. */ +"Organize chats into lists" = "Sohbetleri listelere ayır"; + /* No comment provided by engineer. */ "other" = "diğer"; @@ -3425,9 +3967,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Gösterilecek şifre"; -/* past/unknown group member */ -"Past member %@" = "Geçmiş üye %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Bilgisayar adresini yapıştır"; @@ -3443,9 +3982,18 @@ time to disappear */ /* No comment provided by engineer. */ "peer-to-peer" = "eşler arası"; +/* No comment provided by engineer. */ +"pending" = "beklemede"; + /* No comment provided by engineer. */ "Pending" = "Bekleniyor"; +/* No comment provided by engineer. */ +"pending approval" = "onay bekliyor"; + +/* No comment provided by engineer. */ +"pending review" = "inceleme bekliyor"; + /* No comment provided by engineer. */ "Periodic" = "Periyodik olarak"; @@ -3476,7 +4024,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Lütfen doğru bağlantıyı kullandığınızı kontrol edin veya kişiden size başka bir bağlantı göndermesini isteyin."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Lütfen ağ bağlantınızı %@ ile kontrol edin ve tekrar deneyin."; /* No comment provided by engineer. */ @@ -3512,21 +4060,33 @@ time to disappear */ /* No comment provided by engineer. */ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Lütfen parolayı güvenli bir şekilde saklayın, kaybederseniz parolayı DEĞİŞTİREMEZSİNİZ."; +/* token info */ +"Please try to disable and re-enable notfications." = "Lütfen bildirimleri devre dışı bırakmayı ve yeniden etkinleştirmeyi deneyin."; + +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Lütfen grup moderatörlerinin gruba katılma isteğinizi incelemesini bekleyin."; + +/* token info */ +"Please wait for token activation to complete." = "Lütfen jeton aktivasyonunun tamamlanmasını bekleyin."; + +/* token info */ +"Please wait for token to be registered." = "Lütfen jeton kaydedilene kadar bekleyin."; + /* No comment provided by engineer. */ "Polish interface" = "Lehçe arayüz"; /* No comment provided by engineer. */ "Port" = "Port"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Son mesaj taslağını ekleriyle birlikte koru."; /* No comment provided by engineer. */ "Preset server address" = "Ön ayarlı sunucu adresi"; +/* No comment provided by engineer. */ +"Preset servers" = "Ön ayar sunucuları"; + /* No comment provided by engineer. */ "Preview" = "Ön izleme"; @@ -3536,12 +4096,24 @@ time to disappear */ /* No comment provided by engineer. */ "Privacy & security" = "Gizlilik & güvenlik"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Müşterileriniz için gizlilik."; + +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Gizlilik politikası ve kullanım koşulları."; + /* No comment provided by engineer. */ "Privacy redefined" = "Gizlilik yeniden tanımlandı"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez."; + /* No comment provided by engineer. */ "Private filenames" = "Gizli dosya adları"; +/* No comment provided by engineer. */ +"Private media file names." = "Özel medya dosyası adları."; + /* No comment provided by engineer. */ "Private message routing" = "Gizli mesaj yönlendirme"; @@ -3554,9 +4126,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Gizli yönlendirme"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Gizli yönlendirme hatası"; +/* alert title */ +"Private routing timeout" = "Özel yönlendirme zaman aşımı"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil ve sunucu bağlantıları"; @@ -3587,6 +4162,9 @@ time to disappear */ /* No comment provided by engineer. */ "Prohibit messages reactions." = "Mesajlarda tepkileri yasakla."; +/* No comment provided by engineer. */ +"Prohibit reporting messages to moderators." = "Mesajları moderatörlere bildirmeyi yasakla."; + /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Üyelere doğrudan mesaj göndermeyi yasakla."; @@ -3614,6 +4192,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "IP adresinizi kişileriniz tarafından seçilen mesajlaşma yönlendiricilerinden koruyun.\n*Ağ ve sunucular* ayarlarında etkinleştirin."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Protokol arka plan zaman aşımı"; + /* No comment provided by engineer. */ "Protocol timeout" = "Protokol zaman aşımı"; @@ -3669,7 +4250,7 @@ time to disappear */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "[GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme)."; /* No comment provided by engineer. */ -"Receipts are disabled" = "Alıcılar devre dışı bırakıldı"; +"Receipts are disabled" = "Alındı onayları devre dışı bırakıldı"; /* No comment provided by engineer. */ "Receive errors" = "Alım sırasında hata"; @@ -3749,16 +4330,32 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Azaltılmış pil kullanımı"; -/* reject incoming call via notification +/* No comment provided by engineer. */ +"Register" = "Kaydol"; + +/* token info */ +"Register notification token?" = "Bildirim jetonunu kaydet?"; + +/* token status text */ +"Registered" = "Kayıtlı"; + +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Reddet"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Reddet (göndericiye bildirim GİTMEYECEKTİR)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Bağlanma isteğini reddet"; +/* alert title */ +"Reject member?" = "Üyeyi reddet?"; + +/* No comment provided by engineer. */ +"rejected" = "reddedildi"; + /* call status */ "rejected call" = "geri çevrilmiş çağrı"; @@ -3777,6 +4374,9 @@ swipe action */ /* No comment provided by engineer. */ "Remove image" = "Resmi kaldır"; +/* No comment provided by engineer. */ +"Remove link tracking" = "Bağlantı izlemeyi kaldır"; + /* No comment provided by engineer. */ "Remove member" = "Kişiyi sil"; @@ -3795,12 +4395,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "kişi adresi silindi"; +/* No comment provided by engineer. */ +"removed from group" = "gruptan çıkarıldı"; + /* profile update event chat item */ "removed profile picture" = "profil fotoğrafı silindi"; /* rcv group event chat item */ "removed you" = "sen kaldırıldın"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Mesajları kaldırır ve üyeleri engeller."; + /* No comment provided by engineer. */ "Renegotiate" = "Yeniden müzakere"; @@ -3810,24 +4416,66 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Şifreleme yeniden müzakere edilsin mi?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Bağlantı isteği tekrarlansın mı?"; - /* No comment provided by engineer. */ "Repeat download" = "Tekrar indir"; /* No comment provided by engineer. */ "Repeat import" = "İthalatı tekrarla"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Katılma isteği tekrarlansın mı?"; - /* No comment provided by engineer. */ "Repeat upload" = "Yüklemeyi tekrarla"; /* chat item action */ "Reply" = "Yanıtla"; +/* chat item action */ +"Report" = "Rapor et"; + +/* report reason */ +"Report content: only group moderators will see it." = "İçeriği rapor et: sadece grup moderatörleri görecek."; + +/* report reason */ +"Report member profile: only group moderators will see it." = "Üye profilini rapor et: sadece grup moderatörleri görecek."; + +/* report reason */ +"Report other: only group moderators will see it." = "Diğerini rapor et: sadece grup moderatörleri görecek."; + +/* No comment provided by engineer. */ +"Report reason?" = "Rapor nedeni?"; + +/* alert title */ +"Report sent to moderators" = "Rapor moderatörlere gönderildi"; + +/* report reason */ +"Report spam: only group moderators will see it." = "Spam rapor et: sadece grup moderatörleri görecek."; + +/* report reason */ +"Report violation: only group moderators will see it." = "İhlali rapor et: sadece grup moderatörleri görecek."; + +/* report in notification */ +"Report: %@" = "Rapor: %@"; + +/* No comment provided by engineer. */ +"Reporting messages to moderators is prohibited." = "Mesajları moderatörlere bildirmek yasaktır."; + +/* No comment provided by engineer. */ +"Reports" = "Raporlar"; + +/* No comment provided by engineer. */ +"request is sent" = "istek gönderildi"; + +/* No comment provided by engineer. */ +"request to join rejected" = "katılma isteği reddedildi"; + +/* rcv group event chat item */ +"requested connection" = "istenilen bağlantı"; + +/* rcv direct event chat item */ +"requested connection from group %@" = "%@ grubundan bağlantı isteği"; + +/* chat list item title */ +"requested to connect" = "bağlanma isteği gönderildi"; + /* No comment provided by engineer. */ "Required" = "Gerekli"; @@ -3873,12 +4521,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Veritabanını geri yüklerken hata oluştu"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Yeniden dene"; /* chat item action */ "Reveal" = "Göster"; +/* No comment provided by engineer. */ +"review" = "incele"; + +/* No comment provided by engineer. */ +"Review conditions" = "Koşulları gözden geçir"; + +/* No comment provided by engineer. */ +"Review group members" = "Grup üyelerini gözden geçir"; + +/* admission stage */ +"Review members" = "Üyeleri gözden geçir"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Üyeleri kabul etmeden önce gözden geçir (\"kapı çalma\")."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "yöneticiler tarafından incelendi"; + /* No comment provided by engineer. */ "Revoke" = "İptal et"; @@ -3907,6 +4573,12 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Kaydet (ve kişilere bildir)"; +/* alert button */ +"Save (and notify members)" = "Kaydet (ve üyelere bildir)"; + +/* alert title */ +"Save admission settings?" = "Kabul ayarlarını kaydet?"; + /* alert button */ "Save and notify contact" = "Kaydet ve kişilere bildir"; @@ -3922,6 +4594,12 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Grup profilini kaydet"; +/* alert title */ +"Save group profile?" = "Grup profilini kaydet?"; + +/* No comment provided by engineer. */ +"Save list" = "Listeyi kaydet"; + /* No comment provided by engineer. */ "Save passphrase and open chat" = "Parolayı kaydet ve sohbeti aç"; @@ -4058,10 +4736,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Bir canlı mesaj gönder - yazışına göre kişiye(lere) kendini günceller"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "Görüldü bilgilerini şuraya gönder"; +"Send contact request?" = "Kişi isteği gönderilsin mi?"; /* No comment provided by engineer. */ -"send direct message" = "doğrudan mesaj gönder"; +"Send delivery receipts to" = "Görüldü bilgilerini şuraya gönder"; /* No comment provided by engineer. */ "Send direct message to connect" = "Bağlanmak için doğrudan mesaj gönder"; @@ -4090,18 +4768,30 @@ chat item action */ /* No comment provided by engineer. */ "Send notifications" = "Bildirimler gönder"; +/* No comment provided by engineer. */ +"Send private reports" = "Özel raporlar gönder"; + /* No comment provided by engineer. */ "Send questions and ideas" = "Fikirler ve sorular gönderin"; /* No comment provided by engineer. */ "Send receipts" = "Mesajlar gönder"; +/* No comment provided by engineer. */ +"Send request" = "İstek gönder"; + +/* No comment provided by engineer. */ +"Send request without message" = "Mesaj olmadan istek gönder"; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Bunları galeriden veya özel klavyelerden gönder."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Yeni üyelere 100 adete kadar son mesajları gönderin."; +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Özel geri bildiriminizi gruplara gönderin."; + /* alert message */ "Sender cancelled file transfer." = "Gönderici dosya gönderimini iptal etti."; @@ -4112,7 +4802,7 @@ chat item action */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir."; /* No comment provided by engineer. */ -"Sending delivery receipts will be enabled for all contacts." = "Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek."; +"Sending delivery receipts will be enabled for all contacts." = "Tüm kişiler için alındı bilgisi gönderme özelliği etkinleştirilecek."; /* No comment provided by engineer. */ "Sending file will be stopped." = "Dosya gönderimi durdurulacaktır."; @@ -4165,6 +4855,9 @@ chat item action */ /* No comment provided by engineer. */ "Server" = "Sunucu"; +/* alert message */ +"Server added to operator %@." = "Sunucu operatör %@'ya eklendi."; + /* No comment provided by engineer. */ "Server address" = "Sunucu adresi"; @@ -4174,14 +4867,23 @@ chat item action */ /* srv error text. */ "Server address is incompatible with network settings." = "Sunucu adresi ağ ayarlarıyla uyumlu değil."; +/* alert title */ +"Server operator changed." = "Sunucu operatörü değişti."; + +/* No comment provided by engineer. */ +"Server operators" = "Sunucu operatörleri"; + +/* alert title */ +"Server protocol changed." = "Sunucu protokolü değişti."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "sunucu kuyruk bilgisi: %1$@\n\nson alınan msj: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin"; +"Server requires authorization to create queues, check password." = "Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin"; /* server test error */ -"Server requires authorization to upload, check password" = "Sunucunun yükleme yapması için yetki gereklidir, şifreyi kontrol edin"; +"Server requires authorization to upload, check password." = "Sunucunun yükleme yapması için yetki gereklidir, şifreyi kontrol edin"; /* No comment provided by engineer. */ "Server test failed!" = "Sunucu testinde hata oluştu!"; @@ -4210,6 +4912,9 @@ chat item action */ /* No comment provided by engineer. */ "Set 1 day" = "1 günlüğüne ayarla"; +/* No comment provided by engineer. */ +"Set chat name…" = "Sohbet adını belirle…"; + /* No comment provided by engineer. */ "Set contact name…" = "Kişi adı gir…"; @@ -4222,6 +4927,12 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Sistem kimlik doğrulaması yerine ayarla."; +/* No comment provided by engineer. */ +"Set member admission" = "Üye kabulünü ayarla"; + +/* No comment provided by engineer. */ +"Set message expiration in chats." = "Sohbetlerde mesajın sonlanma süresini ayarla."; + /* profile update event chat item */ "set new contact address" = "yeni kişi adresi ayarla"; @@ -4237,6 +4948,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Dışa aktarmak için parola ayarla"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Profil biyografisi ve hoşgeldiniz mesajı düzenle."; + /* No comment provided by engineer. */ "Set the message shown to new members!" = "Yeni üyeler için gösterilen bir mesaj ayarla!"; @@ -4259,9 +4973,15 @@ chat item action */ /* No comment provided by engineer. */ "Share 1-time link" = "Tek kullanımlık bağlantıyı paylaş"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Arkadaşınızla 1 defaya mahsus bağlantı paylaşın"; + /* No comment provided by engineer. */ "Share address" = "Adresi paylaş"; +/* No comment provided by engineer. */ +"Share address publicly" = "Adresinizi herkese açık olarak paylaşın"; + /* alert title */ "Share address with contacts?" = "Kişilerle adres paylaşılsın mı?"; @@ -4271,9 +4991,18 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Bağlantıyı paylaş"; +/* alert button */ +"Share old address" = "Eski adresi paylaş"; + +/* alert button */ +"Share old link" = "Eski bağlantıyı paylaş"; + /* No comment provided by engineer. */ "Share profile" = "Profil paylaş"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "SimpleX adresini sosyal medyada paylaşın."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Bu tek kullanımlık bağlantı davetini paylaş"; @@ -4283,6 +5012,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Kişilerle paylaş"; +/* No comment provided by engineer. */ +"Share your address" = "Adresini paylaş"; + +/* No comment provided by engineer. */ +"Short description" = "Kısa açıklama"; + +/* No comment provided by engineer. */ +"Short link" = "Kısa bağlantı"; + +/* No comment provided by engineer. */ +"Short SimpleX address" = "Kısa SimpleX adresi"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Gizli yönlendirme yoluyla gönderilen mesajlarda → işaretini göster."; @@ -4319,6 +5060,21 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX Adresi"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "SimpleX adresi ve 1 defaya mahsus bağlantılar, herhangi bir mesajlaşma uygulaması aracılığıyla paylaşmak için güvenlidir."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX adresi mi yoksa 1 defaya mahsus bağlantı mı?"; + +/* alert title */ +"SimpleX address settings" = "Ayarları otomatik olarak kabul et"; + +/* simplex link type */ +"SimpleX channel link" = "SimpleX kanal bağlantısı"; + +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat ve Flux, Flux tarafından işletilen sunucuları uygulamaya dahil etmek için bir anlaşma yaptı."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "SimpleX Chat güvenliği Trails of Bits tarafından denetlenmiştir."; @@ -4358,6 +5114,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "SimpleX protokolleri Trail of Bits tarafından incelenmiştir."; +/* simplex link type */ +"SimpleX relay link" = "SimpleX aktarıcı bağlantısı"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Basitleştirilmiş gizli mod"; @@ -4394,9 +5153,16 @@ chat item action */ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "İçe aktarma sırasında bazı önemli olmayan hatalar oluştu:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Bazı sunucular testi geçemedi:\n%@"; + /* notification title */ "Somebody" = "Biri"; +/* blocking reason +report reason */ +"Spam" = "Spam"; + /* No comment provided by engineer. */ "Square, circle, or anything in between." = "Kare,daire, veya aralarında herhangi bir şey."; @@ -4454,6 +5220,9 @@ chat item action */ /* No comment provided by engineer. */ "Stopping chat" = "Sohbeti durdurma"; +/* No comment provided by engineer. */ +"Storage" = "Depolama"; + /* No comment provided by engineer. */ "strike" = "çizik"; @@ -4496,6 +5265,21 @@ chat item action */ /* No comment provided by engineer. */ "Tap button " = "Tuşa bas "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Sohbet etmek için Bağlan'a dokunun"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Bağlan'a dokunarak isteği gönderin"; + +/* No comment provided by engineer. */ +"Tap Connect to use bot" = "Botu kullanmak için Bağlan tuşuna bas"; + +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun."; + +/* No comment provided by engineer. */ +"Tap Join group" = "Gruba katıl'a dokunun"; + /* No comment provided by engineer. */ "Tap to activate profile." = "Profili etkinleştirmek için tıkla."; @@ -4517,9 +5301,15 @@ chat item action */ /* No comment provided by engineer. */ "TCP connection" = "TCP bağlantısı"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "TCP bağlantı arka plan zaman aşımı"; + /* No comment provided by engineer. */ "TCP connection timeout" = "TCP bağlantı zaman aşımı"; +/* No comment provided by engineer. */ +"TCP port for messaging" = "Mesajlaşma için TCP portu"; + /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -4535,6 +5325,9 @@ chat item action */ /* server test failure */ "Test failed at step %@." = "Test %@ adımında başarısız oldu."; +/* No comment provided by engineer. */ +"Test notifications" = "Bildirimleri test et"; + /* No comment provided by engineer. */ "Test server" = "Sunucuyu test et"; @@ -4553,9 +5346,15 @@ chat item action */ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Kullanıcılar için teşekkürler - Weblate aracılığıyla katkıda bulun!"; +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "Adres kısa olacak ve profiliniz bu adres üzerinden paylaşılacaktır."; + /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "Uygulama, her sohbette farklı operatörler kullanarak gizliliğinizi korur."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Uygulama bilinmeyen dosya sunucularından indirmeleri onaylamanızı isteyecektir (.onion hariç)."; @@ -4565,6 +5364,9 @@ chat item action */ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Taradığınız kod bir SimpleX bağlantı QR kodu değildir."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "Bağlantı, teslim edilmemiş mesajlar limitine ulaştı, kişiniz çevrimdışı olabilir."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Bağlantı kabulünüz iptal edilecektir!"; @@ -4586,6 +5388,9 @@ chat item action */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Bir sonraki mesajın kimliği yanlış (bir öncekinden az veya aynı).\nBazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "Bağlantı kısa olacak ve grup profili bağlantı üzerinden paylaşılacaktır."; + /* No comment provided by engineer. */ "The message will be deleted for all members." = "Mesaj tüm üyeler için silinecektir."; @@ -4602,17 +5407,23 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir."; /* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak."; +"The same conditions will apply to operator **%@**." = "Aynı koşullar operatör **%@** için de geçerli olacaktır."; + +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Uygulamadaki ikinci ön ayar operatörü!"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Özlediğimiz ikinci tik! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Gönderene BİLDİRİLMEYECEKTİR"; /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Mevcut sohbet profilinizin yeni bağlantıları için sunucular **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Mevcut sohbet profiliniz için yeni dosyaların sunucuları **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Yapıştırdığın metin bir SimpleX bağlantısı değildir."; @@ -4622,6 +5433,9 @@ chat item action */ /* No comment provided by engineer. */ "Themes" = "Temalar"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Bu koşullar ayrıca şunlar için de geçerli olacaktır: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Bu ayarlar mevcut profiliniz **%@** içindir."; @@ -4634,6 +5448,9 @@ chat item action */ /* No comment provided by engineer. */ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Bu işlem geri alınamaz - seçilenden daha önce gönderilen ve alınan mesajlar silinecektir. Bu işlem birkaç dakika sürebilir."; +/* alert message */ +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Bu işlem geri alınamaz - daha önce seçilen tarihten önceki bu sohbette gönderilen ve alınan mesajlar silinecektir."; + /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Bu işlem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri döndürülemez şekilde kaybolacaktır."; @@ -4659,17 +5476,23 @@ chat item action */ "This group no longer exists." = "Bu grup artık mevcut değildir."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Bu senin kendi tek kullanımlık bağlantın!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Bu senin kendi SimpleX adresin!"; +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Bu bağlantı daha yeni bir uygulama sürümü gerektiriyor. Lütfen uygulamayı güncelleyin veya kişinizden uyumlu bir bağlantı göndermesini isteyin."; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Bu bağlantı başka bir mobil cihazda kullanıldı, lütfen masaüstünde yeni bir bağlantı oluşturun."; +/* No comment provided by engineer. */ +"This message was deleted or not received yet." = "Bu mesaj silindi veya henüz alınmadı."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Bu ayar, geçerli sohbet profiliniz **%@** deki mesajlara uygulanır."; +/* No comment provided by engineer. */ +"This setting is for your current profile **%@**." = "Bu ayar, mevcut profiliniz içindir."; + +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Kaybolma süresi yalnızca yeni kişiler için ayarlanır."; + /* No comment provided by engineer. */ "Title" = "Başlık"; @@ -4685,6 +5508,9 @@ chat item action */ /* No comment provided by engineer. */ "To make a new connection" = "Yeni bir bağlantı oluşturmak için"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Bağlantınızın değiştirilmesine karşı korunmak için, kişi güvenlik kodlarını karşılaştırabilirsiniz."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Zaman bölgesini korumak için,fotoğraf/ses dosyaları UTC kullanır."; @@ -4697,6 +5523,9 @@ chat item action */ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir."; +/* No comment provided by engineer. */ +"To receive" = "Almak için"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Konuşmayı kaydetmek için lütfen Mikrofon kullanma izni verin."; @@ -4709,9 +5538,21 @@ chat item action */ /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Gizli profilinizi ortaya çıkarmak için **Sohbet profilleriniz** sayfasındaki arama alanına tam bir şifre girin."; +/* No comment provided by engineer. */ +"To send" = "Göndermek için"; + +/* alert message */ +"To send commands you must be connected." = "Komut göndermek için bağlı olmanız gerekir."; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Anlık anlık bildirimleri desteklemek için sohbet veritabanının taşınması gerekir."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Bağlantı denemesinden sonra başka bir profili kullanmak için, sohbeti silin ve bağlantıyı tekrar kullanın."; + +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "**%@**'nın sunucularını kullanmak için, kullanım koşullarını kabul edin."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın)."; @@ -4721,6 +5562,9 @@ chat item action */ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Bağlanırken gizli moda geçiş yap."; +/* token status */ +"Token status: %@." = "Jeton durumu: %@."; + /* No comment provided by engineer. */ "Toolbar opacity" = "Araç çubuğu opaklığı"; @@ -4769,6 +5613,9 @@ chat item action */ /* rcv group event chat item */ "unblocked %@" = "engeli kaldırıldı %@"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Teslim edilmemiş mesajlar"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Beklenmeyen geçiş durumu"; @@ -4835,6 +5682,9 @@ chat item action */ /* swipe action */ "Unread" = "Okunmamış"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Desteklenmeyen bağlantı bağlantısı"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Yeni üyelere 100e kadar en son mesajlar gönderildi."; @@ -4850,6 +5700,9 @@ chat item action */ /* No comment provided by engineer. */ "Update settings?" = "Ayarları güncelleyelim mi?"; +/* No comment provided by engineer. */ +"Updated conditions" = "Güncellenmiş koşullar"; + /* rcv group event chat item */ "updated group profile" = "grup profili güncellendi"; @@ -4859,9 +5712,27 @@ chat item action */ /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Ayarların güncellenmesi, istemciyi tüm sunuculara yeniden bağlayacaktır."; +/* alert button */ +"Upgrade" = "Yükseltme"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Adres güncelleme"; + +/* alert message */ +"Upgrade address?" = "Adres güncellensin mi?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Yükselt ve sohbeti aç"; +/* alert message */ +"Upgrade group link?" = "Grub linki güncellensin mi?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Linki güncelle"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Adresini yükselt"; + /* No comment provided by engineer. */ "Upload errors" = "Yükleme hataları"; @@ -4884,11 +5755,20 @@ chat item action */ "Use .onion hosts" = ".onion ana bilgisayarlarını kullan"; /* No comment provided by engineer. */ -"Use chat" = "Sohbeti kullan"; +"Use %@" = "%@ kullan"; /* No comment provided by engineer. */ +"Use chat" = "Sohbeti kullan"; + +/* new chat action */ "Use current profile" = "Şu anki profili kullan"; +/* No comment provided by engineer. */ +"Use for files" = "Dosyalar için kullan"; + +/* No comment provided by engineer. */ +"Use for messages" = "Mesajlar için kullan"; + /* No comment provided by engineer. */ "Use for new connections" = "Yeni bağlantılar için kullan"; @@ -4896,9 +5776,12 @@ chat item action */ "Use from desktop" = "Bilgisayardan kullan"; /* No comment provided by engineer. */ -"Use iOS call interface" = "iOS arama arayüzünden kullan"; +"Use incognito profile" = "Gizli profil kullan"; /* No comment provided by engineer. */ +"Use iOS call interface" = "iOS arama arayüzünden kullan"; + +/* new chat action */ "Use new incognito profile" = "Yeni gizli profilden kullan"; /* No comment provided by engineer. */ @@ -4913,18 +5796,30 @@ chat item action */ /* No comment provided by engineer. */ "Use server" = "Sunucu kullan"; +/* No comment provided by engineer. */ +"Use servers" = "Sunucuları kullan"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat sunucuları kullanılsın mı?"; /* No comment provided by engineer. */ "Use SOCKS proxy" = "SOCKS vekili kullan"; +/* No comment provided by engineer. */ +"Use TCP port %@ when no port is specified." = "Port belirtilmediğinde TCP port %@ kullanın."; + +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Sadece ön ayar sunucuları için TCP port 443 kullanın."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Görüşme sırasında uygulamayı kullanın."; /* No comment provided by engineer. */ "Use the app with one hand." = "Uygulamayı tek elle kullan."; +/* No comment provided by engineer. */ +"Use web port" = "Web portunu kullan"; + /* No comment provided by engineer. */ "User selection" = "Kullanıcı seçimi"; @@ -4997,9 +5892,15 @@ chat item action */ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "1gb'a kadar videolar ve dosyalar"; +/* No comment provided by engineer. */ +"View conditions" = "Koşulları görüntüle"; + /* No comment provided by engineer. */ "View security code" = "Güvenlik kodunu görüntüle"; +/* No comment provided by engineer. */ +"View updated conditions" = "Güncellenmiş koşulları görüntüle"; + /* chat feature */ "Visible history" = "Görünür geçmiş"; @@ -5069,6 +5970,9 @@ chat item action */ /* No comment provided by engineer. */ "Welcome message is too long" = "Hoş geldiniz mesajı çok uzun"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Kişilerine hoş geldin👋"; + /* No comment provided by engineer. */ "What's new" = "Neler yeni"; @@ -5081,6 +5985,9 @@ chat item action */ /* No comment provided by engineer. */ "when IP hidden" = "IP gizliyken"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Birden fazla operatör etkinleştirildiğinde, hiçbiri kimin kiminle iletişim kurduğunu öğrenmek için meta veriye sahip değildir."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Biriyle gizli bir profil paylaştığınızda, bu profil sizi davet ettikleri gruplar için kullanılacaktır."; @@ -5135,6 +6042,9 @@ chat item action */ /* No comment provided by engineer. */ "You accepted connection" = "Bağlantıyı onayladın"; +/* snd group event chat item */ +"you accepted this member" = "bu üyeyi kabul ettiniz"; + /* No comment provided by engineer. */ "You allow" = "İzin veriyorsunuz"; @@ -5145,32 +6055,29 @@ chat item action */ "You are already connected to %@." = "Zaten %@'a bağlısınız."; /* No comment provided by engineer. */ +"You are already connected with %@." = "Zaten %@ ile bağlantıdasınız."; + +/* new chat sheet message */ "You are already connecting to %@." = "Zaten %@'a bağlanıyorsunuz."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Bu tek seferlik bağlantı üzerinden zaten bağlanıyorsunuz!"; /* No comment provided by engineer. */ "You are already in group %@." = "Zaten %@ grubundasın."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Zaten %@ grubuna katılıyorsunuz."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Bu bağlantı üzerinden gruba zaten katılıyorsunuz!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Gruba zaten bu bağlantı üzerinden katılıyorsunuz."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Gruba zaten katılıyorsunuz!\nKatılma isteği tekrarlansın mı?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Bu kişiden mesaj almak için kullanılan sunucuya bağlısınız."; -/* No comment provided by engineer. */ -"you are invited to group" = "gruba davet edildiniz"; - /* No comment provided by engineer. */ "You are invited to group" = "Gruba davet edildiniz"; @@ -5189,6 +6096,9 @@ chat item action */ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Görünüm ayarlarından değiştirebilirsiniz."; +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Sunucuları ayarlar aracılığıyla yapılandırabilirsiniz."; + /* No comment provided by engineer. */ "You can create it later" = "Daha sonra oluşturabilirsiniz"; @@ -5213,6 +6123,9 @@ chat item action */ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Arşivlenen kişilerden %@'ya mesaj gönderebilirsiniz."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Bağlantı adını ayarlayabilirsiniz, böylece bağlantının kiminle paylaşıldığını hatırlarsınız."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Kilit ekranı bildirim önizlemesini ayarlar üzerinden ayarlayabilirsiniz."; @@ -5237,7 +6150,10 @@ chat item action */ /* alert message */ "You can view invitation link again in connection details." = "Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "Raporlarınızı Yöneticilerle Sohbet bölümünde görüntüleyebilirsiniz."; + +/* alert title */ "You can't send messages!" = "Mesajlar gönderemezsiniz!"; /* chat item text */ @@ -5258,10 +6174,7 @@ chat item action */ /* No comment provided by engineer. */ "You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Bu adres üzerinden zaten bağlantı talebinde bulundunuz!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Zaten bağlantı isteğinde bulundunuz!\nBağlantı isteği tekrarlansın mı?"; /* No comment provided by engineer. */ @@ -5309,9 +6222,15 @@ chat item action */ /* chat list item description */ "you shared one-time link incognito" = "tek kullanımlık link paylaştınız gizli"; +/* token info */ +"You should receive notifications." = "Bildirim almanız gerekiyor."; + /* snd group event chat item */ "you unblocked %@" = "engelini kaldırdın %@"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Mesaj gönderebilmek için **isteğinizin kabul edilmesini beklemelisiniz**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Grup sahibinin cihazı çevrimiçi olduğunda gruba bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin!"; @@ -5328,10 +6247,10 @@ chat item action */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Arka planda 30 saniye kaldıktan sonra uygulamayı başlattığınızda veya devam ettirdiğinizde kimlik doğrulaması yapmanız gerekecektir."; /* No comment provided by engineer. */ -"You will connect to all group members." = "Bütün grup üyelerine bağlanacaksın."; +"You will still receive calls and notifications from muted profiles when they are active." = "Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz."; /* No comment provided by engineer. */ -"You will still receive calls and notifications from muted profiles when they are active." = "Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz."; +"You will stop receiving messages from this chat. Chat history will be preserved." = "Bu sohbetten mesaj almaya son vereceksiniz. Sohbet geçmişi korunacaktır."; /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Bu gruptan artık mesaj almayacaksınız. Sohbet geçmişi korunacaktır."; @@ -5348,6 +6267,9 @@ chat item action */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Bu grup için gizli bir profil kullanıyorsunuz - ana profilinizi paylaşmayı önlemek için kişileri davet etmeye izin verilmiyor"; +/* No comment provided by engineer. */ +"Your business contact" = "İş bağlantınız"; + /* No comment provided by engineer. */ "Your calls" = "Aramaların"; @@ -5363,8 +6285,14 @@ chat item action */ /* No comment provided by engineer. */ "Your chat profiles" = "Sohbet profillerin"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Sohbetiniz %@'ya taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Bağlantınız %@ adresine taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu."; +"Your connection was moved to %@ but an error happened when switching profile." = "Bağlantınız %@ adresine taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu."; + +/* No comment provided by engineer. */ +"Your contact" = "İrtibat kişiniz"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Kişiniz şu anda desteklenen maksimum boyuttan (%@) daha büyük bir dosya gönderdi."; @@ -5384,6 +6312,9 @@ chat item action */ /* No comment provided by engineer. */ "Your current profile" = "Mevcut profiliniz"; +/* No comment provided by engineer. */ +"Your group" = "Grubunuz"; + /* No comment provided by engineer. */ "Your ICE servers" = "ICE sunucularınız"; @@ -5399,21 +6330,24 @@ chat item action */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Profiliniz **%@** paylaşılacaktır."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır."; - /* No comment provided by engineer. */ "Your random profile" = "Rasgele profiliniz"; /* No comment provided by engineer. */ "Your server address" = "Sunucu adresiniz"; +/* No comment provided by engineer. */ +"Your servers" = "Sunucularınız"; + /* No comment provided by engineer. */ "Your settings" = "Ayarlarınız"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 734b8dda82..2c3d7b083d 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -178,6 +178,9 @@ /* time interval */ "%d sec" = "%d сек"; +/* delete after time */ +"%d seconds(s)" = "%d секунд(и)"; + /* integrity error chat item */ "%d skipped message(s)" = "%d пропущено повідомлення(ь)"; @@ -283,6 +286,9 @@ time interval */ time interval */ "1 week" = "1 тиждень"; +/* delete after time */ +"1 year" = "1 рік"; + /* No comment provided by engineer. */ "1-time link" = "Одноразове посилання"; @@ -336,22 +342,38 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "Прийняти"; +/* alert action */ +"Accept as member" = "Прийняти як учасника"; + +/* alert action */ +"Accept as observer" = "Прийняти як спостерігача"; + /* No comment provided by engineer. */ "Accept conditions" = "Прийняти умови"; /* No comment provided by engineer. */ "Accept connection request?" = "Прийняти запит на підключення?"; +/* alert title */ +"Accept contact request" = "Прийняти запит на контакт"; + /* notification body */ "Accept contact request from %@?" = "Прийняти запит на контакт від %@?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "Прийняти інкогніто"; +/* alert title */ +"Accept member" = "Прийняти учасника"; + +/* rcv group event chat item */ +"accepted %@" = "прийнято %@"; + /* call status */ "accepted call" = "прийнято виклик"; @@ -361,12 +383,18 @@ swipe action */ /* chat list item title */ "accepted invitation" = "прийняте запрошення"; +/* rcv group event chat item */ +"accepted you" = "прийняв(ла) вас"; + /* No comment provided by engineer. */ "Acknowledged" = "Визнано"; /* No comment provided by engineer. */ "Acknowledgement errors" = "Помилки підтвердження"; +/* token status text */ +"Active" = "Активний"; + /* No comment provided by engineer. */ "Active connections" = "Активні з'єднання"; @@ -376,6 +404,12 @@ swipe action */ /* No comment provided by engineer. */ "Add friends" = "Додайте друзів"; +/* No comment provided by engineer. */ +"Add list" = "Додати список"; + +/* placeholder for sending contact request */ +"Add message" = "Додати повідомлення"; + /* No comment provided by engineer. */ "Add profile" = "Додати профіль"; @@ -391,6 +425,9 @@ swipe action */ /* No comment provided by engineer. */ "Add to another device" = "Додати до іншого пристрою"; +/* No comment provided by engineer. */ +"Add to list" = "Додати до списку"; + /* No comment provided by engineer. */ "Add welcome message" = "Додати вітальне повідомлення"; @@ -448,12 +485,21 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "узгодження шифрування…"; +/* member criteria value */ +"all" = "усі"; + +/* No comment provided by engineer. */ +"All" = "Всі"; + /* No comment provided by engineer. */ "All app data is deleted." = "Всі дані програми видаляються."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Всі чати та повідомлення будуть видалені - це неможливо скасувати!"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Всі чати будуть видалені з списку %@, і список буде видалений."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Всі дані стираються при введенні."; @@ -481,6 +527,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "Всі профілі"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Всі скарги будуть заархівовані для вас."; + +/* No comment provided by engineer. */ +"All servers" = "Всі сервери"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Всі ваші контакти залишаться на зв'язку."; @@ -526,6 +578,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Дозволяє безповоротно видаляти надіслані повідомлення. (24 години)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Дозволити надсилати скаргу на повідомлення модераторам."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Дозволяє надсилати файли та медіа."; @@ -559,10 +614,10 @@ swipe action */ /* No comment provided by engineer. */ "Already connected?" = "Вже підключено?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "Вже підключаємось!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "Вже приєднуємося до групи!"; /* pref value */ @@ -580,6 +635,9 @@ swipe action */ /* No comment provided by engineer. */ "and %lld other events" = "та %lld інших подій"; +/* report reason */ +"Another reason" = "Інша причина"; + /* No comment provided by engineer. */ "Answer call" = "Відповісти на дзвінок"; @@ -595,6 +653,9 @@ swipe action */ /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Додаток шифрує нові локальні файли (крім відео)."; +/* No comment provided by engineer. */ +"App group:" = "Група застосунків:"; + /* No comment provided by engineer. */ "App icon" = "Іконка програми"; @@ -622,15 +683,36 @@ swipe action */ /* No comment provided by engineer. */ "Apply to" = "Звертатися до"; +/* No comment provided by engineer. */ +"Archive" = "Архівувати"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "Архівувати %lld скарг?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Архівувати всі скарги?"; + /* No comment provided by engineer. */ "Archive and upload" = "Архівування та завантаження"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Архівуйте контакти, щоб поспілкуватися пізніше."; +/* No comment provided by engineer. */ +"Archive report" = "Архівувати скаргу"; + +/* No comment provided by engineer. */ +"Archive report?" = "Архівувати скаргу?"; + +/* swipe action */ +"Archive reports" = "Архівувати скарги"; + /* No comment provided by engineer. */ "Archived contacts" = "Архівні контакти"; +/* No comment provided by engineer. */ +"archived report" = "архівование повідомлення"; + /* No comment provided by engineer. */ "Archiving database" = "Архівування бази даних"; @@ -679,9 +761,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "Автоматичне прийняття зображень"; -/* alert title */ -"Auto-accept settings" = "Автоприйняття налаштувань"; - /* No comment provided by engineer. */ "Back" = "Назад"; @@ -709,6 +788,9 @@ swipe action */ /* No comment provided by engineer. */ "Better groups" = "Кращі групи"; +/* No comment provided by engineer. */ +"Better groups performance" = "Краща продуктивність груп"; + /* No comment provided by engineer. */ "Better message dates." = "Кращі дати повідомлень."; @@ -721,12 +803,21 @@ swipe action */ /* No comment provided by engineer. */ "Better notifications" = "Кращі сповіщення"; +/* No comment provided by engineer. */ +"Better privacy and security" = "Краща конфіденційність і безпека"; + /* No comment provided by engineer. */ "Better security ✅" = "Краща безпека ✅"; /* No comment provided by engineer. */ "Better user experience" = "Покращений користувацький досвід"; +/* No comment provided by engineer. */ +"Bio" = "Біо"; + +/* alert title */ +"Bio too large" = "Біографія занадто велика"; + /* No comment provided by engineer. */ "Black" = "Чорний"; @@ -794,9 +885,18 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Ділові чати"; +/* No comment provided by engineer. */ +"Business connection" = "Бізнес-зв'язок"; + +/* No comment provided by engineer. */ +"Businesses" = "Бізнеси"; + /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Використовуючи SimpleX Chat, ви погоджуєтеся:\n- надсилати лише легальний контент у публічних групах.\n- поважати інших користувачів - без спаму."; + /* No comment provided by engineer. */ "call" = "дзвонити"; @@ -827,6 +927,9 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call member" = "Не вдається зателефонувати користувачеві"; +/* alert title */ +"Can't change profile" = "Не вдається змінити профіль"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Не вдається запросити контакт!"; @@ -836,8 +939,12 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't message member" = "Не можу надіслати повідомлення користувачеві"; +/* No comment provided by engineer. */ +"can't send messages" = "не можна надсилати"; + /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "Скасувати"; /* No comment provided by engineer. */ @@ -864,6 +971,9 @@ alert button */ /* No comment provided by engineer. */ "Change" = "Зміна"; +/* alert title */ +"Change automatic message deletion?" = "Змінити автоматичне видалення повідомлень?"; + /* authentication reason */ "Change chat profiles" = "Зміна профілів користувачів"; @@ -877,7 +987,7 @@ alert button */ "Change member role?" = "Змінити роль учасника?"; /* authentication reason */ -"Change passcode" = "Змінити пароль"; +"Change passcode" = "Змінити код доступу"; /* No comment provided by engineer. */ "Change receiving address" = "Змінити адресу отримання"; @@ -916,7 +1026,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "Чат вже існує"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "Чат вже існує!"; /* No comment provided by engineer. */ @@ -970,9 +1080,21 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Чат буде видалено для вас - цю дію неможливо скасувати!"; +/* chat toolbar */ +"Chat with admins" = "Чат з адміністраторами"; + +/* No comment provided by engineer. */ +"Chat with member" = "Чат з учасником"; + +/* No comment provided by engineer. */ +"Chat with members before they join." = "Спілкуйтеся з учасниками до того, як вони приєднаються."; + /* No comment provided by engineer. */ "Chats" = "Чати"; +/* No comment provided by engineer. */ +"Chats with members" = "Чати з учасниками"; + /* No comment provided by engineer. */ "Check messages every 20 min." = "Перевіряйте повідомлення кожні 20 хв."; @@ -1012,6 +1134,12 @@ set passcode view */ /* No comment provided by engineer. */ "Clear conversation?" = "Відверта розмова?"; +/* No comment provided by engineer. */ +"Clear group?" = "Очистити групу?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "Очистити чи видалити групу?"; + /* No comment provided by engineer. */ "Clear private notes?" = "Чисті приватні нотатки?"; @@ -1027,6 +1155,9 @@ set passcode view */ /* No comment provided by engineer. */ "colored" = "кольоровий"; +/* report reason */ +"Community guidelines violation" = "Порушення правил спільноти"; + /* server test step */ "Compare file" = "Порівняти файл"; @@ -1063,6 +1194,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Налаштування серверів ICE"; +/* No comment provided by engineer. */ +"Configure server operators" = "Налаштувати операторів сервера"; + /* No comment provided by engineer. */ "Confirm" = "Підтвердити"; @@ -1093,6 +1227,9 @@ set passcode view */ /* No comment provided by engineer. */ "Confirm upload" = "Підтвердити завантаження"; +/* token status text */ +"Confirmed" = "Підтверджений"; + /* server test step */ "Connect" = "Підключіться"; @@ -1100,7 +1237,7 @@ set passcode view */ "Connect automatically" = "Підключення автоматично"; /* No comment provided by engineer. */ -"Connect incognito" = "Підключайтеся інкогніто"; +"Connect faster! 🚀" = "Підключайтеся швидше! 🚀"; /* No comment provided by engineer. */ "Connect to desktop" = "Підключення до комп'ютера"; @@ -1111,25 +1248,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "Швидше спілкуйтеся з друзями."; -/* No comment provided by engineer. */ -"Connect to yourself?" = "З'єднатися з самим собою?"; +/* new chat sheet title */ +"Connect to yourself?\nThis is your own one-time link!" = "Підключитися до себе?\nЦе ваше власне одноразове посилання!"; -/* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "Підключитися до себе? \nЦе ваше власне одноразове посилання!"; +/* new chat sheet title */ +"Connect to yourself?\nThis is your own SimpleX address!" = "З'єднатися з самим собою?\nЦе ваша власна SimpleX-адреса!"; -/* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own SimpleX address!" = "З'єднатися з самим собою? \nЦе ваша власна SimpleX-адреса!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "Підключіться за контактною адресою"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "Підключіться за посиланням"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "Під'єднатися за одноразовим посиланням"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "Підключитися до %@"; /* No comment provided by engineer. */ @@ -1141,9 +1275,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "Підключений робочий стіл"; -/* rcv group event chat item */ -"connected directly" = "з'єднані безпосередньо"; - /* No comment provided by engineer. */ "Connected servers" = "Підключені сервери"; @@ -1193,6 +1324,9 @@ set passcode view */ "Connection and servers status." = "Стан з'єднання та серверів."; /* No comment provided by engineer. */ +"Connection blocked" = "Підключення заблоковано"; + +/* alert title */ "Connection error" = "Помилка підключення"; /* No comment provided by engineer. */ @@ -1201,19 +1335,28 @@ set passcode view */ /* chat list item title (it should not be shown */ "connection established" = "з'єднання встановлене"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "Підключення заблоковано оператором сервера:\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "Підключення не готове."; + /* No comment provided by engineer. */ "Connection notifications" = "Сповіщення про підключення"; /* No comment provided by engineer. */ "Connection request sent!" = "Запит на підключення відправлено!"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "Підключення вимагає повторного узгодження шифрування."; + /* No comment provided by engineer. */ "Connection security" = "Безпека з'єднання"; /* No comment provided by engineer. */ "Connection terminated" = "З'єднання розірвано"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "Тайм-аут з'єднання"; /* No comment provided by engineer. */ @@ -1234,9 +1377,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact already exists" = "Контакт вже існує"; +/* No comment provided by engineer. */ +"contact deleted" = "контакт видалено"; + /* No comment provided by engineer. */ "Contact deleted!" = "Контакт видалено!"; +/* No comment provided by engineer. */ +"contact disabled" = "контакт вимкнено"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "контакт має шифрування e2e"; @@ -1255,9 +1404,15 @@ set passcode view */ /* No comment provided by engineer. */ "Contact name" = "Ім'я контактної особи"; +/* No comment provided by engineer. */ +"contact not ready" = "контакт не готовий"; + /* No comment provided by engineer. */ "Contact preferences" = "Налаштування контактів"; +/* No comment provided by engineer. */ +"contact should accept…" = "контакт повинен прийняти…"; + /* No comment provided by engineer. */ "Contact will be deleted - this cannot be undone!" = "Контакт буде видалено - це неможливо скасувати!"; @@ -1267,6 +1422,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "Контакти можуть позначати повідомлення для видалення; ви зможете їх переглянути."; +/* blocking reason */ +"Content violates conditions of use" = "Вміст порушує умови використання"; + /* No comment provided by engineer. */ "Continue" = "Продовжуйте"; @@ -1309,6 +1467,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create link" = "Створити посилання"; +/* No comment provided by engineer. */ +"Create list" = "Створити список"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Створіть новий профіль у [desktop app](https://simplex.chat/downloads/). 💻"; @@ -1319,10 +1480,10 @@ set passcode view */ "Create queue" = "Створити чергу"; /* No comment provided by engineer. */ -"Create secret group" = "Створити секретну групу"; +"Create SimpleX address" = "Створіть адресу SimpleX"; /* No comment provided by engineer. */ -"Create SimpleX address" = "Створіть адресу SimpleX"; +"Create your address" = "Створіть свою адресу"; /* No comment provided by engineer. */ "Create your profile" = "Створіть свій профіль"; @@ -1488,12 +1649,18 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat" = "Видалити чат"; +/* No comment provided by engineer. */ +"Delete chat messages from your device." = "Видалити повідомлення чату з вашого пристрою."; + /* No comment provided by engineer. */ "Delete chat profile" = "Видалити профіль чату"; /* No comment provided by engineer. */ "Delete chat profile?" = "Видалити профіль чату?"; +/* alert title */ +"Delete chat with member?" = "Видалити чат з учасником?"; + /* No comment provided by engineer. */ "Delete chat?" = "Видалити чат?"; @@ -1542,6 +1709,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete link?" = "Видалити посилання?"; +/* alert title */ +"Delete list?" = "Видалити список?"; + /* No comment provided by engineer. */ "Delete member message?" = "Видалити повідомлення учасника?"; @@ -1572,6 +1742,9 @@ swipe action */ /* server test step */ "Delete queue" = "Видалити чергу"; +/* No comment provided by engineer. */ +"Delete report" = "Видалити скаргу"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Видаляйте до 20 повідомлень одночасно."; @@ -1617,6 +1790,9 @@ swipe action */ /* No comment provided by engineer. */ "Description" = "Опис"; +/* alert title */ +"Description too large" = "Опис занадто великий"; + /* No comment provided by engineer. */ "Desktop address" = "Адреса робочого столу"; @@ -1680,6 +1856,12 @@ swipe action */ /* No comment provided by engineer. */ "Disable (keep overrides)" = "Вимкнути (зберегти перевизначення)"; +/* alert title */ +"Disable automatic message deletion?" = "Вимкнути автоматичне видалення повідомлень?"; + +/* alert button */ +"Disable delete messages" = "Вимкнути видалення повідомлень"; + /* No comment provided by engineer. */ "Disable for all" = "Вимкнути для всіх"; @@ -1740,6 +1922,9 @@ swipe action */ /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "НЕ використовуйте SimpleX для екстрених викликів."; +/* No comment provided by engineer. */ +"Documents:" = "Документи:"; + /* No comment provided by engineer. */ "Don't create address" = "Не створювати адресу"; @@ -1747,8 +1932,14 @@ swipe action */ "Don't enable" = "Не вмикати"; /* No comment provided by engineer. */ +"Don't miss important messages." = "Не пропускайте важливі повідомлення."; + +/* alert action */ "Don't show again" = "Більше не показувати"; +/* No comment provided by engineer. */ +"Done" = "Готово"; + /* No comment provided by engineer. */ "Downgrade and open chat" = "Пониження та відкритий чат"; @@ -1804,6 +1995,9 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Редагування профілю групи"; +/* No comment provided by engineer. */ +"Empty message!" = "Порожнє повідомлення!"; + /* No comment provided by engineer. */ "Enable" = "Увімкнути"; @@ -1816,6 +2010,12 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Увімкніть доступ до камери"; +/* No comment provided by engineer. */ +"Enable disappearing messages by default." = "Увімкнути зникаючі повідомлення за замовчуванням."; + +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Увімкніть Flux у налаштуваннях мережі та серверів для кращої конфіденційності метаданих."; + /* No comment provided by engineer. */ "Enable for all" = "Увімкнути для всіх"; @@ -1927,6 +2127,9 @@ chat item action */ /* chat item text */ "encryption re-negotiation required for %@" = "для %@ потрібне повторне узгодження шифрування"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "Виконується повторне узгодження шифрування."; + /* No comment provided by engineer. */ "ended" = "закінчився"; @@ -1981,28 +2184,40 @@ chat item action */ /* No comment provided by engineer. */ "Error accepting contact request" = "Помилка при прийнятті запиту на контакт"; +/* alert title */ +"Error accepting member" = "Помилка при прийомі учасника"; + /* No comment provided by engineer. */ "Error adding member(s)" = "Помилка додавання користувача(ів)"; /* alert title */ "Error adding server" = "Помилка додавання сервера"; +/* No comment provided by engineer. */ +"Error adding short link" = "Помилка додавання короткого посилання"; + /* No comment provided by engineer. */ "Error changing address" = "Помилка зміни адреси"; +/* alert title */ +"Error changing chat profile" = "Помилка зміни профілю чату"; + /* No comment provided by engineer. */ "Error changing connection profile" = "Помилка при зміні профілю з'єднання"; /* No comment provided by engineer. */ "Error changing role" = "Помилка зміни ролі"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "Помилка зміни налаштування"; /* No comment provided by engineer. */ "Error changing to incognito!" = "Помилка переходу на інкогніто!"; /* No comment provided by engineer. */ +"Error checking token status" = "Помилка перевірки статусу токена"; + +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "Помилка підключення до сервера переадресації %@. Спробуйте пізніше."; /* No comment provided by engineer. */ @@ -2014,6 +2229,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating group link" = "Помилка створення посилання на групу"; +/* alert title */ +"Error creating list" = "Помилка при створенні списку"; + /* No comment provided by engineer. */ "Error creating member contact" = "Помилка при створенні контакту користувача"; @@ -2023,22 +2241,28 @@ chat item action */ /* No comment provided by engineer. */ "Error creating profile!" = "Помилка створення профілю!"; +/* No comment provided by engineer. */ +"Error creating report" = "Помилка при створенні скарги"; + /* No comment provided by engineer. */ "Error decrypting file" = "Помилка розшифрування файлу"; -/* No comment provided by engineer. */ +/* alert title */ +"Error deleting chat" = "Помилка при видаленні чату з учасником"; + +/* alert title */ "Error deleting chat database" = "Помилка видалення бази даних чату"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "Помилка видалення чату!"; /* No comment provided by engineer. */ "Error deleting connection" = "Помилка видалення з'єднання"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "Помилка видалення бази даних"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "Помилка видалення старої бази даних"; /* No comment provided by engineer. */ @@ -2059,13 +2283,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "Помилка шифрування бази даних"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "Помилка експорту бази даних чату"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "Помилка експорту теми: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "Помилка імпорту бази даних чату"; /* No comment provided by engineer. */ @@ -2080,6 +2304,9 @@ chat item action */ /* No comment provided by engineer. */ "Error opening chat" = "Помилка відкриття чату"; +/* No comment provided by engineer. */ +"Error opening group" = "Помилка відкриття групи"; + /* alert title */ "Error receiving file" = "Помилка отримання файлу"; @@ -2089,12 +2316,24 @@ chat item action */ /* No comment provided by engineer. */ "Error reconnecting servers" = "Помилка перепідключення серверів"; -/* No comment provided by engineer. */ +/* alert title */ +"Error registering for notifications" = "Помилка під час реєстрації для отримання сповіщень"; + +/* alert title */ +"Error rejecting contact request" = "Помилка відхилення запиту на контакт"; + +/* alert title */ "Error removing member" = "Помилка видалення учасника"; +/* alert title */ +"Error reordering lists" = "Помилка при переупорядкуванні списків"; + /* No comment provided by engineer. */ "Error resetting statistics" = "Статистика скидання помилок"; +/* alert title */ +"Error saving chat list" = "Помилка під час збереження списку чатів"; + /* No comment provided by engineer. */ "Error saving group profile" = "Помилка збереження профілю групи"; @@ -2137,7 +2376,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "Помилка зупинки чату"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "Помилка перемикання профілю"; /* alertTitle */ @@ -2146,6 +2385,9 @@ chat item action */ /* No comment provided by engineer. */ "Error synchronizing connection" = "Помилка синхронізації з'єднання"; +/* No comment provided by engineer. */ +"Error testing server connection" = "Помилка під час перевірки з'єднання з сервером"; + /* No comment provided by engineer. */ "Error updating group link" = "Помилка оновлення посилання на групу"; @@ -2199,6 +2441,9 @@ snd error text */ /* No comment provided by engineer. */ "expired" = "закінчився"; +/* token status text */ +"Expired" = "Термін дії закінчився"; + /* No comment provided by engineer. */ "Export database" = "Експорт бази даних"; @@ -2223,18 +2468,30 @@ snd error text */ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "Швидко і без очікування, поки відправник буде онлайн!"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "Швидше видалення груп."; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "Швидше приєднання та надійніші повідомлення."; +/* No comment provided by engineer. */ +"Faster sending messages." = "Швидше надсилання повідомлень."; + /* swipe action */ "Favorite" = "Улюблений"; +/* No comment provided by engineer. */ +"Favorites" = "Вибране"; + /* file error alert title */ "File error" = "Помилка файлу"; /* alert message */ "File errors:\n%@" = "Помилки файлів:\n%@"; +/* file error text */ +"File is blocked by server operator:\n%@." = "Файл заблоковано оператором сервера:\n%@."; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Файл не знайдено - найімовірніше, файл було видалено або скасовано."; @@ -2292,6 +2549,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "Швидше знаходьте чати"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "Можливо, в адресі сервера неправильно вказано відбиток сертифіката"; + /* No comment provided by engineer. */ "Fix" = "Виправити"; @@ -2310,6 +2570,9 @@ snd error text */ /* No comment provided by engineer. */ "Fix not supported by group member" = "Виправлення не підтримується учасником групи"; +/* No comment provided by engineer. */ +"For all moderators" = "Для всіх модераторів"; + /* servers error */ "For chat profile %@:" = "Для профілю чату %@:"; @@ -2319,6 +2582,9 @@ snd error text */ /* No comment provided by engineer. */ "For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Наприклад, якщо ваш контакт отримує повідомлення через сервер SimpleX Chat, ваш додаток доставлятиме їх через сервер Flux."; +/* No comment provided by engineer. */ +"For me" = "Для мене"; + /* No comment provided by engineer. */ "For private routing" = "Для приватної маршрутизації"; @@ -2355,8 +2621,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "Пересилання повідомлень %lld"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше."; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Серверу переадресації %1$@ не вдалося з'єднатися з сервером призначення %2$@. Спробуйте пізніше."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "Адреса сервера переадресації несумісна з налаштуваннями мережі: %@."; @@ -2391,6 +2657,9 @@ snd error text */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Подальше зменшення використання акумулятора"; +/* No comment provided by engineer. */ +"Get notified when mentioned." = "Отримуйте сповіщення, коли вас згадують."; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIF-файли та наклейки"; @@ -2400,13 +2669,16 @@ snd error text */ /* message preview */ "Good morning!" = "Доброго ранку!"; +/* shown on group welcome message */ +"group" = "група"; + /* No comment provided by engineer. */ "Group" = "Група"; /* No comment provided by engineer. */ "Group already exists" = "Група вже існує"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "Група вже існує!"; /* No comment provided by engineer. */ @@ -2430,6 +2702,9 @@ snd error text */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Групове запрошення більше не дійсне, воно було видалено відправником."; +/* No comment provided by engineer. */ +"group is deleted" = "групу видалено"; + /* No comment provided by engineer. */ "Group link" = "Посилання на групу"; @@ -2454,6 +2729,9 @@ snd error text */ /* snd group event chat item */ "group profile updated" = "оновлено профіль групи"; +/* alert message */ +"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Профіль групи було змінено. Якщо ви збережете його, оновлений профіль буде надіслано учасникам групи."; + /* No comment provided by engineer. */ "Group welcome message" = "Привітальне повідомлення групи"; @@ -2463,9 +2741,15 @@ snd error text */ /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "Група буде видалена для вас - це не може бути скасовано!"; +/* No comment provided by engineer. */ +"Groups" = "Групи"; + /* No comment provided by engineer. */ "Help" = "Довідка"; +/* No comment provided by engineer. */ +"Help admins moderating their groups." = "Допоможіть адміністраторам модерувати їхні групи."; + /* No comment provided by engineer. */ "Hidden" = "Приховано"; @@ -2502,6 +2786,9 @@ snd error text */ /* No comment provided by engineer. */ "How it helps privacy" = "Як це захищає приватність"; +/* alert button */ +"How it works" = "Як це працює"; + /* No comment provided by engineer. */ "How SimpleX works" = "Як працює SimpleX"; @@ -2589,6 +2876,12 @@ snd error text */ /* No comment provided by engineer. */ "inactive" = "неактивний"; +/* report reason */ +"Inappropriate content" = "Невідповідний вміст"; + +/* report reason */ +"Inappropriate profile" = "Невідповідний профіль"; + /* No comment provided by engineer. */ "Incognito" = "Інкогніто"; @@ -2655,6 +2948,21 @@ snd error text */ /* No comment provided by engineer. */ "Interface colors" = "Кольори інтерфейсу"; +/* token status text */ +"Invalid" = "Недійсний"; + +/* token status text */ +"Invalid (bad token)" = "Недійсний (неправильний токен)"; + +/* token status text */ +"Invalid (expired)" = "Недійсний (термін дії закінчився)"; + +/* token status text */ +"Invalid (unregistered)" = "Недійсний (незареєстрований)"; + +/* token status text */ +"Invalid (wrong topic)" = "Недійсний (неправильна тема)"; + /* invalid chat data */ "invalid chat" = "недійсний чат"; @@ -2670,7 +2978,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "Неправильне ім'я користувача!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "Невірне посилання"; /* No comment provided by engineer. */ @@ -2770,24 +3078,18 @@ snd error text */ "Join" = "Приєднуйтесь"; /* No comment provided by engineer. */ -"join as %@" = "приєднатися як %@"; +"Join as %@" = "приєднатися як %@"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "Приєднуйтесь до групи"; /* No comment provided by engineer. */ "Join group conversations" = "Приєднуйтесь до групових розмов"; -/* No comment provided by engineer. */ -"Join group?" = "Приєднатися до групи?"; - /* No comment provided by engineer. */ "Join incognito" = "Приєднуйтесь інкогніто"; -/* No comment provided by engineer. */ -"Join with current profile" = "Приєднатися з поточним профілем"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "Приєднатися до групи?\nЦе ваше посилання на групу %@!"; /* No comment provided by engineer. */ @@ -2805,6 +3107,9 @@ snd error text */ /* alert title */ "Keep unused invitation?" = "Зберігати невикористані запрошення?"; +/* No comment provided by engineer. */ +"Keep your chats clean" = "Підтримуйте чистоту в чатах"; + /* No comment provided by engineer. */ "Keep your connections" = "Зберігайте свої зв'язки"; @@ -2838,6 +3143,9 @@ snd error text */ /* rcv group event chat item */ "left" = "ліворуч"; +/* No comment provided by engineer. */ +"Less traffic on mobile networks." = "Менше трафіку в мобільних мережах."; + /* email subject */ "Let's talk in SimpleX Chat" = "Поговоримо в чаті SimpleX"; @@ -2856,6 +3164,15 @@ snd error text */ /* No comment provided by engineer. */ "Linked desktops" = "Пов'язані робочі столи"; +/* swipe action */ +"List" = "Список"; + +/* No comment provided by engineer. */ +"List name and emoji should be different for all lists." = "Назва списку та емодзі повинні бути різними для всіх списків."; + +/* No comment provided by engineer. */ +"List name..." = "Ім'я в списку..."; + /* No comment provided by engineer. */ "LIVE" = "НАЖИВО"; @@ -2865,6 +3182,9 @@ snd error text */ /* No comment provided by engineer. */ "Live messages" = "Живі повідомлення"; +/* in progress text */ +"Loading profile…" = "Завантаження профілю…"; + /* No comment provided by engineer. */ "Local name" = "Місцева назва"; @@ -2919,12 +3239,21 @@ snd error text */ /* profile update event chat item */ "member %@ changed to %@" = "учасника %1$@ змінено на %2$@"; +/* No comment provided by engineer. */ +"Member admission" = "Прийом членів"; + /* rcv group event chat item */ "member connected" = "з'єднаний"; +/* No comment provided by engineer. */ +"member has old version" = "учасник використовує застарілу версію"; + /* item status text */ "Member inactive" = "Користувач неактивний"; +/* chat feature */ +"Member reports" = "Повідомлення учасників"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "Роль учасника буде змінено на \"%@\". Усі учасники чату отримають сповіщення."; @@ -2940,12 +3269,18 @@ snd error text */ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Учасник буде видалений з групи - це неможливо скасувати!"; +/* alert message */ +"Member will join the group, accept member?" = "Учасник приєднається до групи, прийняти учасника?"; + /* No comment provided by engineer. */ "Members can add message reactions." = "Учасники групи можуть додавати реакції на повідомлення."; /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години)"; +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "Учасники можуть повідомляти повідомлення модераторам."; + /* No comment provided by engineer. */ "Members can send direct messages." = "Учасники групи можуть надсилати прямі повідомлення."; @@ -2961,6 +3296,9 @@ snd error text */ /* No comment provided by engineer. */ "Members can send voice messages." = "Учасники групи можуть надсилати голосові повідомлення."; +/* No comment provided by engineer. */ +"Mention members 👋" = "Згадуйте учасників 👋"; + /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -2982,6 +3320,9 @@ snd error text */ /* item status text */ "Message forwarded" = "Повідомлення переслано"; +/* No comment provided by engineer. */ +"Message instantly once you tap Connect." = "Миттєве повідомлення, щойно ви натиснете \"Підключитися\"."; + /* item status description */ "Message may be delivered later if member becomes active." = "Повідомлення може бути доставлене пізніше, якщо користувач стане активним."; @@ -3030,9 +3371,15 @@ snd error text */ /* No comment provided by engineer. */ "Messages & files" = "Повідомлення та файли"; +/* No comment provided by engineer. */ +"Messages are protected by **end-to-end encryption**." = "Повідомлення захищені **наскрізним шифруванням**."; + /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Повідомлення від %@ будуть показані!"; +/* alert message */ +"Messages in this chat will never be deleted." = "Повідомлення в цьому чаті ніколи не будуть видалені."; + /* No comment provided by engineer. */ "Messages received" = "Отримані повідомлення"; @@ -3105,9 +3452,15 @@ snd error text */ /* marked deleted chat item preview text */ "moderated by %@" = "модерується %@"; +/* member role */ +"moderator" = "модератор"; + /* time unit */ "months" = "місяців"; +/* swipe action */ +"More" = "Більше"; + /* No comment provided by engineer. */ "More improvements are coming soon!" = "Незабаром буде ще більше покращень!"; @@ -3126,6 +3479,9 @@ snd error text */ /* notification label action */ "Mute" = "Вимкнути звук"; +/* notification label action */ +"Mute all" = "Вимкнути звук для всіх"; + /* No comment provided by engineer. */ "Muted when inactive!" = "Вимкнено, коли неактивний!"; @@ -3159,6 +3515,9 @@ snd error text */ /* delete after time */ "never" = "ніколи"; +/* token status text */ +"New" = "Новий"; + /* No comment provided by engineer. */ "New chat" = "Новий чат"; @@ -3180,6 +3539,9 @@ snd error text */ /* notification */ "New events" = "Нові події"; +/* No comment provided by engineer. */ +"New group role: Moderator" = "Нова роль у групі: Модератор"; + /* No comment provided by engineer. */ "New in %@" = "Нове в %@"; @@ -3189,6 +3551,9 @@ snd error text */ /* No comment provided by engineer. */ "New member role" = "Нова роль учасника"; +/* rcv group event chat item */ +"New member wants to join the group." = "Новий учасник хоче приєднатися до групи."; + /* notification */ "new message" = "нове повідомлення"; @@ -3219,6 +3584,18 @@ snd error text */ /* Authentication unavailable */ "No app password" = "Немає пароля програми"; +/* No comment provided by engineer. */ +"No chats" = "Без чатів"; + +/* No comment provided by engineer. */ +"No chats found" = "Чати не знайдено"; + +/* No comment provided by engineer. */ +"No chats in list %@" = "Немає чатів у списку %@"; + +/* No comment provided by engineer. */ +"No chats with members" = "Ніяких чатів з учасниками"; + /* No comment provided by engineer. */ "No contacts selected" = "Не вибрано жодного контакту"; @@ -3252,6 +3629,9 @@ snd error text */ /* servers error */ "No media & file servers." = "Ніяких медіа та файлових серверів."; +/* No comment provided by engineer. */ +"No message" = "Немає повідомлення"; + /* servers error */ "No message servers." = "Ніяких серверів повідомлень."; @@ -3267,6 +3647,9 @@ snd error text */ /* No comment provided by engineer. */ "No permission to record voice message" = "Немає дозволу на запис голосового повідомлення"; +/* alert title */ +"No private routing session" = "Немає приватного сеансу маршрутизації"; + /* No comment provided by engineer. */ "No push server" = "Локально"; @@ -3288,12 +3671,24 @@ snd error text */ /* copied message info in history */ "no text" = "без тексту"; +/* alert title */ +"No token!" = "Немає токена!"; + +/* No comment provided by engineer. */ +"No unread chats" = "Немає непрочитаних чатів"; + /* No comment provided by engineer. */ "No user identifiers." = "Ніяких ідентифікаторів користувачів."; /* No comment provided by engineer. */ "Not compatible!" = "Не сумісні!"; +/* No comment provided by engineer. */ +"not synchronized" = "не синхронізовано"; + +/* No comment provided by engineer. */ +"Notes" = "Нотатки"; + /* No comment provided by engineer. */ "Nothing selected" = "Нічого не вибрано"; @@ -3306,9 +3701,15 @@ snd error text */ /* No comment provided by engineer. */ "Notifications are disabled!" = "Сповіщення вимкнено!"; +/* alert title */ +"Notifications error" = "Помилка сповіщень"; + /* No comment provided by engineer. */ "Notifications privacy" = "Сповіщення про приватність"; +/* alert title */ +"Notifications status" = "Статус сповіщень"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Тепер адміністратори можуть\n- видаляти повідомлення користувачів.\n- відключати користувачів (роль \"спостерігач\")"; @@ -3317,6 +3718,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "вимкнено"; @@ -3329,7 +3731,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "запропонував %1$@: %2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "Гаразд"; /* No comment provided by engineer. */ @@ -3371,6 +3775,12 @@ time to disappear */ /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "Тільки власники груп можуть вмикати голосові повідомлення."; +/* No comment provided by engineer. */ +"Only sender and moderators see it" = "Тільки відправник і модератори бачать це"; + +/* No comment provided by engineer. */ +"Only you and moderators see it" = "Тільки ви та модератори бачать це"; + /* No comment provided by engineer. */ "Only you can add message reactions." = "Тільки ви можете додавати реакції на повідомлення."; @@ -3407,7 +3817,7 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "Відкриті зміни"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "Відкритий чат"; /* authentication reason */ @@ -3416,15 +3826,33 @@ time to disappear */ /* No comment provided by engineer. */ "Open conditions" = "Відкриті умови"; -/* No comment provided by engineer. */ +/* new chat action */ "Open group" = "Відкрита група"; +/* alert title */ +"Open link?" = "Відкрите посилання?"; + /* authentication reason */ "Open migration to another device" = "Відкрита міграція на інший пристрій"; +/* new chat action */ +"Open new chat" = "Відкрити новий чат"; + +/* new chat action */ +"Open new group" = "Відкрити нову групу"; + /* No comment provided by engineer. */ "Open Settings" = "Відкрийте Налаштування"; +/* No comment provided by engineer. */ +"Open to accept" = "Відкрити для прийняття"; + +/* No comment provided by engineer. */ +"Open to connect" = "Відкрито для підключення"; + +/* No comment provided by engineer. */ +"Open to join" = "Відкрито для приєднання"; + /* No comment provided by engineer. */ "Opening app…" = "Відкриваємо програму…"; @@ -3452,6 +3880,9 @@ time to disappear */ /* No comment provided by engineer. */ "Or to share privately" = "Або поділитися приватно"; +/* No comment provided by engineer. */ +"Organize chats into lists" = "Організовуйте чати в списки"; + /* No comment provided by engineer. */ "other" = "інший"; @@ -3491,9 +3922,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "Показати пароль"; -/* past/unknown group member */ -"Past member %@" = "Колишній учасник %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Вставте адресу робочого столу"; @@ -3509,9 +3937,18 @@ time to disappear */ /* No comment provided by engineer. */ "peer-to-peer" = "одноранговий"; +/* No comment provided by engineer. */ +"pending" = "очікує"; + /* No comment provided by engineer. */ "Pending" = "В очікуванні"; +/* No comment provided by engineer. */ +"pending approval" = "очікує на схвалення"; + +/* No comment provided by engineer. */ +"pending review" = "очікує на схвалення"; + /* No comment provided by engineer. */ "Periodic" = "Періодично"; @@ -3542,7 +3979,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Будь ласка, перевірте, чи ви скористалися правильним посиланням, або попросіть контактну особу надіслати вам інше."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "Будь ласка, перевірте підключення до мережі за допомогою %@ і спробуйте ще раз."; /* No comment provided by engineer. */ @@ -3578,15 +4015,24 @@ time to disappear */ /* No comment provided by engineer. */ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Будь ласка, зберігайте пароль надійно, ви НЕ зможете змінити його, якщо втратите."; +/* token info */ +"Please try to disable and re-enable notfications." = "Будь ласка, спробуйте вимкнути та знову увімкнути сповіщення."; + +/* snd group event chat item */ +"Please wait for group moderators to review your request to join the group." = "Будь ласка, зачекайте, поки модератори групи розглянуть ваш запит на приєднання до групи."; + +/* token info */ +"Please wait for token activation to complete." = "Будь ласка, дочекайтеся завершення активації токену."; + +/* token info */ +"Please wait for token to be registered." = "Будь ласка, зачекайте, поки токен буде зареєстровано."; + /* No comment provided by engineer. */ "Polish interface" = "Польський інтерфейс"; /* No comment provided by engineer. */ "Port" = "Порт"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Можливо, в адресі сервера неправильно вказано відбиток сертифіката"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Зберегти чернетку останнього повідомлення з вкладеннями."; @@ -3608,12 +4054,21 @@ time to disappear */ /* No comment provided by engineer. */ "Privacy for your customers." = "Конфіденційність для ваших клієнтів."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Політика конфіденційності та умови використання."; + /* No comment provided by engineer. */ "Privacy redefined" = "Конфіденційність переглянута"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Приватні чати, групи та ваші контакти недоступні для операторів сервера."; + /* No comment provided by engineer. */ "Private filenames" = "Приватні імена файлів"; +/* No comment provided by engineer. */ +"Private media file names." = "Приватні імена медіа-файлів."; + /* No comment provided by engineer. */ "Private message routing" = "Маршрутизація приватних повідомлень"; @@ -3626,9 +4081,12 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "Приватна маршрутизація"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "Помилка приватної маршрутизації"; +/* alert title */ +"Private routing timeout" = "Тайм-аут приватної маршрутизації"; + /* No comment provided by engineer. */ "Profile and server connections" = "З'єднання профілю та сервера"; @@ -3659,6 +4117,9 @@ time to disappear */ /* No comment provided by engineer. */ "Prohibit messages reactions." = "Заборонити реакції на повідомлення."; +/* No comment provided by engineer. */ +"Prohibit reporting messages to moderators." = "Заборонити повідомлення модераторам."; + /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Заборонити надсилати прямі повідомлення учасникам."; @@ -3686,6 +4147,9 @@ time to disappear */ /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Захистіть свою IP-адресу від ретрансляторів повідомлень, обраних вашими контактами.\nУвімкніть у налаштуваннях *Мережа та сервери*."; +/* No comment provided by engineer. */ +"Protocol background timeout" = "Фоновий тайм-аут протоколу"; + /* No comment provided by engineer. */ "Protocol timeout" = "Тайм-аут протоколу"; @@ -3821,16 +4285,32 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "Зменшення використання акумулятора"; -/* reject incoming call via notification +/* No comment provided by engineer. */ +"Register" = "Зареєструватися"; + +/* token info */ +"Register notification token?" = "Зареєструвати токен сповіщення?"; + +/* token status text */ +"Registered" = "Зареєстровано"; + +/* alert action +reject incoming call via notification swipe action */ "Reject" = "Відхилити"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Відхилити (відправника НЕ повідомлено)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "Відхилити запит на контакт"; +/* alert title */ +"Reject member?" = "Відхилити учасника?"; + +/* No comment provided by engineer. */ +"rejected" = "відхилено"; + /* call status */ "rejected call" = "відхилений виклик"; @@ -3867,12 +4347,18 @@ swipe action */ /* profile update event chat item */ "removed contact address" = "видалено контактну адресу"; +/* No comment provided by engineer. */ +"removed from group" = "видалено з групи"; + /* profile update event chat item */ "removed profile picture" = "видалено зображення профілю"; /* rcv group event chat item */ "removed you" = "прибрали вас"; +/* No comment provided by engineer. */ +"Removes messages and blocks members." = "Видаляє повідомлення та блокує користувачів."; + /* No comment provided by engineer. */ "Renegotiate" = "Переузгодьте"; @@ -3882,24 +4368,57 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "Переузгодьте шифрування?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "Повторити запит на підключення?"; - /* No comment provided by engineer. */ "Repeat download" = "Повторити завантаження"; /* No comment provided by engineer. */ "Repeat import" = "Повторний імпорт"; -/* No comment provided by engineer. */ -"Repeat join request?" = "Повторити запит на приєднання?"; - /* No comment provided by engineer. */ "Repeat upload" = "Повторне завантаження"; /* chat item action */ "Reply" = "Відповісти"; +/* chat item action */ +"Report" = "Повідомити"; + +/* report reason */ +"Report content: only group moderators will see it." = "Повідомити про контент: тільки модератори групи побачать це."; + +/* report reason */ +"Report member profile: only group moderators will see it." = "Повідомити про профіль учасника: тільки модератори групи побачать це."; + +/* report reason */ +"Report other: only group moderators will see it." = "Повідомити інше: тільки модератори групи побачать це."; + +/* No comment provided by engineer. */ +"Report reason?" = "Причина повідомлення?"; + +/* alert title */ +"Report sent to moderators" = "Повідомлення надіслано модераторам"; + +/* report reason */ +"Report spam: only group moderators will see it." = "Повідомити про спам: тільки модератори групи побачать це."; + +/* report reason */ +"Report violation: only group moderators will see it." = "Повідомити про порушення: тільки модератори групи побачать це."; + +/* report in notification */ +"Report: %@" = "Повідомити: %@"; + +/* No comment provided by engineer. */ +"Reporting messages to moderators is prohibited." = "Повідомляти про повідомлення модераторам заборонено."; + +/* No comment provided by engineer. */ +"Reports" = "Звіти"; + +/* No comment provided by engineer. */ +"request is sent" = "запит відправлено"; + +/* No comment provided by engineer. */ +"request to join rejected" = "запит на приєднання відхилено"; + /* chat list item title */ "requested to connect" = "запит на підключення"; @@ -3948,15 +4467,30 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "Відновлення помилки бази даних"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "Спробуйте ще раз"; /* chat item action */ "Reveal" = "Показувати"; +/* No comment provided by engineer. */ +"review" = "перегляд"; + /* No comment provided by engineer. */ "Review conditions" = "Умови перегляду"; +/* No comment provided by engineer. */ +"Review group members" = "Учасники групи оглядів"; + +/* admission stage */ +"Review members" = "Схвалювати учасників"; + +/* admission stage description */ +"Review members before admitting (\"knocking\")." = "Перевірка учасників перед тим, як їх прийняти («стукіт»)."; + +/* No comment provided by engineer. */ +"reviewed by admins" = "схвалено адміністраторами"; + /* No comment provided by engineer. */ "Revoke" = "Відкликати"; @@ -3985,6 +4519,12 @@ chat item action */ /* alert button */ "Save (and notify contacts)" = "Зберегти (і повідомити контактам)"; +/* alert button */ +"Save (and notify members)" = "Зберегти (і повідомити учасникам)"; + +/* alert title */ +"Save admission settings?" = "Зберегти налаштування входу?"; + /* alert button */ "Save and notify contact" = "Зберегти та повідомити контакт"; @@ -4000,6 +4540,12 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "Зберегти профіль групи"; +/* alert title */ +"Save group profile?" = "Зберегти профіль групи?"; + +/* No comment provided by engineer. */ +"Save list" = "Зберегти список"; + /* No comment provided by engineer. */ "Save passphrase and open chat" = "Збережіть пароль і відкрийте чат"; @@ -4136,10 +4682,10 @@ chat item action */ "Send a live message - it will update for the recipient(s) as you type it" = "Надішліть повідомлення в реальному часі - воно буде оновлюватися для одержувача (одержувачів), поки ви його вводите"; /* No comment provided by engineer. */ -"Send delivery receipts to" = "Надсилання звітів про доставку"; +"Send contact request?" = "Надіслати запит на контакт?"; /* No comment provided by engineer. */ -"send direct message" = "надіслати пряме повідомлення"; +"Send delivery receipts to" = "Надсилання звітів про доставку"; /* No comment provided by engineer. */ "Send direct message to connect" = "Надішліть пряме повідомлення, щоб підключитися"; @@ -4168,18 +4714,30 @@ chat item action */ /* No comment provided by engineer. */ "Send notifications" = "Надсилати сповіщення"; +/* No comment provided by engineer. */ +"Send private reports" = "Надсилайте приватні звіти"; + /* No comment provided by engineer. */ "Send questions and ideas" = "Надсилайте запитання та ідеї"; /* No comment provided by engineer. */ "Send receipts" = "Надіслати підтвердження"; +/* No comment provided by engineer. */ +"Send request" = "Надіслати запит"; + +/* No comment provided by engineer. */ +"Send request without message" = "Надіслати запит без повідомлення"; + /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Надсилайте їх із галереї чи власних клавіатур."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Надішліть до 100 останніх повідомлень новим користувачам."; +/* No comment provided by engineer. */ +"Send your private feedback to groups." = "Надсилайте свої приватні відгуки до груп."; + /* alert message */ "Sender cancelled file transfer." = "Відправник скасував передачу файлу."; @@ -4268,10 +4826,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "інформація про чергу на сервері: %1$@\n\nостаннє отримане повідомлення: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "Сервер вимагає авторизації для створення черг, перевірте пароль"; +"Server requires authorization to create queues, check password." = "Сервер вимагає авторизації для створення черг, перевірте пароль"; /* server test error */ -"Server requires authorization to upload, check password" = "Сервер вимагає авторизації для завантаження, перевірте пароль"; +"Server requires authorization to upload, check password." = "Сервер вимагає авторизації для завантаження, перевірте пароль"; /* No comment provided by engineer. */ "Server test failed!" = "Тест сервера завершився невдало!"; @@ -4300,6 +4858,9 @@ chat item action */ /* No comment provided by engineer. */ "Set 1 day" = "Встановити 1 день"; +/* No comment provided by engineer. */ +"Set chat name…" = "Назвати чат…"; + /* No comment provided by engineer. */ "Set contact name…" = "Встановити ім'я контакту…"; @@ -4312,6 +4873,12 @@ chat item action */ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Встановіть його замість аутентифікації системи."; +/* No comment provided by engineer. */ +"Set member admission" = "Встановити прийом учасників"; + +/* No comment provided by engineer. */ +"Set message expiration in chats." = "Встановлюйте термін придатності повідомлень у чатах."; + /* profile update event chat item */ "set new contact address" = "встановити нову контактну адресу"; @@ -4327,6 +4894,9 @@ chat item action */ /* No comment provided by engineer. */ "Set passphrase to export" = "Встановити ключову фразу для експорту"; +/* No comment provided by engineer. */ +"Set profile bio and welcome message." = "Налаштуйте біографію профілю та вітальне повідомлення."; + /* No comment provided by engineer. */ "Set the message shown to new members!" = "Налаштуйте повідомлення, яке показуватиметься новим користувачам!"; @@ -4367,6 +4937,12 @@ chat item action */ /* No comment provided by engineer. */ "Share link" = "Поділіться посиланням"; +/* alert button */ +"Share old address" = "Поділіться старою адресою"; + +/* alert button */ +"Share old link" = "Поділіться старим посиланням"; + /* No comment provided by engineer. */ "Share profile" = "Поділіться профілем"; @@ -4382,6 +4958,18 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Поділіться з контактами"; +/* No comment provided by engineer. */ +"Share your address" = "Поділіться своєю адресою"; + +/* No comment provided by engineer. */ +"Short description" = "Короткий опис"; + +/* No comment provided by engineer. */ +"Short link" = "Коротке посилання"; + +/* No comment provided by engineer. */ +"Short SimpleX address" = "Коротка адреса SimpleX"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Показувати → у повідомленнях, надісланих через приватну маршрутизацію."; @@ -4424,6 +5012,12 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX адреса або одноразове посилання?"; +/* alert title */ +"SimpleX address settings" = "Автоприйняття налаштувань"; + +/* simplex link type */ +"SimpleX channel link" = "Посилання на канал SimpleX"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat і Flux уклали угоду про включення серверів, керованих Flux, у додаток."; @@ -4508,6 +5102,10 @@ chat item action */ /* notification title */ "Somebody" = "Хтось"; +/* blocking reason +report reason */ +"Spam" = "Спам"; + /* No comment provided by engineer. */ "Square, circle, or anything in between." = "Квадрат, коло або щось середнє між ними."; @@ -4565,6 +5163,9 @@ chat item action */ /* No comment provided by engineer. */ "Stopping chat" = "Зупинка чату"; +/* No comment provided by engineer. */ +"Storage" = "Зберігання"; + /* No comment provided by engineer. */ "strike" = "закреслено"; @@ -4607,9 +5208,18 @@ chat item action */ /* No comment provided by engineer. */ "Tap button " = "Натисніть кнопку "; +/* No comment provided by engineer. */ +"Tap Connect to chat" = "Натисніть Підключитися до чату"; + +/* No comment provided by engineer. */ +"Tap Connect to send request" = "Натисніть Підключитися, щоб відправити запит"; + /* No comment provided by engineer. */ "Tap Create SimpleX address in the menu to create it later." = "Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше."; +/* No comment provided by engineer. */ +"Tap Join group" = "Натисніть Приєднатися до групи"; + /* No comment provided by engineer. */ "Tap to activate profile." = "Натисніть, щоб активувати профіль."; @@ -4631,9 +5241,15 @@ chat item action */ /* No comment provided by engineer. */ "TCP connection" = "TCP-з'єднання"; +/* No comment provided by engineer. */ +"TCP connection bg timeout" = "Таймаут TCP-з'єднання bg"; + /* No comment provided by engineer. */ "TCP connection timeout" = "Тайм-аут TCP-з'єднання"; +/* No comment provided by engineer. */ +"TCP port for messaging" = "TCP-порт для повідомлень"; + /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -4649,6 +5265,9 @@ chat item action */ /* server test failure */ "Test failed at step %@." = "Тест завершився невдало на кроці %@."; +/* No comment provided by engineer. */ +"Test notifications" = "Тестові сповіщення"; + /* No comment provided by engineer. */ "Test server" = "Тестовий сервер"; @@ -4667,6 +5286,9 @@ chat item action */ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Дякуємо користувачам - зробіть свій внесок через Weblate!"; +/* alert message */ +"The address will be short, and your profile will be shared via the address." = "Адреса буде короткою, і ваш профіль буде доступний за цією адресою."; + /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію."; @@ -4706,6 +5328,9 @@ chat item action */ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Ідентифікатор наступного повідомлення неправильний (менше або дорівнює попередньому).\nЦе може статися через помилку або коли з'єднання скомпрометовано."; +/* alert message */ +"The link will be short, and group profile will be shared via the link." = "Посилання буде коротким, а профіль групи буде поширюватися за посиланням."; + /* No comment provided by engineer. */ "The message will be deleted for all members." = "Повідомлення буде видалено для всіх учасників."; @@ -4721,9 +5346,6 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити."; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам."; - /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; @@ -4733,7 +5355,7 @@ chat item action */ /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Другу галочку ми пропустили! ✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "Відправник НЕ буде повідомлений"; /* No comment provided by engineer. */ @@ -4766,6 +5388,9 @@ chat item action */ /* No comment provided by engineer. */ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Цю дію неможливо скасувати - повідомлення, надіслані та отримані раніше, ніж вибрані, будуть видалені. Це може зайняти кілька хвилин."; +/* alert message */ +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Цю дію не можна скасувати — повідомлення, надіслані та отримані в цьому чаті раніше за обраний час, будуть видалені."; + /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Цю дію неможливо скасувати - ваш профіль, контакти, повідомлення та файли будуть безповоротно втрачені."; @@ -4791,17 +5416,20 @@ chat item action */ "This group no longer exists." = "Цієї групи більше не існує."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Це ваше власне одноразове посилання!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Це ваша власна SimpleX-адреса!"; +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Це посилання вимагає новішої версії додатку. Будь ласка, оновіть додаток або попросіть вашого контакту надіслати сумісне посилання."; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Це посилання було використано з іншого мобільного пристрою, будь ласка, створіть нове посилання на робочому столі."; +/* No comment provided by engineer. */ +"This message was deleted or not received yet." = "Це повідомлення було видалено або ще не отримано."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Це налаштування застосовується до повідомлень у вашому поточному профілі чату **%@**."; +/* No comment provided by engineer. */ +"Time to disappear is set only for new contacts." = "Час зникнення встановлюється тільки для нових контактів."; + /* No comment provided by engineer. */ "Title" = "Заголовок"; @@ -4853,6 +5481,9 @@ chat item action */ /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Для підтримки миттєвих push-повідомлень необхідно перенести базу даних чату."; +/* alert message */ +"To use another profile after connection attempt, delete the chat and use the link again." = "Щоб використовувати інший профіль після спроби з'єднання, видаліть чат і скористайтеся посиланням знову."; + /* No comment provided by engineer. */ "To use the servers of **%@**, accept conditions of use." = "Щоб користуватися серверами **%@**, прийміть умови використання."; @@ -4865,6 +5496,9 @@ chat item action */ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Увімкніть інкогніто при підключенні."; +/* token status */ +"Token status: %@." = "Статус токена: %@."; + /* No comment provided by engineer. */ "Toolbar opacity" = "Непрозорість панелі інструментів"; @@ -4982,6 +5616,9 @@ chat item action */ /* swipe action */ "Unread" = "Непрочитане"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Несумісне посилання для підключення"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Новим користувачам надсилається до 100 останніх повідомлень."; @@ -4997,6 +5634,9 @@ chat item action */ /* No comment provided by engineer. */ "Update settings?" = "Оновити налаштування?"; +/* No comment provided by engineer. */ +"Updated conditions" = "Оновлені умови"; + /* rcv group event chat item */ "updated group profile" = "оновлений профіль групи"; @@ -5006,9 +5646,27 @@ chat item action */ /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Оновлення налаштувань призведе до перепідключення клієнта до всіх серверів."; +/* alert button */ +"Upgrade" = "Оновлення"; + +/* No comment provided by engineer. */ +"Upgrade address" = "Адреса оновлення"; + +/* alert message */ +"Upgrade address?" = "Змінити адресу?"; + /* No comment provided by engineer. */ "Upgrade and open chat" = "Оновлення та відкритий чат"; +/* alert message */ +"Upgrade group link?" = "Оновити посилання на групу?"; + +/* No comment provided by engineer. */ +"Upgrade link" = "Посилання для оновлення"; + +/* No comment provided by engineer. */ +"Upgrade your address" = "Поновіть свою адресу"; + /* No comment provided by engineer. */ "Upload errors" = "Помилки завантаження"; @@ -5036,7 +5694,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "Використовуйте чат"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "Використовувати поточний профіль"; /* No comment provided by engineer. */ @@ -5052,9 +5710,12 @@ chat item action */ "Use from desktop" = "Використання з робочого столу"; /* No comment provided by engineer. */ -"Use iOS call interface" = "Використовуйте інтерфейс виклику iOS"; +"Use incognito profile" = "Використовуйте профіль інкогніто"; /* No comment provided by engineer. */ +"Use iOS call interface" = "Використовуйте інтерфейс виклику iOS"; + +/* new chat action */ "Use new incognito profile" = "Використовуйте новий профіль інкогніто"; /* No comment provided by engineer. */ @@ -5078,12 +5739,21 @@ chat item action */ /* No comment provided by engineer. */ "Use SOCKS proxy" = "Використовуйте SOCKS проксі"; +/* No comment provided by engineer. */ +"Use TCP port %@ when no port is specified." = "Використовуйте TCP-порт %@, якщо порт не вказано."; + +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Використовуйте TCP порт 443 лише для попередньо налаштованих серверів."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Використовуйте додаток під час розмови."; /* No comment provided by engineer. */ "Use the app with one hand." = "Використовуйте додаток однією рукою."; +/* No comment provided by engineer. */ +"Use web port" = "Використовувати веб-порт"; + /* No comment provided by engineer. */ "User selection" = "Вибір користувача"; @@ -5234,6 +5904,9 @@ chat item action */ /* No comment provided by engineer. */ "Welcome message is too long" = "Привітальне повідомлення занадто довге"; +/* No comment provided by engineer. */ +"Welcome your contacts 👋" = "Вітаємо ваші контакти 👋"; + /* No comment provided by engineer. */ "What's new" = "Що нового"; @@ -5303,6 +5976,9 @@ chat item action */ /* No comment provided by engineer. */ "You accepted connection" = "Ви прийняли підключення"; +/* snd group event chat item */ +"you accepted this member" = "ви прийняли цього учасника"; + /* No comment provided by engineer. */ "You allow" = "Ви дозволяєте"; @@ -5315,33 +5991,27 @@ chat item action */ /* No comment provided by engineer. */ "You are already connected with %@." = "Ви вже підключені до %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "Ви вже з'єднані з %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "Ви вже підключаєтеся до %@.Ви вже підключаєтеся за цим одноразовим посиланням!"; /* No comment provided by engineer. */ "You are already in group %@." = "Ви вже перебуваєте в групі %@."; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "Ви вже приєдналися до групи %@."; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "Ви вже приєдналися до групи за цим посиланням!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "Ви вже приєдналися до групи за цим посиланням."; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "Ви вже приєдналися до групи!\nПовторити запит на приєднання?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Ви підключені до сервера, який використовується для отримання повідомлень від цього контакту."; -/* No comment provided by engineer. */ -"you are invited to group" = "вас запрошують до групи"; - /* No comment provided by engineer. */ "You are invited to group" = "Запрошуємо вас до групи"; @@ -5414,7 +6084,10 @@ chat item action */ /* alert message */ "You can view invitation link again in connection details." = "Ви можете переглянути посилання на запрошення ще раз у деталях підключення."; -/* No comment provided by engineer. */ +/* alert message */ +"You can view your reports in Chat with admins." = "Ви можете переглянути свої звіти у чаті з адміністраторами."; + +/* alert title */ "You can't send messages!" = "Ви не можете надсилати повідомлення!"; /* chat item text */ @@ -5435,10 +6108,7 @@ chat item action */ /* No comment provided by engineer. */ "You decide who can connect." = "Ви вирішуєте, хто може під'єднатися."; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Ви вже надсилали запит на підключення за цією адресою!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ви вже надіслали запит на підключення!\nПовторити запит на підключення?"; /* No comment provided by engineer. */ @@ -5486,9 +6156,15 @@ chat item action */ /* chat list item description */ "you shared one-time link incognito" = "ви поділилися одноразовим посиланням інкогніто"; +/* token info */ +"You should receive notifications." = "Ви повинні отримувати сповіщення."; + /* snd group event chat item */ "you unblocked %@" = "ви розблокували %@"; +/* No comment provided by engineer. */ +"You will be able to send messages **only after your request is accepted**." = "Ви зможете надсилати повідомлення **тільки після того, як ваш запит буде прийнято**."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Ви будете підключені до групи, коли пристрій господаря групи буде в мережі, будь ласка, зачекайте або перевірте пізніше!"; @@ -5504,9 +6180,6 @@ chat item action */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вам потрібно буде пройти автентифікацію при запуску або відновленні програми після 30 секунд роботи у фоновому режимі."; -/* No comment provided by engineer. */ -"You will connect to all group members." = "Ви з'єднаєтеся з усіма учасниками групи."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Ви все одно отримуватимете дзвінки та сповіщення від вимкнених профілів, якщо вони активні."; @@ -5528,6 +6201,9 @@ chat item action */ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Ви використовуєте профіль інкогніто для цієї групи - щоб запобігти поширенню вашого основного профілю, запрошення контактів заборонено"; +/* No comment provided by engineer. */ +"Your business contact" = "Ваш діловий контакт"; + /* No comment provided by engineer. */ "Your calls" = "Твої дзвінки"; @@ -5543,8 +6219,14 @@ chat item action */ /* No comment provided by engineer. */ "Your chat profiles" = "Ваші профілі чату"; +/* alert message */ +"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ваш чат було переміщено на %@, але при перенаправленні на профіль сталася несподівана помилка."; + /* No comment provided by engineer. */ -"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ваше з'єднання було переміщено на %@, але під час перенаправлення на профіль сталася несподівана помилка."; +"Your connection was moved to %@ but an error happened when switching profile." = "Ваше з'єднання було переміщено на %@, але під час перенаправлення на профіль сталася несподівана помилка."; + +/* No comment provided by engineer. */ +"Your contact" = "Ваш контакт"; /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ваш контакт надіслав файл, розмір якого перевищує підтримуваний на цей момент максимальний розмір (%@)."; @@ -5564,6 +6246,9 @@ chat item action */ /* No comment provided by engineer. */ "Your current profile" = "Ваш поточний профіль"; +/* No comment provided by engineer. */ +"Your group" = "Ваша група"; + /* No comment provided by engineer. */ "Your ICE servers" = "Ваші сервери ICE"; @@ -5579,15 +6264,15 @@ chat item action */ /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Ваш профіль **%@** буде опублікований."; +/* No comment provided by engineer. */ +"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль."; /* alert message */ "Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам."; -/* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої."; - /* No comment provided by engineer. */ "Your random profile" = "Ваш випадковий профіль"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index e3f9669d9f..2803b374f7 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -342,6 +342,7 @@ time interval */ /* accept contact request via notification accept incoming call via notification +alert action swipe action */ "Accept" = "接受"; @@ -354,7 +355,7 @@ swipe action */ /* notification body */ "Accept contact request from %@?" = "接受来自 %@ 的联系人请求?"; -/* accept contact request via notification +/* alert action swipe action */ "Accept incognito" = "接受隐身聊天"; @@ -586,10 +587,10 @@ swipe action */ /* No comment provided by engineer. */ "Already connected?" = "已连接?"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already connecting!" = "已经在连接了!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Already joining the group!" = "已经加入了该群组!"; /* pref value */ @@ -730,9 +731,6 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "自动接受图片"; -/* alert title */ -"Auto-accept settings" = "自动接受设置"; - /* No comment provided by engineer. */ "Back" = "返回"; @@ -900,7 +898,8 @@ marked deleted chat item preview text */ "Can't message member" = "无法向成员发送消息"; /* alert action -alert button */ +alert button +new chat action */ "Cancel" = "取消"; /* No comment provided by engineer. */ @@ -982,7 +981,7 @@ set passcode view */ /* No comment provided by engineer. */ "Chat already exists" = "聊天已存在"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Chat already exists!" = "聊天已存在!"; /* No comment provided by engineer. */ @@ -1180,9 +1179,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connect automatically" = "自动连接"; -/* No comment provided by engineer. */ -"Connect incognito" = "在隐身状态下连接"; - /* No comment provided by engineer. */ "Connect to desktop" = "连接到桌面"; @@ -1192,25 +1188,22 @@ set passcode view */ /* No comment provided by engineer. */ "Connect to your friends faster." = "更快地与您的朋友联系。"; -/* No comment provided by engineer. */ -"Connect to yourself?" = "连接到你自己?"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own one-time link!" = "与自己建立联系?\n这是您自己的一次性链接!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect to yourself?\nThis is your own SimpleX address!" = "与自己建立联系?\n这是您自己的 SimpleX 地址!"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via contact address" = "通过联系地址连接"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via link" = "通过链接连接"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Connect via one-time link" = "通过一次性链接连接"; -/* No comment provided by engineer. */ +/* new chat action */ "Connect with %@" = "与 %@连接"; /* No comment provided by engineer. */ @@ -1222,9 +1215,6 @@ set passcode view */ /* No comment provided by engineer. */ "Connected desktop" = "已连接的桌面"; -/* rcv group event chat item */ -"connected directly" = "已直连"; - /* No comment provided by engineer. */ "Connected servers" = "已连接的服务器"; @@ -1276,7 +1266,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection blocked" = "连接被阻止"; -/* No comment provided by engineer. */ +/* alert title */ "Connection error" = "连接错误"; /* No comment provided by engineer. */ @@ -1306,7 +1296,7 @@ set passcode view */ /* No comment provided by engineer. */ "Connection terminated" = "连接被终止"; -/* No comment provided by engineer. */ +/* alert title */ "Connection timeout" = "连接超时"; /* No comment provided by engineer. */ @@ -1417,9 +1407,6 @@ set passcode view */ /* server test step */ "Create queue" = "创建队列"; -/* No comment provided by engineer. */ -"Create secret group" = "创建私密群组"; - /* No comment provided by engineer. */ "Create SimpleX address" = "创建 SimpleX 地址"; @@ -1866,7 +1853,7 @@ swipe action */ /* No comment provided by engineer. */ "Don't miss important messages." = "不错过重要消息。"; -/* No comment provided by engineer. */ +/* alert action */ "Don't show again" = "不再显示"; /* No comment provided by engineer. */ @@ -2125,13 +2112,13 @@ chat item action */ /* No comment provided by engineer. */ "Error changing role" = "更改角色错误"; -/* No comment provided by engineer. */ +/* alert title */ "Error changing setting" = "更改设置错误"; /* No comment provided by engineer. */ "Error changing to incognito!" = "切换至隐身聊天出错!"; -/* No comment provided by engineer. */ +/* alert message */ "Error connecting to forwarding server %@. Please try later." = "连接到转发服务器 %@ 时出错。请稍后尝试。"; /* No comment provided by engineer. */ @@ -2161,19 +2148,19 @@ chat item action */ /* No comment provided by engineer. */ "Error decrypting file" = "解密文件时出错"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat database" = "删除聊天数据库错误"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting chat!" = "删除聊天错误!"; /* No comment provided by engineer. */ "Error deleting connection" = "删除连接错误"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting database" = "删除数据库错误"; -/* No comment provided by engineer. */ +/* alert title */ "Error deleting old database" = "删除旧数据库错误"; /* No comment provided by engineer. */ @@ -2194,13 +2181,13 @@ chat item action */ /* No comment provided by engineer. */ "Error encrypting database" = "加密数据库错误"; -/* No comment provided by engineer. */ +/* alert title */ "Error exporting chat database" = "导出聊天数据库错误"; /* No comment provided by engineer. */ "Error exporting theme: %@" = "导出主题时出错: %@"; -/* No comment provided by engineer. */ +/* alert title */ "Error importing chat database" = "导入聊天数据库错误"; /* No comment provided by engineer. */ @@ -2227,7 +2214,7 @@ chat item action */ /* alert title */ "Error registering for notifications" = "注册消息推送出错"; -/* No comment provided by engineer. */ +/* alert title */ "Error removing member" = "删除成员错误"; /* alert title */ @@ -2281,7 +2268,7 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "停止聊天错误"; -/* No comment provided by engineer. */ +/* alert title */ "Error switching profile" = "切换配置文件出错"; /* alertTitle */ @@ -2454,6 +2441,9 @@ snd error text */ /* No comment provided by engineer. */ "Find chats faster" = "更快地查找聊天记录"; +/* server test error */ +"Fingerprint in server address does not match certificate." = "服务器地址中的证书指纹可能不正确"; + /* No comment provided by engineer. */ "Fix" = "修复"; @@ -2523,8 +2513,8 @@ snd error text */ /* No comment provided by engineer. */ "Forwarding %lld messages" = "正在转发 %lld 条消息"; -/* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。"; +/* alert message */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "转发服务器 %1$@ 无法连接到目标服务器 %2$@。请稍后尝试。"; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "转发服务器地址与网络设置不兼容:%@。"; @@ -2577,7 +2567,7 @@ snd error text */ /* No comment provided by engineer. */ "Group already exists" = "群组已存在"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Group already exists!" = "群已存在!"; /* No comment provided by engineer. */ @@ -2871,7 +2861,7 @@ snd error text */ /* No comment provided by engineer. */ "Invalid display name!" = "无效的显示名!"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid link" = "无效链接"; /* No comment provided by engineer. */ @@ -2971,24 +2961,18 @@ snd error text */ "Join" = "加入"; /* No comment provided by engineer. */ -"join as %@" = "以 %@ 身份加入"; +"Join as %@" = "以 %@ 身份加入"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "Join group" = "加入群组"; /* No comment provided by engineer. */ "Join group conversations" = "加入群对话"; -/* No comment provided by engineer. */ -"Join group?" = "加入群组?"; - /* No comment provided by engineer. */ "Join incognito" = "加入隐身聊天"; -/* No comment provided by engineer. */ -"Join with current profile" = "使用当前档案加入"; - -/* No comment provided by engineer. */ +/* new chat action */ "Join your group?\nThis is your link for group %@!" = "加入您的群组?\n这是您组 %@ 的链接!"; /* No comment provided by engineer. */ @@ -3575,6 +3559,7 @@ snd error text */ /* enabled status group pref value +member criteria value time to disappear */ "off" = "关闭"; @@ -3587,7 +3572,9 @@ time to disappear */ /* feature offered item */ "offered %@: %@" = "已提供 %1$@:%2$@"; -/* alert button */ +/* alert action +alert button +new chat action */ "Ok" = "好的"; /* No comment provided by engineer. */ @@ -3671,7 +3658,7 @@ time to disappear */ /* No comment provided by engineer. */ "Open changes" = "打开更改"; -/* No comment provided by engineer. */ +/* new chat action */ "Open chat" = "打开聊天"; /* authentication reason */ @@ -3680,7 +3667,7 @@ time to disappear */ /* No comment provided by engineer. */ "Open conditions" = "打开条款"; -/* No comment provided by engineer. */ +/* new chat action */ "Open group" = "打开群"; /* authentication reason */ @@ -3755,9 +3742,6 @@ time to disappear */ /* No comment provided by engineer. */ "Password to show" = "显示密码"; -/* past/unknown group member */ -"Past member %@" = "前任成员 %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "粘贴桌面地址"; @@ -3806,7 +3790,7 @@ time to disappear */ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "请检查您使用的链接是否正确,或者让您的联系人给您发送另一个链接。"; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection with %@ and try again." = "请检查您与%@的网络连接,然后重试。"; /* No comment provided by engineer. */ @@ -3845,9 +3829,6 @@ time to disappear */ /* No comment provided by engineer. */ "Polish interface" = "波兰语界面"; -/* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "服务器地址中的证书指纹可能不正确"; - /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "保留最后的消息草稿及其附件。"; @@ -3887,7 +3868,7 @@ time to disappear */ /* No comment provided by engineer. */ "Private routing" = "专用路由"; -/* No comment provided by engineer. */ +/* alert title */ "Private routing error" = "专用路由错误"; /* No comment provided by engineer. */ @@ -4079,14 +4060,15 @@ time to disappear */ /* No comment provided by engineer. */ "Reduced battery usage" = "减少电池使用量"; -/* reject incoming call via notification +/* alert action +reject incoming call via notification swipe action */ "Reject" = "拒绝"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "拒绝联系人(发送者不会被通知)"; -/* No comment provided by engineer. */ +/* alert title */ "Reject contact request" = "拒绝联系人请求"; /* call status */ @@ -4137,18 +4119,12 @@ swipe action */ /* No comment provided by engineer. */ "Renegotiate encryption?" = "重新协商加密?"; -/* No comment provided by engineer. */ -"Repeat connection request?" = "重复连接请求吗?"; - /* No comment provided by engineer. */ "Repeat download" = "重复下载"; /* No comment provided by engineer. */ "Repeat import" = "重复导入"; -/* No comment provided by engineer. */ -"Repeat join request?" = "重复加入请求吗?"; - /* No comment provided by engineer. */ "Repeat upload" = "重复上传"; @@ -4200,7 +4176,7 @@ swipe action */ /* No comment provided by engineer. */ "Restore database error" = "恢复数据库错误"; -/* No comment provided by engineer. */ +/* alert action */ "Retry" = "重试"; /* chat item action */ @@ -4390,9 +4366,6 @@ chat item action */ /* No comment provided by engineer. */ "Send delivery receipts to" = "将送达回执发送给"; -/* No comment provided by engineer. */ -"send direct message" = "发送私信"; - /* No comment provided by engineer. */ "Send direct message to connect" = "发送私信来连接"; @@ -4505,10 +4478,10 @@ chat item action */ "server queue info: %@\n\nlast received msg: %@" = "服务器队列信息: %1$@\n\n上次收到的消息: %2$@"; /* server test error */ -"Server requires authorization to create queues, check password" = "服务器需要授权才能创建队列,检查密码"; +"Server requires authorization to create queues, check password." = "服务器需要授权才能创建队列,检查密码"; /* server test error */ -"Server requires authorization to upload, check password" = "服务器需要授权来上传,检查密码"; +"Server requires authorization to upload, check password." = "服务器需要授权来上传,检查密码"; /* No comment provided by engineer. */ "Server test failed!" = "服务器测试失败!"; @@ -4646,6 +4619,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX 地址或一次性链接?"; +/* alert title */ +"SimpleX address settings" = "自动接受设置"; + /* simplex link type */ "SimpleX channel link" = "SimpleX 频道链接"; @@ -4922,13 +4898,10 @@ chat item action */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; -/* No comment provided by engineer. */ -"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。"; - /* No comment provided by engineer. */ "The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅"; -/* No comment provided by engineer. */ +/* alert message */ "The sender will NOT be notified" = "发送者将不会收到通知"; /* No comment provided by engineer. */ @@ -4976,12 +4949,6 @@ chat item action */ /* No comment provided by engineer. */ "This group no longer exists." = "该群组已不存在。"; -/* No comment provided by engineer. */ -"This is your own one-time link!" = "这是你自己的一次性链接!"; - -/* No comment provided by engineer. */ -"This is your own SimpleX address!" = "这是你自己的 SimpleX 地址!"; - /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "此链接已在其他移动设备上使用,请在桌面上创建新链接。"; @@ -5198,7 +5165,7 @@ chat item action */ /* No comment provided by engineer. */ "Use chat" = "使用聊天"; -/* No comment provided by engineer. */ +/* new chat action */ "Use current profile" = "使用当前配置文件"; /* No comment provided by engineer. */ @@ -5210,7 +5177,7 @@ chat item action */ /* No comment provided by engineer. */ "Use iOS call interface" = "使用 iOS 通话界面"; -/* No comment provided by engineer. */ +/* new chat action */ "Use new incognito profile" = "使用新的隐身配置文件"; /* No comment provided by engineer. */ @@ -5450,33 +5417,27 @@ chat item action */ /* No comment provided by engineer. */ "You are already connected to %@." = "您已经连接到 %@。"; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting to %@." = "您已连接到 %@。"; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already connecting via this one-time link!" = "你已经在通过这个一次性链接进行连接!"; /* No comment provided by engineer. */ "You are already in group %@." = "您已在组 %@ 中。"; -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group %@." = "您已加入组 %@。"; -/* No comment provided by engineer. */ -"You are already joining the group via this link!" = "您已经通过此链接加入群组!"; - -/* No comment provided by engineer. */ +/* new chat sheet message */ "You are already joining the group via this link." = "你已经在通过此链接加入该群。"; -/* No comment provided by engineer. */ +/* new chat sheet title */ "You are already joining the group!\nRepeat join request?" = "您已经加入了这个群组!\n重复加入请求?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "您已连接到用于接收该联系人消息的服务器。"; -/* No comment provided by engineer. */ -"you are invited to group" = "您被邀请加入群组"; - /* No comment provided by engineer. */ "You are invited to group" = "您被邀请加入群组"; @@ -5543,7 +5504,7 @@ chat item action */ /* alert message */ "You can view invitation link again in connection details." = "您可以在连接详情中再次查看邀请链接。"; -/* No comment provided by engineer. */ +/* alert title */ "You can't send messages!" = "您无法发送消息!"; /* chat item text */ @@ -5564,10 +5525,7 @@ chat item action */ /* No comment provided by engineer. */ "You decide who can connect." = "你决定谁可以连接。"; -/* No comment provided by engineer. */ -"You have already requested connection via this address!" = "你已经请求通过此地址进行连接!"; - -/* No comment provided by engineer. */ +/* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "您已经请求连接了!\n重复连接请求?"; /* No comment provided by engineer. */ @@ -5633,9 +5591,6 @@ chat item action */ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。"; -/* No comment provided by engineer. */ -"You will connect to all group members." = "你将连接到所有群成员。"; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。"; @@ -5697,10 +5652,10 @@ chat item action */ "Your profile **%@** will be shared." = "您的个人资料 **%@** 将被共享。"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。"; +"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。"; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "您的资料、联系人和发送的消息存储在您的设备上。"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。"; /* No comment provided by engineer. */ "Your random profile" = "您的随机资料"; diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index 8fc9d104d2..8168c92bf8 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -5,10 +5,11 @@ plugins { id("org.jetbrains.compose") kotlin("android") id("org.jetbrains.kotlin.plugin.serialization") + id("org.jetbrains.kotlin.plugin.compose") } android { - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "chat.simplex.app" @@ -93,6 +94,7 @@ android { "fi", "fr", "hu", + "in", "it", "iw", "ja", @@ -100,10 +102,12 @@ android { "nl", "pl", "pt-rBR", + "ro", "ru", "th", "tr", "uk", + "vi", "zh-rCN" ) ndkVersion = "23.1.7779620" @@ -188,7 +192,7 @@ tasks { outputDir = outputs.files.files.last() } exec { - workingDir("../../../scripts/android") + workingDir("../../scripts/android") environment = mapOf("JAVA_HOME" to "$javaHome") commandLine = listOf( "./compress-and-sign-apk.sh", diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index 0470977bcd..d6059896a5 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ android:fullBackupOnly="false" android:icon="@mipmap/icon" android:label="${app_name}" + android:largeHeap="true" android:extractNativeLibs="${extract_native_libs}" android:supportsRtl="true" android:theme="@style/Theme.SimpleX"> diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 5545595dc6..83767f90d7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -46,6 +46,7 @@ class SimplexApp: Application(), LifecycleEventObserver { override fun onCreate() { super.onCreate() + AppContextProvider.initialize(this) if (ProcessPhoenix.isPhoenixProcess(this)) { return } else { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index ad86759ba9..289ecc0a31 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -112,7 +112,7 @@ class SimplexService: Service() { val title = generalGetString(MR.strings.simplex_service_notification_title) val text = generalGetString(MR.strings.simplex_service_notification_text) notificationManager = createNotificationChannel() - val newNtf = createNotification(title, text) + val newNtf = createServiceNotification(title, text) serviceNotification = newNtf return newNtf } @@ -144,11 +144,18 @@ class SimplexService: Service() { return@withLongRunningApi } saveServiceState(self, ServiceState.STARTED) - wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { - newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG).apply { - acquire() - } - } +// Permanent wakelock prevents deep sleep and results in high battery usage. +// Instead, the app relies on being whitelisted for unrestricted battery usage, +// and also takes wakelock on network events and on network information changes, which allows it to reconnect. +// Network events and information changes are delivered even when device is in deep sleep. +// Possibly, we may need to additionally use "alarms" to wake the app periodically, +// but in all pre-release tests the app was reliably delivering messages in deep sleep, even restored dropped connections. +// +// wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { +// newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG).apply { +// acquire() +// } +// } } finally { isCheckingNewMessages = false } @@ -168,7 +175,7 @@ class SimplexService: Service() { return null } - private fun createNotification(title: String, text: String): Notification { + private fun createServiceNotification(title: String, text: String): Notification { val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent -> PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE) } @@ -181,6 +188,7 @@ class SimplexService: Service() { .setContentIntent(pendingIntent) .setSilent(true) .setShowWhen(false) // no date/time + .setOngoing(true) // Starting SDK 33 / Android 13, foreground notifications can be swiped away // Shows a button which opens notification channel settings if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt index cf19589d4a..5d8371708c 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt @@ -136,7 +136,6 @@ object NtfManager { val actionPendingIntent: PendingIntent = PendingIntent.getBroadcast(SimplexApp.context, 0, actionIntent, flags) val actionButton = when (action) { NotificationAction.ACCEPT_CONTACT_REQUEST -> generalGetString(MR.strings.accept) - NotificationAction.ACCEPT_CONTACT_REQUEST_INCOGNITO -> generalGetString(MR.strings.accept_contact_incognito_button) } builder.addAction(0, actionButton, actionPendingIntent) } @@ -316,7 +315,6 @@ object NtfManager { val m = SimplexApp.context.chatModel when (intent.action) { NotificationAction.ACCEPT_CONTACT_REQUEST.name -> ntfManager.acceptContactRequestAction(userId, incognito = false, chatId) - NotificationAction.ACCEPT_CONTACT_REQUEST_INCOGNITO.name -> ntfManager.acceptContactRequestAction(userId, incognito = true, chatId) RejectCallAction -> { val invitation = m.callInvitations[chatId] if (invitation != null) { diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index e2927e4aaf..8b4dd0e4d7 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("org.jetbrains.compose") id("com.android.library") id("org.jetbrains.kotlin.plugin.serialization") + id("org.jetbrains.kotlin.plugin.compose") id("dev.icerock.mobile.multiplatform-resources") id("com.github.gmazzo.buildconfig") version "5.3.5" } @@ -39,6 +40,8 @@ kotlin { api("com.russhwolf:multiplatform-settings:1.1.1") api("com.charleskorn.kaml:kaml:0.59.0") api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}") + implementation("org.jetbrains.compose.material:material-icons-core:1.7.3") + implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3") implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}") //Barcode api("org.boofcv:boofcv-core:1.1.3") @@ -125,7 +128,7 @@ kotlin { android { namespace = "chat.simplex.common" - compileSdk = 34 + compileSdk = 35 sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 26 diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/NetworkObserver.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/NetworkObserver.kt index 825bc8b846..77e0f49cc8 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/NetworkObserver.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/NetworkObserver.kt @@ -73,6 +73,7 @@ class NetworkObserver { } private fun setNetworkInfo(info: UserNetworkInfo) { + getWakeLock(timeout = 180000) Log.d(TAG, "Network changed: $info") noNetworkJob.cancel() if (info.online) { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index cd1672f3e9..a5e105ce93 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -13,7 +13,6 @@ import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.Semaphore import kotlin.concurrent.thread -import kotlin.random.Random actual val appPlatform = AppPlatform.ANDROID diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt index fc323f6ffd..4f47fda130 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt @@ -101,13 +101,13 @@ actual fun GrayU8.toImageBitmap(): ImageBitmap = ConvertBitmap.grayToBitmap(this actual fun ImageBitmap.hasAlpha(): Boolean = hasAlpha -actual fun ImageBitmap.addLogo(): ImageBitmap = asAndroidBitmap().applyCanvas { - val radius = (width * 0.16f) / 2 +actual fun ImageBitmap.addLogo(size: Float): ImageBitmap = asAndroidBitmap().applyCanvas { + val radius = (width * size) / 2 val paint = android.graphics.Paint() paint.color = android.graphics.Color.WHITE drawCircle(width / 2f, height / 2f, radius, paint) val logo = androidAppContext.resources.getDrawable(R.drawable.icon_foreground_android_common, null).toBitmap() - val logoSize = (width * 0.24).toInt() + val logoSize = (width * size * 1.5).toInt() translate((width - logoSize) / 2f, (height - logoSize) / 2f) drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) }.asImageBitmap() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/SimplexService.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/SimplexService.android.kt new file mode 100644 index 0000000000..3ccdcecab0 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/SimplexService.android.kt @@ -0,0 +1,31 @@ +package chat.simplex.common.platform + +import android.content.Context +import android.os.PowerManager + +actual fun getWakeLock(timeout: Long): (() -> Unit) { + val context = AppContextProvider.getApplicationContext() + ?: throw IllegalStateException("Application context not initialized") + var wakeLock: PowerManager.WakeLock? = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SimplexService::lock").apply { + acquire(timeout) + } + } + return { + val lock = wakeLock + if (lock != null) { + if (lock.isHeld) lock.release() + wakeLock = null + } + } +} + +object AppContextProvider { + private var applicationContext: Context? = null + + fun initialize(context: Context) { + this.applicationContext = context.applicationContext + } + + fun getApplicationContext(): Context? = applicationContext +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 166f4ec355..22e53af849 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -709,9 +709,11 @@ fun WebRTCView(callCommand: SnapshotStateList, onResponse: (WVAPIM .filterNotNull() .collect { while (callCommand.isNotEmpty()) { - val cmd = callCommand.removeFirst() + val cmd = callCommand.removeFirstOrNull() Log.d(TAG, "WebRTCView LaunchedEffect executing $cmd") - processCommand(wv, cmd) + if (cmd != null) { + processCommand(wv, cmd) + } } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.android.kt index b24150ed24..e81827cb9a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.android.kt @@ -46,7 +46,7 @@ actual fun SaveOrOpenFileMenu( } ItemAction( stringResource(MR.strings.save_verb), - painterResource(if (encrypted) MR.images.ic_lock_open_right else MR.images.ic_download), + painterResource(MR.images.ic_download), color = MaterialTheme.colors.primary, onClick = { saveFile() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt index 9e8eb8ee8f..b0bc6dd970 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt @@ -28,7 +28,7 @@ actual fun ReactionIcon(text: String, fontSize: TextUnit) { @Composable actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserLauncher, showMenu: MutableState) { val writePermissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE) - ItemAction(stringResource(MR.strings.save_verb), painterResource(if (cItem.file?.fileSource?.cryptoArgs == null) MR.images.ic_download else MR.images.ic_lock_open_right), onClick = { + ItemAction(stringResource(MR.strings.save_verb), painterResource(MR.images.ic_download), onClick = { when (cItem.content.msgContent) { is MsgContent.MCImage -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.status == PermissionStatus.Granted) { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt index 54e3061d25..a09ca2792b 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -91,7 +91,7 @@ fun UserPickerUserBox( ProfileImageForActiveCall(size = USER_PICKER_IMAGE_SIZE, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) if (userInfo.unreadCount > 0 && !userInfo.user.activeUser) { - unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false) + userUnreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false) } } val user = userInfo.user diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index 320a8e876a..47506d9532 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -159,11 +159,11 @@ fun AppearanceScope.AppearanceLayout( } } -private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon -> +private fun findEnabledIcon(): AppIcon = AppIcon.values().firstOrNull { icon -> androidAppContext.packageManager.getComponentEnabledSetting( ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}") ).let { it == COMPONENT_ENABLED_STATE_DEFAULT || it == COMPONENT_ENABLED_STATE_ENABLED } -} +} ?: AppIcon.DEFAULT @Preview @Composable diff --git a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c index b9b5277aeb..fd7f71d49c 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c @@ -58,12 +58,13 @@ typedef long* chat_ctrl; extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl); extern char *chat_close_store(chat_ctrl ctrl); -extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd); -extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd); +extern char *chat_send_cmd_retry(chat_ctrl ctrl, const char *cmd, const int retryNum); +extern char *chat_send_remote_cmd_retry(chat_ctrl ctrl, const int rhId, const char *cmd, const int retryNum); extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait); extern char *chat_parse_markdown(const char *str); extern char *chat_parse_server(const char *str); +extern char *chat_parse_uri(const char *str, const int safe); extern char *chat_password_hash(const char *pwd, const char *salt); extern char *chat_valid_name(const char *name); extern int chat_json_length(const char *str); @@ -102,20 +103,20 @@ Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, __unused jc } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) { +Java_chat_simplex_common_platform_CoreKt_chatSendCmdRetry(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg, jint retryNum) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); //jint length = (jint) (*env)->GetStringUTFLength(env, msg); //for (int i = 0; i < length; ++i) // __android_log_print(ANDROID_LOG_ERROR, "simplex", "%d: %02x\n", i, _msg[i]); - jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg)); + jstring res = (*env)->NewStringUTF(env, chat_send_cmd_retry((void*)controller, _msg, retryNum)); (*env)->ReleaseStringUTFChars(env, msg, _msg); return res; } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jint rhId, jstring msg) { +Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmdRetry(JNIEnv *env, __unused jclass clazz, jlong controller, jint rhId, jstring msg, jint retryNum) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); - jstring res = (*env)->NewStringUTF(env, chat_send_remote_cmd((void*)controller, rhId, _msg)); + jstring res = (*env)->NewStringUTF(env, chat_send_remote_cmd_retry((void*)controller, rhId, _msg, retryNum)); (*env)->ReleaseStringUTFChars(env, msg, _msg); return res; } @@ -146,6 +147,14 @@ Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, __unused j return res; } +JNIEXPORT jstring JNICALL +Java_chat_simplex_common_platform_CoreKt_chatParseUri(JNIEnv *env, __unused jclass clazz, jstring str, jint safe) { + const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); + jstring res = (*env)->NewStringUTF(env, chat_parse_uri(_str, safe)); + (*env)->ReleaseStringUTFChars(env, str, _str); + return res; +} + JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, __unused jclass clazz, jstring pwd, jstring salt) { const char *_pwd = (*env)->GetStringUTFChars(env, pwd, JNI_FALSE); diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c index 5c921c400d..9844a5927d 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c @@ -31,12 +31,13 @@ typedef long* chat_ctrl; extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl); extern char *chat_close_store(chat_ctrl ctrl); -extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd); -extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd); +extern char *chat_send_cmd_retry(chat_ctrl ctrl, const char *cmd, const int retryNum); +extern char *chat_send_remote_cmd_retry(chat_ctrl ctrl, const int rhId, const char *cmd, const int retryNum); extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait); extern char *chat_parse_markdown(const char *str); extern char *chat_parse_server(const char *str); +extern char *chat_parse_uri(const char *str, const int safe); extern char *chat_password_hash(const char *pwd, const char *salt); extern char *chat_valid_name(const char *name); extern int chat_json_length(const char *str); @@ -115,17 +116,17 @@ Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, jclass claz } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) { +Java_chat_simplex_common_platform_CoreKt_chatSendCmdRetry(JNIEnv *env, jclass clazz, jlong controller, jstring msg, jint retryNum) { const char *_msg = encode_to_utf8_chars(env, msg); - jstring res = decode_to_utf8_string(env, chat_send_cmd((void*)controller, _msg)); + jstring res = decode_to_utf8_string(env, chat_send_cmd_retry((void*)controller, _msg, retryNum)); (*env)->ReleaseStringUTFChars(env, msg, _msg); return res; } JNIEXPORT jstring JNICALL -Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmd(JNIEnv *env, jclass clazz, jlong controller, jint rhId, jstring msg) { +Java_chat_simplex_common_platform_CoreKt_chatSendRemoteCmdRetry(JNIEnv *env, jclass clazz, jlong controller, jint rhId, jstring msg, jint retryNum) { const char *_msg = encode_to_utf8_chars(env, msg); - jstring res = decode_to_utf8_string(env, chat_send_remote_cmd((void*)controller, rhId, _msg)); + jstring res = decode_to_utf8_string(env, chat_send_remote_cmd_retry((void*)controller, rhId, _msg, retryNum)); (*env)->ReleaseStringUTFChars(env, msg, _msg); return res; } @@ -156,6 +157,14 @@ Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, jclass cla return res; } +JNIEXPORT jstring JNICALL +Java_chat_simplex_common_platform_CoreKt_chatParseUri(JNIEnv *env, jclass clazz, jstring str, jint safe) { + const char *_str = encode_to_utf8_chars(env, str); + jstring res = decode_to_utf8_string(env, chat_parse_uri(_str, safe)); + (*env)->ReleaseStringUTFChars(env, str, _str); + return res; +} + JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, jclass clazz, jstring pwd, jstring salt) { const char *_pwd = encode_to_utf8_chars(env, pwd); diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index d88a450fd1..70e0067260 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -243,9 +243,9 @@ fun MainScreen() { ModalManager.fullscreen.showOneTimePasscodeInView() AlertManager.privacySensitive.showInView() if (onboarding == OnboardingStage.OnboardingComplete) { - LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) { + LaunchedEffect(chatModel.chatRunning.value, chatModel.currentUser.value, chatModel.appOpenUrl.value) { val (rhId, url) = chatModel.appOpenUrl.value ?: (null to null) - if (url != null) { + if (url != null && chatModel.chatRunning.value == true) { chatModel.appOpenUrl.value = null connectIfOpenedViaUri(rhId, url, chatModel) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 61c20587bf..76a9ab4c16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -26,6 +26,8 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlin.collections.removeAll as remAll import kotlinx.datetime.* +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.serialization.* import kotlinx.serialization.descriptors.* @@ -43,6 +45,39 @@ import kotlin.collections.ArrayList import kotlin.random.Random import kotlin.time.* +object ConnectProgressManager { + private val connectInProgress = mutableStateOf(null) + private val connectProgressByTimeout = mutableStateOf(false) + private var onCancel: (() -> Unit)? = null + + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + + fun startConnectProgress(text: String, onCancel: (() -> Unit)? = null) { + connectInProgress.value = text + this.onCancel = onCancel + coroutineScope.launch { + delay(1000) + connectProgressByTimeout.value = connectInProgress.value != null + } + } + + fun stopConnectProgress() { + connectInProgress.value = null + onCancel = null + connectProgressByTimeout.value = false + } + + fun cancelConnectProgress() { + onCancel?.invoke() + stopConnectProgress() + } + + val showConnectProgress: String? get() = + if (connectProgressByTimeout.value) connectInProgress.value else null +} + +val connectProgressManager = ConnectProgressManager + /* * Without this annotation an animation from ChatList to ChatView has 1 frame per the whole animation. Don't delete it * */ @@ -98,6 +133,7 @@ object ChatModel { // set when app is opened via contact or invitation URI (rhId, uri) val appOpenUrl = mutableStateOf?>(null) + val appOpenUrlConnecting = mutableStateOf(false) // Needed to check for bottom nav bar and to apply or not navigation bar color on Android val newChatSheetVisible = mutableStateOf(false) @@ -171,6 +207,8 @@ object ChatModel { // return true if you handled the click var centerPanelBackgroundClickHandler: (() -> Boolean)? = null + fun addressShortLinkDataSet(): Boolean = userAddress.value?.shortLinkDataSet ?: true + fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value } else { @@ -295,14 +333,13 @@ object ChatModel { } } - class ChatsContext(val contentTag: MsgContentTag?) { + class ChatsContext(val secondaryContextFilter: SecondaryContextFilter?) { val chats = mutableStateOf(SnapshotStateList()) /** if you modify the items by adding/removing them, use helpers methods like [addToChatItems], [removeLastChatItems], [removeAllAndNotify], [clearAndNotify] and so on. * If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages]. * If you use api call to get the items, use just [add] instead of [addToChatItems]. * Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */ val chatItems = mutableStateOf(SnapshotStateList()) - val chatItemStatuses = mutableMapOf() // set listener here that will be notified on every add/delete of a chat item val chatState = ActiveChatState() @@ -310,6 +347,26 @@ object ChatModel { fun getChat(id: String): Chat? = chats.value.firstOrNull { it.id == id } private fun getChatIndex(rhId: Long?, id: String): Int = chats.value.indexOfFirst { it.id == id && it.remoteHostId == rhId } + val contentTag: MsgContentTag? = + when (secondaryContextFilter) { + null -> null + is SecondaryContextFilter.GroupChatScopeContext -> null + is SecondaryContextFilter.MsgContentTagContext -> secondaryContextFilter.contentTag + } + + val groupScopeInfo: GroupChatScopeInfo? = + when (secondaryContextFilter) { + null -> null + is SecondaryContextFilter.GroupChatScopeContext -> secondaryContextFilter.groupScopeInfo + is SecondaryContextFilter.MsgContentTagContext -> null + } + + val isUserSupportChat: Boolean = + when (groupScopeInfo) { + null -> false + is GroupChatScopeInfo.MemberSupport -> groupScopeInfo.groupMember_ == null + } + suspend fun addChat(chat: Chat) { chats.add(index = 0, chat) popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = 0) @@ -342,6 +399,8 @@ object ChatModel { ) ) } + } else if (currentCInfo is ChatInfo.Group && newCInfo is ChatInfo.Group && newCInfo.groupChatScope != null) { + newCInfo = newCInfo.copy(groupInfo = newCInfo.groupInfo, groupChatScope = null) } chats[i] = chats[i].copy(chatInfo = newCInfo) } @@ -364,7 +423,7 @@ object ChatModel { updateContact(rhId, updatedContact) } - suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo)) + suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo, groupChatScope = null)) private suspend fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) { if (hasChat(rhId, cInfo.id)) { @@ -375,8 +434,19 @@ object ChatModel { } } - fun updateChats(newChats: List) { - chats.replaceAll(newChats) + fun updateChats(newChats: List, keepingChatId: String? = null) { + if (keepingChatId != null) { + val chatToKeep = getChat(keepingChatId) + val indexToRemove = newChats.indexOfFirst { it.id == keepingChatId } + if (chatToKeep != null && indexToRemove != -1) { + val remainingNewChats = newChats.toMutableList().apply { removeAt(indexToRemove) } + chats.replaceAll(listOf(chatToKeep) + remainingNewChats) + } else { + chats.replaceAll(newChats) + } + } else { + chats.replaceAll(newChats) + } popChatCollector.clear() val cId = chatId.value @@ -405,66 +475,80 @@ object ChatModel { } fun removeLastChatItems() { - val removed: Triple + val remIndex: Int + val rem: ChatItem? chatItems.value = SnapshotStateList().apply { addAll(chatItems.value) - val remIndex = lastIndex - val rem = removeLast() - removed = Triple(rem.id, remIndex, rem.isRcvNew) + remIndex = lastIndex + rem = removeLastOrNull() + } + if (rem != null) { + val removed = Triple(rem.id, remIndex, rem.isRcvNew) + chatState.itemsRemoved(listOf(removed), chatItems.value) } - chatState.itemsRemoved(listOf(removed), chatItems.value) } - suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) { - // mark chat non deleted - if (cInfo is ChatInfo.Direct && cInfo.chatDeleted) { - val updatedContact = cInfo.contact.copy(chatDeleted = false) - updateContact(rhId, updatedContact) + suspend fun addChatItem(rhId: Long?, chatInfo: ChatInfo, cItem: ChatItem) { + // updates membersRequireAttention + val cInfo = if (chatInfo is ChatInfo.Direct && chatInfo.chatDeleted) { + // mark chat non deleted + val updatedContact = chatInfo.contact.copy(chatDeleted = false) + ChatInfo.Direct(updatedContact) + } else { + chatInfo } - // update previews + updateChatInfo(rhId, cInfo) + // update chat list val i = getChatIndex(rhId, cInfo.id) val chat: Chat if (i >= 0) { - chat = chats[i] - val newPreviewItem = when (cInfo) { - is ChatInfo.Group -> { - val currentPreviewItem = chat.chatItems.firstOrNull() - if (currentPreviewItem != null) { - if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) { - cItem + chat = chatsContext.chats[i] + // update preview (for chat from main scope to show new items for invitee in pending status) + if (cInfo.groupChatScope() == null || cInfo.groupInfo_?.membership?.memberPending == true) { + val newPreviewItem = when (cInfo) { + is ChatInfo.Group -> { + val currentPreviewItem = chat.chatItems.firstOrNull() + if (currentPreviewItem != null) { + if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) { + cItem + } else { + currentPreviewItem + } } else { - currentPreviewItem + cItem } - } else { - cItem } - } - else -> cItem - } - val wasUnread = chat.unreadTag - chats[i] = chat.copy( - chatItems = arrayListOf(newPreviewItem), - chatStats = - if (cItem.meta.itemStatus is CIStatus.RcvNew) { - increaseUnreadCounter(rhId, currentUser.value!!) - chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, unreadMentions = if (cItem.meta.userMention) chat.chatStats.unreadMentions + 1 else chat.chatStats.unreadMentions) - } - else - chat.chatStats - ) - updateChatTagReadNoContentTag(chats[i], wasUnread) + else -> cItem + } + val wasUnread = chat.unreadTag + chatsContext.chats[i] = chat.copy( + chatItems = arrayListOf(newPreviewItem), + chatStats = + if (cItem.meta.itemStatus is CIStatus.RcvNew) { + increaseUnreadCounter(rhId, currentUser.value!!) + chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, unreadMentions = if (cItem.meta.userMention) chat.chatStats.unreadMentions + 1 else chat.chatStats.unreadMentions) + } else + chat.chatStats + ) + updateChatTagReadInPrimaryContext(chatsContext.chats[i], wasUnread) + } + // pop chat if (appPlatform.isDesktop && cItem.chatDir.sent) { - reorderChat(chats[i], 0) + reorderChat(chatsContext.chats[i], 0) } else { popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = i) } } else { - addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) + if (cInfo.groupChatScope() == null) { + addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) + } else { + addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = emptyList())) + } } + // add to current scope withContext(Dispatchers.Main) { - // add to current chat - if (chatId.value == cInfo.id) { + if (chatItemBelongsToScope(cInfo, cItem)) { // Prevent situation when chat item already in the list received from backend if (chatItems.value.none { it.id == cItem.id }) { if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { @@ -477,83 +561,100 @@ object ChatModel { } } - suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean { - // update previews - val i = getChatIndex(rhId, cInfo.id) - val chat: Chat - val res: Boolean - if (i >= 0) { - chat = chats[i] - val pItem = chat.chatItems.lastOrNull() - if (pItem?.id == cItem.id) { - chats[i] = chat.copy(chatItems = arrayListOf(cItem)) - if (pItem.isRcvNew && !cItem.isRcvNew) { - // status changed from New to Read, update counter - decreaseCounterInChatNoContentTag(rhId, cInfo.id) + private fun chatItemBelongsToScope(cInfo: ChatInfo, cItem: ChatItem): Boolean = + when (secondaryContextFilter) { + null -> + chatId.value == cInfo.id && cInfo.groupChatScope() == null + is SecondaryContextFilter.GroupChatScopeContext -> { + val cInfoScope = cInfo.groupChatScope() + if (cInfoScope != null) { + chatId.value == cInfo.id && sameChatScope(cInfoScope, secondaryContextFilter.groupScopeInfo.toChatScope()) + } else { + false } } - res = false - } else { - addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) - res = true + is SecondaryContextFilter.MsgContentTagContext -> + chatId.value == cInfo.id && cItem.isReport } - return withContext(Dispatchers.Main) { - // update current chat - if (chatId.value == cInfo.id) { + + suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean { + var itemAdded = false + // update chat list + if (cInfo.groupChatScope() == null) { + val i = getChatIndex(rhId, cInfo.id) + val chat: Chat + if (i >= 0) { + chat = chats[i] + val pItem = chat.chatItems.lastOrNull() + if (pItem?.id == cItem.id) { + chats[i] = chat.copy(chatItems = arrayListOf(cItem)) + if (pItem.isRcvNew && !cItem.isRcvNew) { + // status changed from New to Read, update counter + decreaseCounterInPrimaryContext(rhId, cInfo.id) + } + } + } else { + addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) + itemAdded = true + } + } + // update current scope + withContext(Dispatchers.Main) { + if (chatItemBelongsToScope(cInfo, cItem)) { if (cItem.isDeletedContent || cItem.meta.itemDeleted != null) { AudioPlayer.stop(cItem) } val items = chatItems.value val itemIndex = items.indexOfFirst { it.id == cItem.id } if (itemIndex >= 0) { - items[itemIndex] = cItem - false - } else { - val status = chatItemStatuses.remove(cItem.id) - val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) { - cItem.copy(meta = cItem.meta.copy(itemStatus = status)) + val oldStatus = items[itemIndex].meta.itemStatus + val newStatus = cItem.meta.itemStatus + val ci = if (shouldKeepOldSndCIStatus(oldStatus, newStatus)) { + cItem.copy(meta = cItem.meta.copy(itemStatus = oldStatus)) } else { cItem } - addToChatItems(ci) - true + items[itemIndex] = ci + } else { + addToChatItems(cItem) + itemAdded = true } - } else { - res } } + return itemAdded } suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null, atIndex: Int? = null) { withContext(Dispatchers.Main) { - if (chatId.value == cInfo.id) { + if (chatItemBelongsToScope(cInfo, cItem)) { val items = chatItems.value val itemIndex = atIndex ?: items.indexOfFirst { it.id == cItem.id } if (itemIndex >= 0) { items[itemIndex] = cItem } - } else if (status != null) { - chatItemStatuses[cItem.id] = status } } } fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) { - if (cItem.isRcvNew) { - decreaseCounterInChatNoContentTag(rhId, cInfo.id) - } - // update previews - val i = getChatIndex(rhId, cInfo.id) - val chat: Chat - if (i >= 0) { - chat = chats[i] - val pItem = chat.chatItems.lastOrNull() - if (pItem?.id == cItem.id) { - chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy)) + // update chat list + if (cInfo.groupChatScope() == null) { + if (cItem.isRcvNew) { + decreaseCounterInPrimaryContext(rhId, cInfo.id) + } + // update preview + val i = getChatIndex(rhId, cInfo.id) + val chat: Chat + if (i >= 0) { + chat = chats[i] + val pItem = chat.chatItems.lastOrNull() + if (pItem?.id == cItem.id) { + chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy)) + } } } - // remove from current chat - if (chatId.value == cInfo.id) { + // remove from current scope + if (chatItemBelongsToScope(cInfo, cItem)) { chatItems.removeAllAndNotify { // We delete taking into account meta.createdAt to make sure we will not be in situation when two items with the same id will be deleted // (it can happen if already deleted chat item in backend still in the list and new one came with the same (re-used) chat item id) @@ -586,7 +687,7 @@ object ChatModel { Log.d(TAG, "exiting removeMemberItems") return } - val cInfo = ChatInfo.Group(groupInfo) + val cInfo = ChatInfo.Group(groupInfo, groupChatScope = null) // TODO [knocking] review if (chatId.value == groupInfo.id) { for (i in 0 until chatItems.value.size) { val updatedItem = removedUpdatedItem(chatItems.value[i]) @@ -617,7 +718,6 @@ object ChatModel { } // clear current chat if (chatId.value == cInfo.id) { - chatItemStatuses.clear() chatItems.clearAndNotify() } } @@ -688,7 +788,7 @@ object ChatModel { chats[chatIdx] = chat.copy( chatStats = chat.chatStats.copy(unreadCount = unreadCount, unreadMentions = unreadMentions) ) - updateChatTagReadNoContentTag(chats[chatIdx], wasUnread) + updateChatTagReadInPrimaryContext(chats[chatIdx], wasUnread) } } } @@ -729,9 +829,9 @@ object ChatModel { return markedRead to mentionsMarkedRead } - private fun decreaseCounterInChatNoContentTag(rhId: Long?, chatId: ChatId) { + private fun decreaseCounterInPrimaryContext(rhId: Long?, chatId: ChatId) { // updates anything only in main ChatView, not GroupReportsView or anything else from the future - if (contentTag != null) return + if (secondaryContextFilter != null) return val chatIndex = getChatIndex(rhId, chatId) if (chatIndex == -1) return @@ -745,7 +845,7 @@ object ChatModel { unreadCount = unreadCount, ) ) - updateChatTagReadNoContentTag(chats[chatIndex], wasUnread) + updateChatTagReadInPrimaryContext(chats[chatIndex], wasUnread) } fun removeChat(rhId: Long?, id: String) { @@ -814,16 +914,16 @@ object ChatModel { } fun increaseUnreadCounter(rhId: Long?, user: UserLike) { - changeUnreadCounterNoContentTag(rhId, user, 1) + changeUnreadCounterInPrimaryContext(rhId, user, 1) } fun decreaseUnreadCounter(rhId: Long?, user: UserLike, by: Int = 1) { - changeUnreadCounterNoContentTag(rhId, user, -by) + changeUnreadCounterInPrimaryContext(rhId, user, -by) } - private fun changeUnreadCounterNoContentTag(rhId: Long?, user: UserLike, by: Int) { + private fun changeUnreadCounterInPrimaryContext(rhId: Long?, user: UserLike, by: Int) { // updates anything only in main ChatView, not GroupReportsView or anything else from the future - if (contentTag != null) return + if (secondaryContextFilter != null) return val i = users.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == rhId } if (i != -1) { @@ -831,9 +931,9 @@ object ChatModel { } } - fun updateChatTagReadNoContentTag(chat: Chat, wasUnread: Boolean) { + fun updateChatTagReadInPrimaryContext(chat: Chat, wasUnread: Boolean) { // updates anything only in main ChatView, not GroupReportsView or anything else from the future - if (contentTag != null) return + if (secondaryContextFilter != null) return val tags = chat.chatInfo.chatTags ?: return val nowUnread = chat.unreadTag @@ -843,21 +943,21 @@ object ChatModel { unreadTags[tag] = (unreadTags[tag] ?: 0) + 1 } } else if (!nowUnread && wasUnread) { - markChatTagReadNoContentTag_(chat, tags) + markChatTagReadInPrimaryContext_(chat, tags) } } fun markChatTagRead(chat: Chat) { if (chat.unreadTag) { chat.chatInfo.chatTags?.let { tags -> - markChatTagReadNoContentTag_(chat, tags) + markChatTagReadInPrimaryContext_(chat, tags) } } } - private fun markChatTagReadNoContentTag_(chat: Chat, tags: List) { + private fun markChatTagReadInPrimaryContext_(chat: Chat, tags: List) { // updates anything only in main ChatView, not GroupReportsView or anything else from the future - if (contentTag != null) return + if (secondaryContextFilter != null) return for (tag in tags) { val count = unreadTags[tag] @@ -889,12 +989,12 @@ object ChatModel { val wasReportsCount = chat.chatStats.reportsCount val nowReportsCount = chats[i].chatStats.reportsCount val by = if (wasReportsCount == 0 && nowReportsCount > 0) 1 else if (wasReportsCount > 0 && nowReportsCount == 0) -1 else 0 - changeGroupReportsTagNoContentTag(by) + changeGroupReportsTagInPrimaryContext(by) } } - private fun changeGroupReportsTagNoContentTag(by: Int = 0) { - if (by == 0 || contentTag != null) return + private fun changeGroupReportsTagInPrimaryContext(by: Int = 0) { + if (by == 0 || secondaryContextFilter != null) return presetTags[PresetTagKind.GROUP_REPORTS] = kotlin.math.max(0, (presetTags[PresetTagKind.GROUP_REPORTS] ?: 0) + by) clearActiveChatFilterIfNeeded() } @@ -1099,6 +1199,28 @@ enum class ChatType(val type: String) { ContactConnection(":"); } +sealed class GroupChatScope { + class MemberSupport(val groupMemberId_: Long?): GroupChatScope() +} + +fun sameChatScope(scope1: GroupChatScope, scope2: GroupChatScope) = + scope1 is GroupChatScope.MemberSupport + && scope2 is GroupChatScope.MemberSupport + && scope1.groupMemberId_ == scope2.groupMemberId_ + +@Serializable +sealed class GroupChatScopeInfo { + @Serializable @SerialName("memberSupport") data class MemberSupport(val groupMember_: GroupMember?) : GroupChatScopeInfo() + + fun toChatScope(): GroupChatScope = + when (this) { + is MemberSupport -> when (groupMember_) { + null -> GroupChatScope.MemberSupport(groupMemberId_ = null) + else -> GroupChatScope.MemberSupport(groupMemberId_ = groupMember_.groupMemberId) + } + } +} + @Serializable data class User( val remoteHostId: Long?, @@ -1112,11 +1234,13 @@ data class User( override val showNtfs: Boolean, val sendRcptsContacts: Boolean, val sendRcptsSmallGroups: Boolean, + val autoAcceptMemberContacts: Boolean, val viewPwdHash: UserPwdHash?, val uiThemes: ThemeModeOverrides? = null, ): NamedChat, UserLike { override val displayName: String get() = profile.displayName override val fullName: String get() = profile.fullName + override val shortDescr: String? get() = profile.shortDescr override val image: String? get() = profile.image override val localAlias: String = "" @@ -1140,6 +1264,7 @@ data class User( showNtfs = true, sendRcptsContacts = true, sendRcptsSmallGroups = false, + autoAcceptMemberContacts = false, viewPwdHash = null, uiThemes = null, ) @@ -1186,6 +1311,7 @@ typealias ChatId = String interface NamedChat { val displayName: String val fullName: String + val shortDescr: String? val image: String? val localAlias: String val chatViewName: String @@ -1204,8 +1330,9 @@ interface SomeChat { val apiId: Long val ready: Boolean val chatDeleted: Boolean - val userCantSendReason: Pair? - val sendMsgEnabled: Boolean + val nextConnect: Boolean + val nextConnectPrepared: Boolean + val profileChangeProhibited: Boolean val incognito: Boolean fun featureEnabled(feature: ChatFeature): Boolean val timedMessagesTTL: Int? @@ -1237,6 +1364,16 @@ data class Chat( val id: String get() = chatInfo.id + val supportUnreadCount: Int get() = when (chatInfo) { + is ChatInfo.Group -> + if (chatInfo.groupInfo.canModerate) { + chatInfo.groupInfo.membersRequireAttention + } else { + chatInfo.groupInfo.membership.supportChat?.unread ?: 0 + } + else -> 0 + } + fun groupFeatureEnabled(feature: GroupFeature): Boolean = if (chatInfo is ChatInfo.Group) { chatInfo.groupInfo.groupFeatureEnabled(feature) @@ -1275,8 +1412,9 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contact.apiId override val ready get() = contact.ready override val chatDeleted get() = contact.chatDeleted - override val userCantSendReason get() = contact.userCantSendReason - override val sendMsgEnabled get() = contact.sendMsgEnabled + override val nextConnect get() = contact.nextConnect + override val nextConnectPrepared get() = contact.nextConnectPrepared + override val profileChangeProhibited get() = contact.profileChangeProhibited override val incognito get() = contact.incognito override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature) override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL @@ -1284,6 +1422,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = contact.updatedAt override val displayName get() = contact.displayName override val fullName get() = contact.fullName + override val shortDescr get() = contact.profile.shortDescr override val image get() = contact.image override val localAlias: String get() = contact.localAlias override fun anyNameContains(searchAnyCase: String): Boolean = contact.anyNameContains(searchAnyCase) @@ -1294,15 +1433,16 @@ sealed class ChatInfo: SomeChat, NamedChat { } @Serializable @SerialName("group") - data class Group(val groupInfo: GroupInfo): ChatInfo() { + data class Group(val groupInfo: GroupInfo, val groupChatScope: GroupChatScopeInfo?): ChatInfo() { override val chatType get() = ChatType.Group override val localDisplayName get() = groupInfo.localDisplayName override val id get() = groupInfo.id override val apiId get() = groupInfo.apiId override val ready get() = groupInfo.ready override val chatDeleted get() = groupInfo.chatDeleted - override val userCantSendReason get() = groupInfo.userCantSendReason - override val sendMsgEnabled get() = groupInfo.sendMsgEnabled + override val nextConnect get() = groupInfo.nextConnect + override val nextConnectPrepared get() = groupInfo.nextConnectPrepared + override val profileChangeProhibited get() = groupInfo.profileChangeProhibited override val incognito get() = groupInfo.incognito override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature) override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL @@ -1310,11 +1450,12 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = groupInfo.updatedAt override val displayName get() = groupInfo.displayName override val fullName get() = groupInfo.fullName + override val shortDescr get() = groupInfo.groupProfile.shortDescr override val image get() = groupInfo.image override val localAlias get() = groupInfo.localAlias companion object { - val sampleData = Group(GroupInfo.sampleData) + val sampleData = Group(GroupInfo.sampleData, groupChatScope = null) } } @@ -1326,8 +1467,9 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = noteFolder.apiId override val ready get() = noteFolder.ready override val chatDeleted get() = noteFolder.chatDeleted - override val userCantSendReason get() = noteFolder.userCantSendReason - override val sendMsgEnabled get() = noteFolder.sendMsgEnabled + override val nextConnect get() = noteFolder.nextConnect + override val nextConnectPrepared get() = noteFolder.nextConnectPrepared + override val profileChangeProhibited get() = noteFolder.profileChangeProhibited override val incognito get() = noteFolder.incognito override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature) override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL @@ -1335,6 +1477,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = noteFolder.updatedAt override val displayName get() = noteFolder.displayName override val fullName get() = noteFolder.fullName + override val shortDescr get() = null override val image get() = noteFolder.image override val localAlias get() = noteFolder.localAlias @@ -1351,8 +1494,9 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contactRequest.apiId override val ready get() = contactRequest.ready override val chatDeleted get() = contactRequest.chatDeleted - override val userCantSendReason get() = contactRequest.userCantSendReason - override val sendMsgEnabled get() = contactRequest.sendMsgEnabled + override val nextConnect get() = contactRequest.nextConnect + override val nextConnectPrepared get() = contactRequest.nextConnectPrepared + override val profileChangeProhibited get() = contactRequest.profileChangeProhibited override val incognito get() = contactRequest.incognito override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature) override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL @@ -1360,6 +1504,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = contactRequest.updatedAt override val displayName get() = contactRequest.displayName override val fullName get() = contactRequest.fullName + override val shortDescr get() = contactRequest.profile.shortDescr override val image get() = contactRequest.image override val localAlias get() = contactRequest.localAlias @@ -1376,8 +1521,9 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contactConnection.apiId override val ready get() = contactConnection.ready override val chatDeleted get() = contactConnection.chatDeleted - override val userCantSendReason get() = contactConnection.userCantSendReason - override val sendMsgEnabled get() = contactConnection.sendMsgEnabled + override val nextConnect get() = contactConnection.nextConnect + override val nextConnectPrepared get() = contactConnection.nextConnectPrepared + override val profileChangeProhibited get() = contactConnection.profileChangeProhibited override val incognito get() = contactConnection.incognito override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature) override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL @@ -1385,6 +1531,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = contactConnection.updatedAt override val displayName get() = contactConnection.displayName override val fullName get() = contactConnection.fullName + override val shortDescr get() = null override val image get() = contactConnection.image override val localAlias get() = contactConnection.localAlias @@ -1406,13 +1553,15 @@ sealed class ChatInfo: SomeChat, NamedChat { override val id get() = "?$apiId" override val ready get() = false override val chatDeleted get() = false - override val userCantSendReason get() = generalGetString(MR.strings.cant_send_message_generic) to null - override val sendMsgEnabled get() = false + override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null override val displayName get() = invalidChatName override val fullName get() = invalidChatName + override val shortDescr get() = null override val image get() = null override val localAlias get() = "" @@ -1422,6 +1571,91 @@ sealed class ChatInfo: SomeChat, NamedChat { } } + val userCantSendReason: Pair? + get() { + when (this) { + is Direct -> { + if (contact.sendMsgToConnect) return null + if (contact.nextAcceptContactRequest) { return generalGetString(MR.strings.cant_send_message_generic) to null } + if (!contact.active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null + if (!contact.sndReady) { + return if (contact.preparedContact?.uiConnLinkType == ConnectionMode.Con) { + generalGetString(MR.strings.cant_send_message_request_is_sent) to null + } else { + generalGetString(MR.strings.cant_send_message_contact_not_ready) to null + } + } + if (contact.activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null + if (contact.activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null + return null + } + is Group -> { + if (groupInfo.membership.memberActive) { + when (groupChatScope) { + null -> { + if (groupInfo.membership.memberPending) { + return generalGetString(MR.strings.reviewed_by_admins) to generalGetString(MR.strings.observer_cant_send_message_desc) + } + if (groupInfo.membership.memberRole == GroupMemberRole.Observer) { + return generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc) + } + return null + } + is GroupChatScopeInfo.MemberSupport -> + if (groupChatScope.groupMember_ != null) { + if ( + groupChatScope.groupMember_.versionRange.maxVersion < GROUP_KNOCKING_VERSION + && !groupChatScope.groupMember_.memberPending + ) { + return generalGetString(MR.strings.cant_send_message_member_has_old_version) to null + } + return null + } else { + return null + } + } + } else if (groupInfo.nextConnectPrepared) { + return null + } else { + return when (groupInfo.membership.memberStatus) { + GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null + GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null + GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null + GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null + else -> generalGetString(MR.strings.cant_send_message_generic) to null + } + } + } + is Local -> + return null + is ContactRequest -> + return generalGetString(MR.strings.cant_send_message_generic) to null + is ContactConnection -> + return generalGetString(MR.strings.cant_send_message_generic) to null + is InvalidJSON -> + return generalGetString(MR.strings.cant_send_message_generic) to null + } + } + + val sendMsgEnabled get() = userCantSendReason == null + + val sndReady: Boolean get() = + when(this) { + is Direct -> contact.sndReady + is Group -> + groupInfo.membership.memberActive + && (groupChatScope != null || (!groupInfo.membership.memberPending && groupInfo.membership.memberRole != GroupMemberRole.Observer)) + is Local -> true + is ContactRequest -> false + is ContactConnection -> false + is InvalidJSON -> false + } + + fun groupChatScope(): GroupChatScope? = when (this) { + is Group -> groupChatScope?.toChatScope() + else -> null + } + fun ntfsEnabled(ci: ChatItem): Boolean = ntfsEnabled(ci.meta.userMention) @@ -1460,12 +1694,32 @@ sealed class ChatInfo: SomeChat, NamedChat { val hasMentions: Boolean get() = this is Group + val useCommands: Boolean get() = when(this) { + is Direct -> contact.isBot + is Group -> groupInfo.groupProfile.groupPreferences?.commands?.isNotEmpty() ?: false + else -> false + } + + val menuCommands: List get() = when(this) { + is Direct -> + if (contact.isBot) contact.profile.preferences?.commands ?: emptyList() + else emptyList() + is Group -> groupInfo.groupProfile.groupPreferences?.commands ?: emptyList() + else -> emptyList() + } + val contactCard: Boolean get() = when (this) { - is Direct -> contact.activeConn == null && contact.profile.contactLink != null && contact.active + is Direct -> contact.isContactCard else -> false } - } + + val groupInfo_: GroupInfo? + get() = when (this) { + is Group -> groupInfo + else -> null + } +} @Serializable sealed class NetworkStatus { @@ -1506,8 +1760,11 @@ data class Contact( override val createdAt: Instant, override val updatedAt: Instant, val chatTs: Instant?, + val preparedContact: PreparedContact?, + val contactRequestId: Long?, val contactGroupMemberId: Long? = null, val contactGrpInvSent: Boolean, + val groupDirectInv: GroupDirectInvitation? = null, val chatTags: List, val chatItemTTL: Long?, override val chatDeleted: Boolean, @@ -1519,29 +1776,28 @@ data class Contact( override val ready get() = activeConn?.connStatus == ConnStatus.Ready val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady val active get() = contactStatus == ContactStatus.Active - override val userCantSendReason: Pair? - get() { - // TODO [short links] this will have additional statuses for pending contact requests before they are accepted - if (nextSendGrpInv) return null - if (!active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null - if (!sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null - if (activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null - if (activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null - return null - } - override val sendMsgEnabled get() = userCantSendReason == null + override val nextConnect get() = sendMsgToConnect val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent + override val nextConnectPrepared get() = active && preparedContact != null && (activeConn == null || activeConn.connStatus == ConnStatus.Prepared) + override val profileChangeProhibited get() = activeConn != null + val nextAcceptContactRequest get() = + active && + (contactRequestId != null || groupDirectInv != null) && + (activeConn == null || activeConn.connStatus == ConnStatus.New || activeConn.connStatus == ConnStatus.Prepared) + val sendMsgToConnect get() = nextSendGrpInv || nextConnectPrepared override val incognito get() = contactConnIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { ChatFeature.TimedMessages -> mergedPreferences.timedMessages.enabled.forUser ChatFeature.FullDelete -> mergedPreferences.fullDelete.enabled.forUser ChatFeature.Reactions -> mergedPreferences.reactions.enabled.forUser ChatFeature.Voice -> mergedPreferences.voice.enabled.forUser + ChatFeature.Files -> mergedPreferences.files.enabled.forUser ChatFeature.Calls -> mergedPreferences.calls.enabled.forUser } override val timedMessagesTTL: Int? get() = with(mergedPreferences.timedMessages) { if (enabled.forUser) userPreference.pref.ttl else null } override val displayName get() = localAlias.ifEmpty { profile.displayName } override val fullName get() = profile.fullName + override val shortDescr get() = profile.shortDescr override val image get() = profile.image val contactLink: String? = profile.contactLink override val localAlias get() = profile.localAlias @@ -1552,7 +1808,6 @@ data class Contact( return profile.chatViewName.lowercase().contains(s) || profile.displayName.lowercase().contains(s) || profile.fullName.lowercase().contains(s) } - val directOrUsed: Boolean get() = if (activeConn != null) { (activeConn.connLevel == 0 && !activeConn.viaGroupLink) || contactUsed @@ -1560,13 +1815,22 @@ data class Contact( true } - val contactConnIncognito = + val isContactCard: Boolean get() = + (activeConn == null || activeConn.connStatus == ConnStatus.Prepared) && profile.contactLink != null && active && preparedContact == null && contactRequestId == null + + val isBot: Boolean get() = profile.peerType == ChatPeerType.Bot + + val contactConnIncognito: Boolean get() = activeConn?.customUserProfileId != null + val chatIconName: ImageResource get() = + if (isBot) MR.images.ic_cube else MR.images.ic_account_circle_filled + fun allowsFeature(feature: ChatFeature): Boolean = when (feature) { ChatFeature.TimedMessages -> mergedPreferences.timedMessages.contactPreference.allow != FeatureAllowed.NO ChatFeature.FullDelete -> mergedPreferences.fullDelete.contactPreference.allow != FeatureAllowed.NO ChatFeature.Voice -> mergedPreferences.voice.contactPreference.allow != FeatureAllowed.NO + ChatFeature.Files -> mergedPreferences.files.contactPreference.allow != FeatureAllowed.NO ChatFeature.Reactions -> mergedPreferences.reactions.contactPreference.allow != FeatureAllowed.NO ChatFeature.Calls -> mergedPreferences.calls.contactPreference.allow != FeatureAllowed.NO } @@ -1576,6 +1840,7 @@ data class Contact( ChatFeature.FullDelete -> mergedPreferences.fullDelete.userPreference.pref.allow != FeatureAllowed.NO ChatFeature.Reactions -> mergedPreferences.reactions.userPreference.pref.allow != FeatureAllowed.NO ChatFeature.Voice -> mergedPreferences.voice.userPreference.pref.allow != FeatureAllowed.NO + ChatFeature.Files -> mergedPreferences.files.userPreference.pref.allow != FeatureAllowed.NO ChatFeature.Calls -> mergedPreferences.calls.userPreference.pref.allow != FeatureAllowed.NO } @@ -1593,6 +1858,8 @@ data class Contact( createdAt = Clock.System.now(), updatedAt = Clock.System.now(), chatTs = Clock.System.now(), + preparedContact = null, + contactRequestId = null, contactGrpInvSent = false, chatDeleted = false, uiThemes = null, @@ -1608,6 +1875,30 @@ data class NavigationInfo( val afterTotal: Int = 0 ) +@Serializable +data class PreparedContact ( + val connLinkToConnect: CreatedConnLink, + val uiConnLinkType: ConnectionMode +) + +@Serializable +data class GroupDirectInvitation ( + val groupDirectInvLink: String, + val fromGroupId_: Long?, + val fromGroupMemberId_: Long?, + val fromGroupMemberConnId_: Long?, + val groupDirectInvStartedConnection: Boolean +) { + val memberRemoved: Boolean + get() = fromGroupId_ == null || fromGroupMemberId_ == null || fromGroupMemberConnId_ == null +} + +@Serializable +enum class ConnectionMode { + @SerialName("inv") Inv, + @SerialName("con") Con +} + @Serializable enum class ContactStatus { @SerialName("active") Active, @@ -1675,22 +1966,25 @@ data class SecurityCode(val securityCode: String, val verifiedAt: Instant) data class Profile( override val displayName: String, override val fullName: String, + override val shortDescr: String?, override val image: String? = null, override val localAlias : String = "", val contactLink: String? = null, - val preferences: ChatPreferences? = null + val preferences: ChatPreferences? = null, + val peerType: ChatPeerType? = null ): NamedChat { val profileViewName: String get() { return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" } - fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, image, localAlias, contactLink, preferences) + fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType) companion object { val sampleData = Profile( displayName = "alice", - fullName = "Alice" + fullName = "Alice", + shortDescr = null, ) } } @@ -1700,26 +1994,35 @@ data class LocalProfile( val profileId: Long, override val displayName: String, override val fullName: String, + override val shortDescr: String?, override val image: String? = null, override val localAlias: String, val contactLink: String? = null, - val preferences: ChatPreferences? = null + val preferences: ChatPreferences? = null, + val peerType: ChatPeerType? = null ): NamedChat { val profileViewName: String = localAlias.ifEmpty { if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" } - fun toProfile(): Profile = Profile(displayName, fullName, image, localAlias, contactLink, preferences) + fun toProfile(): Profile = Profile(displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType) companion object { val sampleData = LocalProfile( profileId = 1L, displayName = "alice", fullName = "Alice", + shortDescr = null, preferences = ChatPreferences.sampleData, localAlias = "" ) } } +@Serializable +enum class ChatPeerType { + @SerialName("human") Human, + @SerialName("bot") Bot +} + @Serializable data class UserProfileUpdateSummary( val updateSuccesses: Int, @@ -1753,7 +2056,9 @@ data class GroupInfo ( override val createdAt: Instant, override val updatedAt: Instant, val chatTs: Instant?, + val preparedGroup: PreparedGroup?, val uiThemes: ThemeModeOverrides? = null, + val membersRequireAttention: Int, val chatTags: List, val chatItemTTL: Long?, override val localAlias: String, @@ -1762,42 +2067,30 @@ data class GroupInfo ( override val id get() = "#$groupId" override val apiId get() = groupId override val ready get() = membership.memberActive + override val nextConnect get() = nextConnectPrepared + override val nextConnectPrepared = if (preparedGroup != null) !preparedGroup.connLinkStartedConnection else false + override val profileChangeProhibited get() = preparedGroup?.connLinkPreparedConnection ?: false override val chatDeleted get() = false - override val userCantSendReason: Pair? get() = - if (membership.memberActive) { - if (membership.memberRole == GroupMemberRole.Observer) { - generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc) - } else { - null - } - } else { - when (membership.memberStatus) { - GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null - GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null - GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null - GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null - else -> generalGetString(MR.strings.cant_send_message_generic) to null - } - } - override val sendMsgEnabled get() = userCantSendReason == null override val incognito get() = membership.memberIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on ChatFeature.FullDelete -> fullGroupPreferences.fullDelete.on ChatFeature.Reactions -> fullGroupPreferences.reactions.on ChatFeature.Voice -> fullGroupPreferences.voice.on(membership) + ChatFeature.Files -> fullGroupPreferences.files.on(membership) ChatFeature.Calls -> false } override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null } override val displayName get() = localAlias.ifEmpty { groupProfile.displayName } override val fullName get() = groupProfile.fullName + override val shortDescr get() = groupProfile.shortDescr override val image get() = groupProfile.image val isOwner: Boolean get() = membership.memberRole == GroupMemberRole.Owner && membership.memberCurrent val canDelete: Boolean - get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrent + get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrentOrPending val canAddMembers: Boolean get() = membership.memberRole >= GroupMemberRole.Admin && membership.memberActive @@ -1805,6 +2098,13 @@ data class GroupInfo ( val canModerate: Boolean get() = membership.memberRole >= GroupMemberRole.Moderator && membership.memberActive + val chatIconName: ImageResource + get() = when (businessChat?.chatType) { + null -> MR.images.ic_supervised_user_circle_filled + BusinessChatType.Business -> MR.images.ic_work_filled_padded + BusinessChatType.Customer -> MR.images.ic_account_circle_filled + } + fun groupFeatureEnabled(feature: GroupFeature): Boolean { val p = fullGroupPreferences return when (feature) { @@ -1831,7 +2131,9 @@ data class GroupInfo ( createdAt = Clock.System.now(), updatedAt = Clock.System.now(), chatTs = Clock.System.now(), + preparedGroup = null, uiThemes = null, + membersRequireAttention = 0, chatTags = emptyList(), localAlias = "", chatItemTTL = null @@ -1839,6 +2141,13 @@ data class GroupInfo ( } } +@Serializable +data class PreparedGroup ( + val connLinkToConnect: CreatedConnLink, + val connLinkPreparedConnection: Boolean, + val connLinkStartedConnection: Boolean +) + @Serializable data class GroupRef(val groupId: Long, val localDisplayName: String) @@ -1846,19 +2155,55 @@ data class GroupRef(val groupId: Long, val localDisplayName: String) data class GroupProfile ( override val displayName: String, override val fullName: String, + override val shortDescr: String?, val description: String? = null, override val image: String? = null, override val localAlias: String = "", - val groupPreferences: GroupPreferences? = null + val groupPreferences: GroupPreferences? = null, + val memberAdmission: GroupMemberAdmission? = null ): NamedChat { companion object { val sampleData = GroupProfile( displayName = "team", - fullName = "My Team" + fullName = "My Team", + shortDescr = null, ) } } +@Serializable +data class GroupMemberAdmission( + val review: MemberCriteria? = null, +) { + companion object { + val sampleData = GroupMemberAdmission( + review = null, + ) + } +} + +@Serializable +enum class MemberCriteria { + @SerialName("all") All; + + val text: String + get() = when(this) { + MemberCriteria.All -> generalGetString(MR.strings.member_criteria_all) + } +} + +@Serializable +data class ContactShortLinkData ( + val profile: Profile, + val message: MsgContent?, + val business: Boolean +) + +@Serializable +data class GroupShortLinkData ( + val groupProfile: GroupProfile +) + @Serializable data class BusinessChatInfo ( val chatType: BusinessChatType, @@ -1887,7 +2232,9 @@ data class GroupMember ( val memberProfile: LocalProfile, val memberContactId: Long? = null, val memberContactProfileId: Long, - var activeConn: Connection? = null + var activeConn: Connection? = null, + val supportChat: GroupSupportChat? = null, + val memberChatVRange: VersionRange ): NamedChat { val id: String get() = "#$groupId @$groupMemberId" val ready get() = activeConn?.connStatus == ConnStatus.Ready @@ -1901,9 +2248,10 @@ data class GroupMember ( get() { val p = memberProfile val name = p.localAlias.ifEmpty { p.displayName } - return pastMember(name) + return unknownMember(name) } override val fullName: String get() = memberProfile.fullName + override val shortDescr: String? get() = memberProfile.shortDescr override val image: String? get() = memberProfile.image val contactLink: String? = memberProfile.contactLink val verified get() = activeConn?.connectionCode != null @@ -1915,13 +2263,19 @@ data class GroupMember ( get() { val p = memberProfile val name = p.localAlias.ifEmpty { p.displayName + (if (p.fullName == "" || p.fullName == p.displayName) "" else " / ${p.fullName}") } - return pastMember(name) + return unknownMember(name) } - private fun pastMember(name: String): String { - return if (memberStatus == GroupMemberStatus.MemUnknown) - String.format(generalGetString(MR.strings.past_member_vName), name) - else + private fun unknownMember(name: String): String { + return if (memberStatus == GroupMemberStatus.MemUnknown) { + if (memberId.startsWith(name)) { + // unknown member was created using memberId for name + String.format(generalGetString(MR.strings.past_member_vName), name) + } else { + // unknown member was created with name + name + } + } else name } @@ -1935,7 +2289,7 @@ data class GroupMember ( } else { fullName } - return pastMember(name) + return unknownMember(name) } val memberActive: Boolean get() = when (this.memberStatus) { @@ -1946,6 +2300,7 @@ data class GroupMember ( GroupMemberStatus.MemUnknown -> false GroupMemberStatus.MemInvited -> false GroupMemberStatus.MemPendingApproval -> true + GroupMemberStatus.MemPendingReview -> true GroupMemberStatus.MemIntroduced -> false GroupMemberStatus.MemIntroInvited -> false GroupMemberStatus.MemAccepted -> false @@ -1963,6 +2318,7 @@ data class GroupMember ( GroupMemberStatus.MemUnknown -> false GroupMemberStatus.MemInvited -> false GroupMemberStatus.MemPendingApproval -> false + GroupMemberStatus.MemPendingReview -> false GroupMemberStatus.MemIntroduced -> true GroupMemberStatus.MemIntroInvited -> true GroupMemberStatus.MemAccepted -> true @@ -1972,6 +2328,15 @@ data class GroupMember ( GroupMemberStatus.MemCreator -> true } + val memberPending: Boolean get() = when (this.memberStatus) { + GroupMemberStatus.MemPendingApproval -> true + GroupMemberStatus.MemPendingReview -> true + else -> false + } + + val memberCurrentOrPending: Boolean get() = + memberCurrent || memberPending + fun canBeRemoved(groupInfo: GroupInfo): Boolean { val userRole = groupInfo.membership.memberRole return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft @@ -1979,7 +2344,7 @@ data class GroupMember ( } fun canChangeRoleTo(groupInfo: GroupInfo): List? = - if (!canBeRemoved(groupInfo)) null + if (!canBeRemoved(groupInfo) || memberPending) null else groupInfo.membership.memberRole.let { userRole -> GroupMemberRole.selectableRoles.filter { it <= userRole } } @@ -1988,8 +2353,17 @@ data class GroupMember ( val userRole = groupInfo.membership.memberRole return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft && memberRole < GroupMemberRole.Moderator && userRole >= GroupMemberRole.Moderator && userRole >= memberRole && groupInfo.membership.memberActive + && !memberPending } + val supportChatNotRead: Boolean get() = + if (supportChat != null) + supportChat.memberAttention > 0 || supportChat.mentions > 0 || supportChat.unread > 0 + else + false + + val versionRange: VersionRange = activeConn?.peerChatVRange ?: memberChatVRange + val memberIncognito = memberProfile.profileId != memberContactProfileId companion object { @@ -2007,11 +2381,20 @@ data class GroupMember ( memberProfile = LocalProfile.sampleData, memberContactId = 1, memberContactProfileId = 1L, - activeConn = Connection.sampleData + activeConn = Connection.sampleData, + memberChatVRange = VersionRange(minVersion = 1, maxVersion = 15) ) } } +@Serializable +class GroupSupportChat ( + val chatTs: Instant, + val unread: Int, + val memberAttention: Int, + val mentions: Int +) + @Serializable data class GroupMemberSettings(val showMessages: Boolean) {} @@ -2037,7 +2420,7 @@ enum class GroupMemberRole(val memberRole: String) { @SerialName("owner") Owner("owner"); companion object { - val selectableRoles: List = listOf(Observer, Member, Admin, Owner) + val selectableRoles: List = listOf(Observer, Member, Moderator, Admin, Owner) } val text: String get() = when (this) { @@ -2068,6 +2451,7 @@ enum class GroupMemberStatus { @SerialName("unknown") MemUnknown, @SerialName("invited") MemInvited, @SerialName("pending_approval") MemPendingApproval, + @SerialName("pending_review") MemPendingReview, @SerialName("introduced") MemIntroduced, @SerialName("intro-inv") MemIntroInvited, @SerialName("accepted") MemAccepted, @@ -2084,6 +2468,7 @@ enum class GroupMemberStatus { MemUnknown -> generalGetString(MR.strings.group_member_status_unknown) MemInvited -> generalGetString(MR.strings.group_member_status_invited) MemPendingApproval -> generalGetString(MR.strings.group_member_status_pending_approval) + MemPendingReview -> generalGetString(MR.strings.group_member_status_pending_review) MemIntroduced -> generalGetString(MR.strings.group_member_status_introduced) MemIntroInvited -> generalGetString(MR.strings.group_member_status_intro_invitation) MemAccepted -> generalGetString(MR.strings.group_member_status_accepted) @@ -2101,6 +2486,7 @@ enum class GroupMemberStatus { MemUnknown -> generalGetString(MR.strings.group_member_status_unknown_short) MemInvited -> generalGetString(MR.strings.group_member_status_invited) MemPendingApproval -> generalGetString(MR.strings.group_member_status_pending_approval_short) + MemPendingReview -> generalGetString(MR.strings.group_member_status_pending_review_short) MemIntroduced -> generalGetString(MR.strings.group_member_status_connecting) MemIntroInvited -> generalGetString(MR.strings.group_member_status_connecting) MemAccepted -> generalGetString(MR.strings.group_member_status_connecting) @@ -2155,13 +2541,22 @@ class NoteFolder( override val apiId get() = noteFolderId override val chatDeleted get() = false override val ready get() = true - override val userCantSendReason: Pair? = null - override val sendMsgEnabled get() = true + override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = false - override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice + override fun featureEnabled(feature: ChatFeature) = when (feature) { + ChatFeature.TimedMessages -> false + ChatFeature.FullDelete -> false + ChatFeature.Reactions -> false + ChatFeature.Voice -> true + ChatFeature.Files -> true + ChatFeature.Calls -> false + } override val timedMessagesTTL: Int? get() = null override val displayName get() = generalGetString(MR.strings.note_folder_local_display_name) override val fullName get() = "" + override val shortDescr get() = null override val image get() = null override val localAlias get() = "" override val localDisplayName: String get() = "" @@ -2188,17 +2583,19 @@ class UserContactRequest ( override val updatedAt: Instant ): SomeChat, NamedChat { override val chatType get() = ChatType.ContactRequest - override val id get() = "<@$contactRequestId" + override val id get() = contactRequestChatId(contactRequestId) override val apiId get() = contactRequestId override val chatDeleted get() = false override val ready get() = true - override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null - override val sendMsgEnabled get() = false + override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null override val displayName get() = profile.displayName override val fullName get() = profile.fullName + override val shortDescr get() = profile.shortDescr override val image get() = profile.image override val localAlias get() = "" @@ -2214,6 +2611,8 @@ class UserContactRequest ( } } +fun contactRequestChatId(contactRequestId: Long): String = "<@$contactRequestId" + @Serializable class PendingContactConnection( val pccConnId: Long, @@ -2232,8 +2631,9 @@ class PendingContactConnection( override val apiId get() = pccConnId override val chatDeleted get() = false override val ready get() = false - override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null - override val sendMsgEnabled get() = false + override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = customUserProfileId != null override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null @@ -2253,6 +2653,7 @@ class PendingContactConnection( } } override val fullName get() = "" + override val shortDescr get() = null override val image get() = null val initiated get() = (pccConnStatus.initiated ?: false) && !viaContactUri @@ -2451,11 +2852,16 @@ data class ChatItem ( is CIContent.RcvGroupFeature, is CIContent.SndGroupFeature -> CIMergeCategory.ChatFeature is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) { - is RcvGroupEvent.UserRole, is RcvGroupEvent.UserDeleted, is RcvGroupEvent.GroupDeleted, is RcvGroupEvent.MemberCreatedContact -> null + is RcvGroupEvent.UserRole, + is RcvGroupEvent.UserDeleted, + is RcvGroupEvent.GroupDeleted, + is RcvGroupEvent.MemberCreatedContact, + is RcvGroupEvent.NewMemberPendingReview -> + null else -> CIMergeCategory.RcvGroupEvent } is CIContent.SndGroupEventContent -> when (content.sndGroupEvent) { - is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft -> null + is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft, is SndGroupEvent.MemberAccepted, is SndGroupEvent.UserPendingReview -> null else -> CIMergeCategory.SndGroupEvent } else -> { @@ -2493,6 +2899,7 @@ data class ChatItem ( is CIContent.RcvDirectE2EEInfo -> false is CIContent.SndGroupE2EEInfo -> false is CIContent.RcvGroupE2EEInfo -> false + is CIContent.ChatBanner -> false else -> true } @@ -2522,10 +2929,13 @@ data class ChatItem ( is CIContent.RcvDirectEventContent -> when (content.rcvDirectEvent) { is RcvDirectEvent.ContactDeleted -> false is RcvDirectEvent.ProfileUpdated -> false + is RcvDirectEvent.GroupInvLinkReceived -> true } is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) { is RcvGroupEvent.MemberAdded -> false is RcvGroupEvent.MemberConnected -> false + is RcvGroupEvent.MemberAccepted -> false + is RcvGroupEvent.UserAccepted -> false is RcvGroupEvent.MemberLeft -> false is RcvGroupEvent.MemberRole -> false is RcvGroupEvent.MemberBlocked -> false @@ -2537,6 +2947,7 @@ data class ChatItem ( is RcvGroupEvent.InvitedViaGroupLink -> false is RcvGroupEvent.MemberCreatedContact -> false is RcvGroupEvent.MemberProfileUpdated -> false + is RcvGroupEvent.NewMemberPendingReview -> true } is CIContent.SndGroupEventContent -> false is CIContent.RcvConnEventContent -> false @@ -2556,6 +2967,7 @@ data class ChatItem ( is CIContent.RcvDirectE2EEInfo -> false is CIContent.SndGroupE2EEInfo -> false is CIContent.RcvGroupE2EEInfo -> false + is CIContent.ChatBanner -> false is CIContent.InvalidJSON -> false } @@ -2674,6 +3086,7 @@ data class ChatItem ( deletable = false, editable = false, userMention = false, + showGroupAsSender = false, ), content = CIContent.RcvDeleted(deleteMode = CIDeleteMode.cidmBroadcast), quotedItem = null, @@ -2699,6 +3112,7 @@ data class ChatItem ( deletable = false, editable = false, userMention = false, + showGroupAsSender = false ), content = CIContent.SndMsgContent(MsgContent.MCText("")), quotedItem = null, @@ -2718,6 +3132,11 @@ data class ChatItem ( } } +sealed class SecondaryContextFilter { + class GroupChatScopeContext(val groupScopeInfo: GroupChatScopeInfo): SecondaryContextFilter() + class MsgContentTagContext(val contentTag: MsgContentTag): SecondaryContextFilter() +} + fun MutableState>.add(index: Int, elem: Chat) { value = SnapshotStateList().apply { addAll(value); add(index, elem) } } @@ -2839,7 +3258,8 @@ data class CIMeta ( val itemLive: Boolean?, val userMention: Boolean, val deletable: Boolean, - val editable: Boolean + val editable: Boolean, + val showGroupAsSender: Boolean ) { val timestampText: String get() = getTimestampText(itemTs, true) @@ -2878,6 +3298,7 @@ data class CIMeta ( deletable = deletable, editable = editable, userMention = false, + showGroupAsSender = false ) fun invalidJSON(): CIMeta = @@ -2897,7 +3318,8 @@ data class CIMeta ( itemLive = false, deletable = false, editable = false, - userMention = false + userMention = false, + showGroupAsSender = false ) } } @@ -2976,6 +3398,19 @@ sealed class CIStatus { @Serializable @SerialName("rcvRead") class RcvRead: CIStatus() @Serializable @SerialName("invalid") class Invalid(val text: String): CIStatus() + // as in corresponds to SENT response from agent + fun isSent(): Boolean = when(this) { + is SndNew -> false + is SndSent -> true + is SndRcvd -> false + is SndErrorAuth -> true + is CISSndError -> true + is SndWarning -> true + is RcvNew -> false + is RcvRead -> false + is Invalid -> false + } + fun statusIcon( primaryColor: Color, metaColor: Color = CurrentColors.value.colors.secondary, @@ -3015,6 +3450,13 @@ sealed class CIStatus { } } +fun shouldKeepOldSndCIStatus(oldStatus: CIStatus, newStatus: CIStatus): Boolean = + when { + oldStatus is CIStatus.SndRcvd && newStatus !is CIStatus.SndRcvd -> true + oldStatus.isSent() && newStatus is CIStatus.SndNew -> true + else -> false + } + @Serializable sealed class SndError { @Serializable @SerialName("auth") class Auth: SndError() @@ -3196,6 +3638,7 @@ sealed class CIContent: ItemContent { @Serializable @SerialName("rcvDirectE2EEInfo") class RcvDirectE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("sndGroupE2EEInfo") class SndGroupE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("rcvGroupE2EEInfo") class RcvGroupE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } + @Serializable @SerialName("chatBanner") object ChatBanner: CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("invalidJSON") data class InvalidJSON(val json: String): CIContent() { override val msgContent: MsgContent? get() = null } override val text: String get() = when (this) { @@ -3229,9 +3672,17 @@ sealed class CIContent: ItemContent { is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) is SndGroupE2EEInfo -> e2eeInfoNoPQStr is RcvGroupE2EEInfo -> e2eeInfoNoPQStr + is ChatBanner -> "" is InvalidJSON -> "invalid data" } + val hasMsgContent: Boolean get() = + if (msgContent != null) { + (msgContent as MsgContent).text.trim().isNotEmpty() + } else { + false + } + val showMemberName: Boolean get() = when (this) { is RcvMsgContent -> true @@ -3248,7 +3699,7 @@ sealed class CIContent: ItemContent { companion object { fun directE2EEInfoStr(e2EEInfo: E2EEInfo): String = - if (e2EEInfo.pqEnabled) { + if (e2EEInfo.pqEnabled == true) { generalGetString(MR.strings.e2ee_info_pq_short) } else { e2eeInfoNoPQStr @@ -3727,6 +4178,7 @@ sealed class MsgContent { @Serializable(with = MsgContentSerializer::class) class MCVoice(override val text: String, val duration: Int): MsgContent() @Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): MsgContent() @Serializable(with = MsgContentSerializer::class) class MCReport(override val text: String, val reason: ReportReason): MsgContent() + @Serializable(with = MsgContentSerializer::class) class MCChat(override val text: String, val chatLink: MsgChatLink): MsgContent() @Serializable(with = MsgContentSerializer::class) class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent() val isVoice: Boolean get() = @@ -3780,7 +4232,7 @@ enum class CIGroupInvitationStatus { } @Serializable -class E2EEInfo (val pqEnabled: Boolean) {} +class E2EEInfo (val pqEnabled: Boolean?) {} object MsgContentSerializer : KSerializer { override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) { @@ -3807,6 +4259,10 @@ object MsgContentSerializer : KSerializer { element("text") element("reason") }) + element("MCChat", buildClassSerialDescriptor("MCChat") { + element("text") + element("chatLink") + }) element("MCUnknown", buildClassSerialDescriptor("MCUnknown")) } @@ -3841,6 +4297,10 @@ object MsgContentSerializer : KSerializer { val reason = Json.decodeFromString(json["reason"].toString()) MsgContent.MCReport(text, reason) } + "chat" -> { + val chatLink = Json.decodeFromString(json["chatLink"].toString()) + MsgContent.MCChat(text, chatLink) + } else -> MsgContent.MCUnknown(t, text, json) } } else { @@ -3895,6 +4355,12 @@ object MsgContentSerializer : KSerializer { put("text", value.text) put("reason", json.encodeToJsonElement(value.reason)) } + is MsgContent.MCChat -> + buildJsonObject { + put("type", "chat") + put("text", value.text) + put("chatLink", json.encodeToJsonElement(value.chatLink)) + } is MsgContent.MCUnknown -> value.json } encoder.encodeJsonElement(json) @@ -3910,25 +4376,24 @@ enum class MsgContentTag { @SerialName("voice") Voice, @SerialName("file") File, @SerialName("report") Report, + @SerialName("chat") Chat, +} + +@Serializable +sealed class MsgChatLink { + @Serializable @SerialName("contact") data class Contact(val connLink: String, val profile: Profile, val business: Boolean) : MsgChatLink() + @Serializable @SerialName("invitation") data class Invitation(val invLink: String, val profile: Profile) : MsgChatLink() + @Serializable @SerialName("group") data class Group(val connLink: String, val groupProfile: GroupProfile) : MsgChatLink() } @Serializable class FormattedText(val text: String, val format: Format? = null) { - // TODO make it dependent on simplexLinkMode preference - fun link(mode: SimplexLinkMode): String? = when (format) { - is Format.Uri -> if (text.startsWith("http://", ignoreCase = true) || text.startsWith("https://", ignoreCase = true)) text else "https://$text" - is Format.SimplexLink -> if (mode == SimplexLinkMode.BROWSER) text else format.simplexUri - is Format.Email -> "mailto:$text" - is Format.Phone -> "tel:$text" - else -> null - } - - // TODO make it dependent on simplexLinkMode preference - fun viewText(mode: SimplexLinkMode): String = - if (format is Format.SimplexLink && mode == SimplexLinkMode.DESCRIPTION) simplexLinkText(format.linkType, format.smpHosts) else text - - fun simplexLinkText(linkType: SimplexLinkType, smpHosts: List): String = - "${linkType.description} (${String.format(generalGetString(MR.strings.simplex_link_connection), smpHosts.firstOrNull() ?: "?")})" + val linkUri: String? get() = + when (format) { + is Format.Uri -> text + is Format.HyperLink -> format.linkUri + else -> null + } companion object { fun plain(text: String): List = if (text.isEmpty()) emptyList() else listOf(FormattedText(text)) @@ -3944,10 +4409,18 @@ sealed class Format { @Serializable @SerialName("secret") class Secret: Format() @Serializable @SerialName("colored") class Colored(val color: FormatColor): Format() @Serializable @SerialName("uri") class Uri: Format() - @Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val smpHosts: List): Format() + @Serializable @SerialName("hyperLink") class HyperLink(val showText: String?, val linkUri: String): Format() + @Serializable @SerialName("simplexLink") class SimplexLink(val showText: String?, val linkType: SimplexLinkType, val simplexUri: String, val smpHosts: List): Format() { + val simplexLinkText: String get() = + "${linkType.description} $viaHosts" + val viaHosts: String get() = + "(${String.format(generalGetString(MR.strings.simplex_link_connection), smpHosts.firstOrNull() ?: "?")})" + } + @Serializable @SerialName("command") class Command(val commandStr: String): Format() @Serializable @SerialName("mention") class Mention(val memberName: String): Format() @Serializable @SerialName("email") class Email: Format() @Serializable @SerialName("phone") class Phone: Format() + @Serializable @SerialName("unknown") class Unknown: Format() val style: SpanStyle @Composable get() = when (this) { is Bold -> SpanStyle(fontWeight = FontWeight.Bold) @@ -3957,10 +4430,13 @@ sealed class Format { is Secret -> SpanStyle(color = Color.Transparent, background = SecretColor) is Colored -> SpanStyle(color = this.color.uiColor) is Uri -> linkStyle + is HyperLink -> linkStyle is SimplexLink -> linkStyle + is Command -> SpanStyle(color = MaterialTheme.colors.primary, fontFamily = FontFamily.Monospace) is Mention -> SpanStyle(fontWeight = FontWeight.Medium) is Email -> linkStyle is Phone -> linkStyle + is Unknown -> SpanStyle() } val isSimplexLink = this is SimplexLink @@ -3975,13 +4451,15 @@ enum class SimplexLinkType(val linkType: String) { contact("contact"), invitation("invitation"), group("group"), - channel("channel"); + channel("channel"), + relay("relay"); val description: String get() = generalGetString(when (this) { contact -> MR.strings.simplex_link_contact invitation -> MR.strings.simplex_link_invitation group -> MR.strings.simplex_link_group channel -> MR.strings.simplex_link_channel + relay -> MR.strings.simplex_link_relay }) } @@ -4124,10 +4602,12 @@ sealed class MsgErrorType() { sealed class RcvDirectEvent() { @Serializable @SerialName("contactDeleted") class ContactDeleted(): RcvDirectEvent() @Serializable @SerialName("profileUpdated") class ProfileUpdated(val fromProfile: Profile, val toProfile: Profile): RcvDirectEvent() + @Serializable @SerialName("groupInvLinkReceived") class GroupInvLinkReceived(val groupProfile: GroupProfile): RcvDirectEvent() val text: String get() = when (this) { is ContactDeleted -> generalGetString(MR.strings.rcv_direct_event_contact_deleted) is ProfileUpdated -> profileUpdatedText(fromProfile, toProfile) + is GroupInvLinkReceived -> generalGetString(MR.strings.rcv_direct_event_group_inv_link_received).format(groupProfile.displayName) } private fun profileUpdatedText(from: Profile, to: Profile): String = @@ -4153,6 +4633,8 @@ sealed class RcvDirectEvent() { sealed class RcvGroupEvent() { @Serializable @SerialName("memberAdded") class MemberAdded(val groupMemberId: Long, val profile: Profile): RcvGroupEvent() @Serializable @SerialName("memberConnected") class MemberConnected(): RcvGroupEvent() + @Serializable @SerialName("memberAccepted") class MemberAccepted(val groupMemberId: Long, val profile: Profile): RcvGroupEvent() + @Serializable @SerialName("userAccepted") class UserAccepted(): RcvGroupEvent() @Serializable @SerialName("memberLeft") class MemberLeft(): RcvGroupEvent() @Serializable @SerialName("memberRole") class MemberRole(val groupMemberId: Long, val profile: Profile, val role: GroupMemberRole): RcvGroupEvent() @Serializable @SerialName("memberBlocked") class MemberBlocked(val groupMemberId: Long, val profile: Profile, val blocked: Boolean): RcvGroupEvent() @@ -4164,10 +4646,13 @@ sealed class RcvGroupEvent() { @Serializable @SerialName("invitedViaGroupLink") class InvitedViaGroupLink(): RcvGroupEvent() @Serializable @SerialName("memberCreatedContact") class MemberCreatedContact(): RcvGroupEvent() @Serializable @SerialName("memberProfileUpdated") class MemberProfileUpdated(val fromProfile: Profile, val toProfile: Profile): RcvGroupEvent() + @Serializable @SerialName("newMemberPendingReview") class NewMemberPendingReview(): RcvGroupEvent() val text: String get() = when (this) { is MemberAdded -> String.format(generalGetString(MR.strings.rcv_group_event_member_added), profile.profileViewName) is MemberConnected -> generalGetString(MR.strings.rcv_group_event_member_connected) + is MemberAccepted -> String.format(generalGetString(MR.strings.rcv_group_event_member_accepted), profile.profileViewName) + is UserAccepted -> generalGetString(MR.strings.rcv_group_event_user_accepted) is MemberLeft -> generalGetString(MR.strings.rcv_group_event_member_left) is MemberRole -> String.format(generalGetString(MR.strings.rcv_group_event_changed_member_role), profile.profileViewName, role.text) is MemberBlocked -> if (blocked) { @@ -4183,6 +4668,7 @@ sealed class RcvGroupEvent() { is InvitedViaGroupLink -> generalGetString(MR.strings.rcv_group_event_invited_via_your_group_link) is MemberCreatedContact -> generalGetString(MR.strings.rcv_group_event_member_created_contact) is MemberProfileUpdated -> profileUpdatedText(fromProfile, toProfile) + is NewMemberPendingReview -> generalGetString(MR.strings.rcv_group_event_new_member_pending_review) } private fun profileUpdatedText(from: Profile, to: Profile): String = @@ -4207,6 +4693,8 @@ sealed class SndGroupEvent() { @Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): SndGroupEvent() @Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent() @Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): SndGroupEvent() + @Serializable @SerialName("memberAccepted") class MemberAccepted(val groupMemberId: Long, val profile: Profile): SndGroupEvent() + @Serializable @SerialName("userPendingReview") class UserPendingReview(): SndGroupEvent() val text: String get() = when (this) { is MemberRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_member_role), profile.profileViewName, role.text) @@ -4219,6 +4707,8 @@ sealed class SndGroupEvent() { is MemberDeleted -> String.format(generalGetString(MR.strings.snd_group_event_member_deleted), profile.profileViewName) is UserLeft -> generalGetString(MR.strings.snd_group_event_user_left) is GroupUpdated -> generalGetString(MR.strings.snd_group_event_group_profile_updated) + is MemberAccepted -> generalGetString(MR.strings.snd_group_event_member_accepted) + is UserPendingReview -> generalGetString(MR.strings.snd_group_event_user_pending_review) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 979d79c839..ea740f238b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatModel.changingActiveUserMutex +import chat.simplex.common.model.GroupFeature.Files import chat.simplex.common.model.MsgContent.MCUnknown import chat.simplex.common.model.SMPProxyFallback.AllowProtected import chat.simplex.common.model.SMPProxyMode.Always @@ -58,6 +59,9 @@ typealias ChatCtrl = Long // version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core) val CREATE_MEMBER_CONTACT_VERSION = 2 +// support group knocking (MsgScope) +val GROUP_KNOCKING_VERSION = 15 + enum class CallOnLockScreen { DISABLE, SHOW, @@ -100,11 +104,13 @@ class AppPreferences { val privacyProtectScreen = mkBoolPreference(SHARED_PREFS_PRIVACY_PROTECT_SCREEN, true) val privacyAcceptImages = mkBoolPreference(SHARED_PREFS_PRIVACY_ACCEPT_IMAGES, true) val privacyLinkPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS, true) + val privacyLinkPreviewsShowAlert = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS_SHOW_ALERT, true) + val privacySanitizeLinks = mkBoolPreference(SHARED_PREFS_PRIVACY_SANITIZE_LINKS, false) + // TODO remove val privacyChatListOpenLinks = mkEnumPreference(SHARED_PREFS_PRIVACY_CHAT_LIST_OPEN_LINKS, PrivacyChatListOpenLinksMode.ASK) { PrivacyChatListOpenLinksMode.values().firstOrNull { it.name == this } } val simplexLinkMode: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE, SimplexLinkMode.default) val privacyShowChatPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS, true) val privacySaveLastDraft = mkBoolPreference(SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT, true) - val privacyShortLinks = mkBoolPreference(SHARED_PREFS_PRIVACY_SHORT_LINKS, false) val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false) val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true) val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true) @@ -147,8 +153,10 @@ class AppPreferences { val networkHostMode: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.default) val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false) val networkSMPWebPortServers: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_SMP_WEB_PORT_SERVERS, SMPWebPortServers.default) - val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout) - val networkTCPTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT, NetCfg.defaults.tcpTimeout, NetCfg.proxyDefaults.tcpTimeout) + val networkTCPConnectTimeoutBackground = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND, NetCfg.defaults.tcpConnectTimeout.backgroundTimeout, NetCfg.proxyDefaults.tcpConnectTimeout.backgroundTimeout) + val networkTCPConnectTimeoutInteractive = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE, NetCfg.defaults.tcpConnectTimeout.interactiveTimeout, NetCfg.proxyDefaults.tcpConnectTimeout.interactiveTimeout) + val networkTCPTimeoutBackground = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_BACKGROUND, NetCfg.defaults.tcpTimeout.backgroundTimeout, NetCfg.proxyDefaults.tcpTimeout.backgroundTimeout) + val networkTCPTimeoutInteractive = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_INTERACTIVE, NetCfg.defaults.tcpTimeout.interactiveTimeout, NetCfg.proxyDefaults.tcpTimeout.interactiveTimeout) val networkTCPTimeoutPerKb = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB, NetCfg.defaults.tcpTimeoutPerKb, NetCfg.proxyDefaults.tcpTimeoutPerKb) val networkRcvConcurrency = mkIntPreference(SHARED_PREFS_NETWORK_RCV_CONCURRENCY, NetCfg.defaults.rcvConcurrency) val networkSMPPingInterval = mkLongPreference(SHARED_PREFS_NETWORK_SMP_PING_INTERVAL, NetCfg.defaults.smpPingInterval) @@ -163,6 +171,7 @@ class AppPreferences { val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false) val addressCreationCardShown = mkBoolPreference(SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN, false) val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true) + val showReportsInSupportChatAlert = mkBoolPreference(SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT, true) val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null) val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } } val appSkippedUpdate = mkStrPreference(SHARED_PREFS_APP_SKIPPED_UPDATE, "") @@ -245,6 +254,7 @@ class AppPreferences { liveMessageAlertShown to false, showHiddenProfilesNotice to true, showMuteProfileAlert to true, + showReportsInSupportChatAlert to true, showDeleteConversationNotice to true, showDeleteContactNotice to true, ) @@ -267,13 +277,14 @@ class AppPreferences { set = fun(value) = settings.putFloat(prefName, value) ) - private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): SharedPreference { - val d = if (networkUseSocksProxy.get()) proxyDefault else default - return SharedPreference( - get = fun() = settings.getLong(prefName, d), + private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): SharedPreference = + SharedPreference( + get = { + val d = if (networkUseSocksProxy.get()) proxyDefault else default + settings.getLong(prefName, d) + }, set = fun(value) = settings.putLong(prefName, value) ) - } private fun mkBoolPreference(prefName: String, default: Boolean) = SharedPreference( @@ -361,11 +372,12 @@ class AppPreferences { private const val SHARED_PREFS_PRIVACY_ACCEPT_IMAGES = "PrivacyAcceptImages" private const val SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE = "PrivacyTransferImagesInline" private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS = "PrivacyLinkPreviews" - private const val SHARED_PREFS_PRIVACY_CHAT_LIST_OPEN_LINKS = "ChatListOpenLinks" + private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS_SHOW_ALERT = "PrivacyLinkPreviewsShowAlert" + private const val SHARED_PREFS_PRIVACY_SANITIZE_LINKS = "PrivacySanitizeLinks" + private const val SHARED_PREFS_PRIVACY_CHAT_LIST_OPEN_LINKS = "ChatListOpenLinks" // TODO remove private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode" private const val SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS = "PrivacyShowChatPreviews" private const val SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT = "PrivacySaveLastDraft" - private const val SHARED_PREFS_PRIVACY_SHORT_LINKS = "PrivacyShortLinks" private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet" private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles" private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays" @@ -399,8 +411,12 @@ class AppPreferences { private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode" private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode" private const val SHARED_PREFS_NETWORK_SMP_WEB_PORT_SERVERS = "NetworkSMPWebPortServers" - private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout" - private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout" +// private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout" +// private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout" + private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_BACKGROUND = "NetworkTCPConnectTimeoutBackground" + private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT_INTERACTIVE = "NetworkTCPConnectTimeoutInteractive" + private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_BACKGROUND = "NetworkTCPTimeoutBackground" + private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_INTERACTIVE = "NetworkTCPTimeoutInteractive" private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb" private const val SHARED_PREFS_NETWORK_RCV_CONCURRENCY = "networkRcvConcurrency" private const val SHARED_PREFS_NETWORK_SMP_PING_INTERVAL = "NetworkSMPPingInterval" @@ -415,6 +431,7 @@ class AppPreferences { private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown" private const val SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN = "AddressCreationCardShown" private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert" + private const val SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "ShowReportsInSupportChatAlert" private const val SHARED_PREFS_STORE_DB_PASSPHRASE = "StoreDBPassphrase" private const val SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE = "InitialRandomDBPassphrase" private const val SHARED_PREFS_ENCRYPTED_DB_PASSPHRASE = "EncryptedDBPassphrase" @@ -462,7 +479,7 @@ class AppPreferences { } } -private const val MESSAGE_TIMEOUT: Int = 15_000_000 +private const val MESSAGE_TIMEOUT: Int = 300_000_000 object ChatController { var ctrl: ChatCtrl? = -1 @@ -562,7 +579,7 @@ object ChatController { suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? { Log.d(TAG, "startChatWithTemporaryDatabase") - val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) + val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = "", shortDescr = null), ctrl = ctrl) if (!apiSetNetworkConfig(netCfg, ctrl = ctrl)) { Log.e(TAG, "Error setting network config, stopping migration") return null @@ -587,7 +604,7 @@ object ChatController { } } - suspend fun changeActiveUser_(rhId: Long?, toUserId: Long?, viewPwd: String?) { + suspend fun changeActiveUser_(rhId: Long?, toUserId: Long?, viewPwd: String?, keepingChatId: String? = null) { val prevActiveUser = chatModel.currentUser.value val currentUser = changingActiveUserMutex.withLock { (if (toUserId != null) apiSetActiveUser(rhId, toUserId, viewPwd) else apiGetActiveUser(rhId)).also { @@ -600,20 +617,20 @@ object ChatController { val users = listUsers(rhId) chatModel.users.clear() chatModel.users.addAll(users) - getUserChatData(rhId) + getUserChatData(rhId, keepingChatId = keepingChatId) val invitation = chatModel.callInvitations.values.firstOrNull { inv -> inv.user.userId == toUserId } if (invitation != null && currentUser != null) { chatModel.callManager.reportNewIncomingCall(invitation.copy(user = currentUser)) } } - suspend fun getUserChatData(rhId: Long?) { + suspend fun getUserChatData(rhId: Long?, keepingChatId: String? = null) { val hasUser = chatModel.currentUser.value != null chatModel.userAddress.value = if (hasUser) apiGetUserAddress(rhId) else null chatModel.chatItemTTL.value = if (hasUser) getChatItemTTL(rhId) else ChatItemTTL.None withContext(Dispatchers.Main) { val chats = apiGetChats(rhId) - chatModel.chatsContext.updateChats(chats) + chatModel.chatsContext.updateChats(chats, keepingChatId = keepingChatId) } chatModel.userTags.value = apiGetChatTags(rhId).takeIf { hasUser } ?: emptyList() chatModel.activeChatTagFilter.value = null @@ -625,6 +642,7 @@ object ChatController { if (receiverStarted) return receiverStarted = true CoroutineScope(Dispatchers.IO).launch { + var releaseLock: (() -> Unit) = {} while (true) { /** Global [ctrl] can be null. It's needed for having the same [ChatModel] that already made in [ChatController] without the need * to change it everywhere in code after changing a database. @@ -635,7 +653,16 @@ object ChatController { break } try { + val release = releaseLock + // delaying the release of wake lock in order to: + // 1. avoid race condition with the incoming call activity that fails to show if called after wake lock release (primary reason), + // 2. allow any other necessary processing for a bit of time with wakelock held. + launch { + delay(30000) + release() + } val msg = recvMsg(ctrl) + releaseLock = getWakeLock(timeout = 60000) if (msg != null) { val finishedWithoutTimeout = withTimeoutOrNull(60_000L) { processReceivedMsg(msg) @@ -662,7 +689,90 @@ object ChatController { } } - suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, log: Boolean = true): API { + private suspend fun sendCmdWithRetry(rhId: Long?, cmd: CC, inProgress: MutableState? = null, retryNum: Int = 0): API? { + val r = sendCmd(rhId, cmd, retryNum = retryNum) + val alert = if (r is API.Error) retryableNetworkErrorAlert(r.err) else null + if ((inProgress == null || inProgress.value) && alert != null) { + return suspendCancellableCoroutine { cont -> + showRetryAlert( + alert, + onCancel = { + cont.resumeWith(Result.success(null)) + }, + onRetry = { + withLongRunningApi { + cont.resumeWith( + runCatching { + coroutineScope { + sendCmdWithRetry(rhId, cmd, inProgress = inProgress, retryNum = retryNum + 1) + } + } + ) + } + } + ) + + cont.invokeOnCancellation { + cont.resumeWith(Result.success(null)) + } + } + } else { + return r + } + } + + private fun showRetryAlert(alert: Pair, onCancel: () -> Unit, onRetry: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(alert.first), + text = alert.second, + confirmText = generalGetString(MR.strings.retry_verb), + onConfirm = onRetry, + onDismiss = onCancel, + onDismissRequest = onCancel + ) + } + + private fun retryableNetworkErrorAlert(err: ChatError): Pair? { + if (err !is ChatError.ChatErrorAgent) return null + val e = err.agentError + when (e) { + is AgentErrorType.BROKER -> { + val message = { String.format(generalGetString(MR.strings.network_error_desc), serverHostname(e.brokerAddress)) } + return when (e.brokerErr) { + is BrokerErrorType.TIMEOUT -> MR.strings.connection_timeout to message() + is BrokerErrorType.NETWORK -> if (e.brokerErr.networkError is NetworkError.UnknownCAError) null else MR.strings.connection_error to message() + else -> null + } + } + is AgentErrorType.SMP -> + if (e.smpErr is SMPErrorType.PROXY && e.smpErr.proxyErr is ProxyError.BROKER) { + val message = { String.format(generalGetString(MR.strings.smp_proxy_error_connecting), serverHostname(e.serverAddress)) } + return when (e.smpErr.proxyErr.brokerErr) { + is BrokerErrorType.TIMEOUT -> MR.strings.private_routing_timeout to message() + is BrokerErrorType.NETWORK -> if (e.smpErr.proxyErr.brokerErr.networkError is NetworkError.UnknownCAError) null else MR.strings.private_routing_error to message() + else -> null + } + } + is AgentErrorType.PROXY -> + if (e.proxyErr is ProxyClientError.ProxyProtocolError && e.proxyErr.protocolErr is SMPErrorType.PROXY) { + val message = { String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(e.proxyServer), serverHostname(e.relayServer)) } + return when (e.proxyErr.protocolErr.proxyErr) { + is ProxyError.BROKER -> + when (e.proxyErr.protocolErr.proxyErr.brokerErr) { + is BrokerErrorType.TIMEOUT -> MR.strings.private_routing_timeout to message() + is BrokerErrorType.NETWORK -> if (e.proxyErr.protocolErr.proxyErr.brokerErr.networkError is NetworkError.UnknownCAError) null else MR.strings.private_routing_error to message() + else -> null + } + is ProxyError.NO_SESSION -> MR.strings.private_routing_no_session to message() + else -> null + } + } + else -> return null + } + return null + } + + suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, retryNum: Int = 0, log: Boolean = true): API { val ctrl = otherCtrl ?: ctrl ?: throw Exception("Controller is not initialized") return withContext(Dispatchers.IO) { @@ -671,7 +781,7 @@ object ChatController { chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) Log.d(TAG, "sendCmd: ${cmd.cmdType}") } - val rStr = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) + val rStr = if (rhId == null) chatSendCmdRetry(ctrl, c, retryNum) else chatSendRemoteCmdRetry(ctrl, rhId.toInt(), c, retryNum) // coroutine was cancelled already, no need to process response (helps with apiListMembers - very heavy query in large groups) interruptIfCancelled() val r = json.decodeFromString(rStr) @@ -763,6 +873,12 @@ object ChatController { throw Exception("failed to set receipts for user groups ${r.responseType} ${r.details}") } + suspend fun apiSetUserAutoAcceptMemberContacts(u: User, enable: Boolean) { + val r = sendCmd(u.remoteHostId, CC.ApiSetUserAutoAcceptMemberContacts(u.userId, enable)) + if (r.result is CR.CmdOk) return + throw Exception("failed to set auto-accept ${r.responseType} ${r.details}") + } + suspend fun apiHideUser(u: User, viewPwd: String): User = setUserPrivacy(u.remoteHostId, CC.ApiHideUser(u.userId, viewPwd)) @@ -882,8 +998,8 @@ object ChatController { return null } - suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair? { - val r = sendCmd(rh, CC.ApiGetChat(type, id, contentTag, pagination, search)) + suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair? { + val r = sendCmd(rh, CC.ApiGetChat(type, id, scope, contentTag, pagination, search)) if (r is API.Result && r.res is CR.ApiChat) return if (rh == null) r.res.chat to r.res.navInfo else r.res.chat.copy(remoteHostId = rh) to r.res.navInfo Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}") val e = (r as? API.Error)?.err @@ -917,8 +1033,8 @@ object ChatController { suspend fun apiReorderChatTags(rh: Long?, tagIds: List) = sendCommandOkResp(rh, CC.ApiReorderChatTags(tagIds)) - suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, live: Boolean = false, ttl: Int? = null, composedMessages: List): List? { - val cmd = CC.ApiSendMessages(type, id, live, ttl, composedMessages) + suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, live: Boolean = false, ttl: Int? = null, composedMessages: List): List? { + val cmd = CC.ApiSendMessages(type, id, scope, live, ttl, composedMessages) return processSendMessageCmd(rh, cmd) } @@ -969,27 +1085,27 @@ object ChatController { return null } - suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? { - val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId)) + suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long): ChatItemInfo? { + val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, scope, itemId)) if (r is API.Result && r.res is CR.ApiChatItemInfo) return r.res.chatItemInfo apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r) return null } - suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemIds: List, ttl: Int?): List? { - val cmd = CC.ApiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl) + suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Long, fromScope: GroupChatScope?, itemIds: List, ttl: Int?): List? { + val cmd = CC.ApiForwardChatItems(toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl) return processSendMessageCmd(rh, cmd)?.map { it.chatItem } } - suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, chatItemIds: List): CR.ForwardPlan? { - val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds)) + suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, fromScope: GroupChatScope?, chatItemIds: List): CR.ForwardPlan? { + val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, fromScope, chatItemIds)) if (r is API.Result && r.res is CR.ForwardPlan) return r.res apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r) return null } - suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? { - val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, updatedMessage, live)) + suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? { + val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, scope, itemId, updatedMessage, live)) when { r is API.Result && r.res is CR.ChatItemUpdated -> return r.res.chatItem r is API.Result && r.res is CR.ChatItemNotChanged -> return r.res.chatItem @@ -1011,8 +1127,8 @@ object ChatController { return null } - suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? { - val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, itemId, add, reaction)) + suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? { + val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, scope, itemId, add, reaction)) if (r is API.Result && r.res is CR.ChatItemReaction) return r.res.reaction.chatReaction.chatItem Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}") return null @@ -1026,8 +1142,8 @@ object ChatController { return null } - suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, itemIds: List, mode: CIDeleteMode): List? { - val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemIds, mode)) + suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List, mode: CIDeleteMode): List? { + val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, scope, itemIds, mode)) if (r is API.Result && r.res is CR.ChatItemsDeleted) return r.res.chatItemDeletions Log.e(TAG, "apiDeleteChatItem bad response: ${r.responseType} ${r.details}") return null @@ -1204,16 +1320,16 @@ object ChatController { } suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { - val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) + val r = sendCmdWithRetry(rh, CC.APIContactQueueInfo(contactId)) if (r is API.Result && r.res is CR.QueueInfoR) return r.res.rcvMsgInfo to r.res.queueInfo - apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) + if (r != null) apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) return null } suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { - val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) + val r = sendCmdWithRetry(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) if (r is API.Result && r.res is CR.QueueInfoR) return r.res.rcvMsgInfo to r.res.queueInfo - apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) + if (r != null) apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) return null } @@ -1289,10 +1405,10 @@ object ChatController { suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair?, (() -> Unit)?> { val userId = try { currentUserId("apiAddContact") } catch (e: Exception) { return null to null } - val short = appPrefs.privacyShortLinks.get() - val r = sendCmd(rh, CC.APIAddContact(userId, short = short, incognito = incognito)) + val r = sendCmdWithRetry(rh, CC.APIAddContact(userId, incognito = incognito)) return when { r is API.Result && r.res is CR.Invitation -> (r.res.connLinkInvitation to r.res.connection) to null + r == null -> null to null !(networkErrorAlert(r)) -> null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) } else -> null to null } @@ -1308,25 +1424,26 @@ object ChatController { } suspend fun apiChangeConnectionUser(rh: Long?, connId: Long, userId: Long): PendingContactConnection? { - val r = sendCmd(rh, CC.ApiChangeConnectionUser(connId, userId)) + val r = sendCmdWithRetry(rh, CC.ApiChangeConnectionUser(connId, userId)) if (r is API.Result && r.res is CR.ConnectionUserChanged) return r.res.toConnection + if (r == null) return null if (!(networkErrorAlert(r))) { apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r) } return null } - suspend fun apiConnectPlan(rh: Long?, connLink: String): Pair? { + suspend fun apiConnectPlan(rh: Long?, connLink: String, inProgress: MutableState): Pair? { val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null } - val r = sendCmd(rh, CC.APIConnectPlan(userId, connLink)) + val r = sendCmdWithRetry(rh, CC.APIConnectPlan(userId, connLink), inProgress = inProgress) if (r is API.Result && r.res is CR.CRConnectionPlan) return r.res.connLink to r.res.connectionPlan - apiConnectResponseAlert(r) + if (inProgress.value && r != null) apiConnectResponseAlert(r) return null } suspend fun apiConnect(rh: Long?, incognito: Boolean, connLink: CreatedConnLink): PendingContactConnection? { val userId = try { currentUserId("apiConnect") } catch (e: Exception) { return null } - val r = sendCmd(rh, CC.APIConnect(userId, incognito, connLink)) + val r = sendCmdWithRetry(rh, CC.APIConnect(userId, incognito, connLink)) when { r is API.Result && r.res is CR.SentConfirmation -> return r.res.connection r is API.Result && r.res is CR.SentInvitation -> return r.res.connection @@ -1335,7 +1452,7 @@ object ChatController { generalGetString(MR.strings.contact_already_exists), String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), r.res.contact.displayName) ) - else -> apiConnectResponseAlert(r) + r != null -> apiConnectResponseAlert(r) } return null } @@ -1388,10 +1505,65 @@ object ChatController { } } + suspend fun apiPrepareContact(rh: Long?, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData): Chat? { + val userId = try { currentUserId("apiPrepareContact") } catch (e: Exception) { return null } + val r = sendCmd(rh, CC.APIPrepareContact(userId, connLink, contactShortLinkData)) + if (r is API.Result && r.res is CR.NewPreparedChat) return r.res.chat + Log.e(TAG, "apiPrepareContact bad response: ${r.responseType} ${r.details}") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_contact), "${r.responseType}: ${r.details}") + return null + } + + suspend fun apiPrepareGroup(rh: Long?, connLink: CreatedConnLink, groupShortLinkData: GroupShortLinkData): Chat? { + val userId = try { currentUserId("apiPrepareGroup") } catch (e: Exception) { return null } + val r = sendCmd(rh, CC.APIPrepareGroup(userId, connLink, groupShortLinkData)) + if (r is API.Result && r.res is CR.NewPreparedChat) return r.res.chat + Log.e(TAG, "apiPrepareGroup bad response: ${r.responseType} ${r.details}") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_group), "${r.responseType}: ${r.details}") + return null + } + + suspend fun apiChangePreparedContactUser(rh: Long?, contactId: Long, newUserId: Long): Contact? { + val r = sendCmd(rh, CC.APIChangePreparedContactUser(contactId, newUserId)) + if (r is API.Result && r.res is CR.ContactUserChanged) return r.res.toContact + Log.e(TAG, "apiChangePreparedContactUser bad response: ${r.responseType} ${r.details}") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_user), "${r.responseType}: ${r.details}") + return null + } + + suspend fun apiChangePreparedGroupUser(rh: Long?, groupId: Long, newUserId: Long): GroupInfo? { + val r = sendCmd(rh, CC.APIChangePreparedGroupUser(groupId, newUserId)) + if (r is API.Result && r.res is CR.GroupUserChanged) return r.res.toGroup + Log.e(TAG, "apiChangePreparedGroupUser bad response: ${r.responseType} ${r.details}") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_user), "${r.responseType}: ${r.details}") + return null + } + + suspend fun apiConnectPreparedContact(rh: Long?, contactId: Long, incognito: Boolean, msg: MsgContent?): Contact? { + val r = sendCmdWithRetry(rh, CC.APIConnectPreparedContact(contactId, incognito, msg)) + if (r is API.Result && r.res is CR.StartedConnectionToContact) return r.res.contact + if (r != null) { + Log.e(TAG, "apiConnectPreparedContact bad response: ${r.responseType} ${r.details}") + apiConnectResponseAlert(r) + } + return null + } + + suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean, msg: MsgContent?): GroupInfo? { + val r = sendCmdWithRetry(rh, CC.APIConnectPreparedGroup(groupId, incognito, msg)) + if (r is API.Result && r.res is CR.StartedConnectionToGroup) return r.res.groupInfo + if (r != null) { + Log.e(TAG, "apiConnectPreparedGroup bad response: ${r.responseType} ${r.details}") + apiConnectResponseAlert(r) + } + return null + } + suspend fun apiConnectContactViaAddress(rh: Long?, incognito: Boolean, contactId: Long): Contact? { val userId = try { currentUserId("apiConnectContactViaAddress") } catch (e: Exception) { return null } - val r = sendCmd(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId)) + val r = sendCmdWithRetry(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId)) if (r is API.Result && r.res is CR.SentInvitationToContact) return r.res.contact + if (r == null) return null if (!(networkErrorAlert(r))) { apiErrorAlert("apiConnectContactViaAddress", generalGetString(MR.strings.connection_error), r) } @@ -1533,10 +1705,11 @@ object ChatController { return false } - suspend fun apiCreateUserAddress(rh: Long?, short: Boolean): CreatedConnLink? { + suspend fun apiCreateUserAddress(rh: Long?): CreatedConnLink? { val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null } - val r = sendCmd(rh, CC.ApiCreateMyAddress(userId, short)) + val r = sendCmdWithRetry(rh, CC.ApiCreateMyAddress(userId)) if (r is API.Result && r.res is CR.UserContactLinkCreated) return r.res.connLinkContact + if (r == null) return null if (!(networkErrorAlert(r))) { apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r) } @@ -1545,8 +1718,9 @@ object ChatController { suspend fun apiDeleteUserAddress(rh: Long?): User? { val userId = try { currentUserId("apiDeleteUserAddress") } catch (e: Exception) { return null } - val r = sendCmd(rh, CC.ApiDeleteMyAddress(userId)) + val r = sendCmdWithRetry(rh, CC.ApiDeleteMyAddress(userId)) if (r is API.Result && r.res is CR.UserContactLinkDeleted) return r.res.user.updateRemoteHostId(rh) + if (r == null) return null Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") return null } @@ -1564,21 +1738,33 @@ object ChatController { return null } - suspend fun userAddressAutoAccept(rh: Long?, autoAccept: AutoAccept?): UserContactLinkRec? { - val userId = kotlin.runCatching { currentUserId("userAddressAutoAccept") }.getOrElse { return null } - val r = sendCmd(rh, CC.ApiAddressAutoAccept(userId, autoAccept)) + suspend fun apiAddMyAddressShortLink(rh: Long?): UserContactLinkRec? { + val userId = kotlin.runCatching { currentUserId("apiAddMyAddressShortLink") }.getOrElse { return null } + val r = sendCmdWithRetry(rh, CC.ApiAddMyAddressShortLink(userId)) + if (r is API.Result && r.res is CR.UserContactLink) return r.res.contactLink + if (r == null) return null + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiAddMyAddressShortLink", generalGetString(MR.strings.error_creating_address), r) + } + return null + } + + suspend fun apiSetUserAddressSettings(rh: Long?, settings: AddressSettings): UserContactLinkRec? { + val userId = kotlin.runCatching { currentUserId("apiSetUserAddressSettings") }.getOrElse { return null } + val r = sendCmdWithRetry(rh, CC.ApiSetAddressSettings(userId, settings)) if (r is API.Result && r.res is CR.UserContactLinkUpdated) return r.res.contactLink if (r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.UserContactLinkNotFound ) { return null } + if (r == null) return null Log.e(TAG, "userAddressAutoAccept bad response: ${r.responseType} ${r.details}") return null } suspend fun apiAcceptContactRequest(rh: Long?, incognito: Boolean, contactReqId: Long): Contact? { - val r = sendCmd(rh, CC.ApiAcceptContact(incognito, contactReqId)) + val r = sendCmdWithRetry(rh, CC.ApiAcceptContact(incognito, contactReqId)) return when { r is API.Result && r.res is CR.AcceptingContactRequest -> r.res.contact r is API.Error && r.err is ChatError.ChatErrorAgent @@ -1590,20 +1776,24 @@ object ChatController { ) null } - else -> { + r != null -> { if (!(networkErrorAlert(r))) { apiErrorAlert("apiAcceptContactRequest", generalGetString(MR.strings.error_accepting_contact_request), r) } null } + else -> null } } - suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Boolean { + suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Contact? { val r = sendCmd(rh, CC.ApiRejectContact(contactReqId)) - if (r is API.Result && r.res is CR.ContactRequestRejected) return true + if (r is API.Result && r.res is CR.ContactRequestRejected) return r.res.contact_ Log.e(TAG, "apiRejectContactRequest bad response: ${r.responseType} ${r.details}") - return false + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiRejectContactRequest", generalGetString(MR.strings.error_rejecting_contact_request), r) + } + return null } suspend fun apiGetCallInvitations(rh: Long?): List { @@ -1660,17 +1850,24 @@ object ChatController { } suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean { - val r = sendCmd(rh, CC.ApiChatRead(type, id)) + val r = sendCmd(rh, CC.ApiChatRead(type, id, scope = null)) if (r.result is CR.CmdOk) return true Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}") return false } - suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, itemIds: List): Boolean { - val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, itemIds)) - if (r.result is CR.CmdOk) return true + suspend fun apiSupportChatRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope): Pair? { + val r = sendCmd(rh, CC.ApiChatRead(type, id, scope)) + if (r is API.Result && r.res is CR.MemberSupportChatRead) return r.res.groupInfo to r.res.member + apiErrorAlert("apiSupportChatRead", generalGetString(MR.strings.error_marking_member_support_chat_read), r) + return null + } + + suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List): ChatInfo? { + val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, scope, itemIds)) + if (r is API.Result && r.res is CR.ItemsReadForChat) return r.res.chatInfo Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}") - return false + return null } suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean { @@ -1874,7 +2071,7 @@ object ChatController { } suspend fun apiJoinGroup(rh: Long?, groupId: Long) { - val r = sendCmd(rh, CC.ApiJoinGroup(groupId)) + val r = sendCmdWithRetry(rh, CC.ApiJoinGroup(groupId)) when { r is API.Result && r.res is CR.UserAcceptedGroupSent -> withContext(Dispatchers.Main) { @@ -1895,13 +2092,29 @@ object ChatController { apiErrorAlert("apiJoinGroup", generalGetString(MR.strings.error_joining_group), r) } } - else -> apiErrorAlert("apiJoinGroup", generalGetString(MR.strings.error_joining_group), r) + r != null -> apiErrorAlert("apiJoinGroup", generalGetString(MR.strings.error_joining_group), r) } } - suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): List? { + suspend fun apiAcceptMember(rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole): Pair? { + val r = sendCmd(rh, CC.ApiAcceptMember(groupId, groupMemberId, memberRole)) + if (r is API.Result && r.res is CR.MemberAccepted) return r.res.groupInfo to r.res.member + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiAcceptMember", generalGetString(MR.strings.error_accepting_member), r) + } + return null + } + + suspend fun apiDeleteMemberSupportChat(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.ApiDeleteMemberSupportChat(groupId, groupMemberId)) + if (r is API.Result && r.res is CR.MemberSupportChatDeleted) return r.res.groupInfo to r.res.member + apiErrorAlert("apiDeleteMemberSupportChat", generalGetString(MR.strings.error_deleting_member_support_chat), r) + return null + } + + suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): Pair>? { val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages)) - if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.members + if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.groupInfo to r.res.members if (!(networkErrorAlert(r))) { apiErrorAlert("apiRemoveMembers", generalGetString(MR.strings.error_removing_member), r) } @@ -1959,19 +2172,19 @@ object ChatController { } } - suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { - val short = appPrefs.privacyShortLinks.get() - val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole, short)) - if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.connLinkContact to r.res.memberRole + suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? { + val r = sendCmdWithRetry(rh, CC.APICreateGroupLink(groupId, memberRole)) + if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.groupLink + if (r == null) return null if (!(networkErrorAlert(r))) { apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r) } return null } - suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { + suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? { val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole)) - if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole + if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink if (!(networkErrorAlert(r))) { apiErrorAlert("apiGroupLinkMemberRole", generalGetString(MR.strings.error_updating_link_for_group), r) } @@ -1979,21 +2192,32 @@ object ChatController { } suspend fun apiDeleteGroupLink(rh: Long?, groupId: Long): Boolean { - val r = sendCmd(rh, CC.APIDeleteGroupLink(groupId)) + val r = sendCmdWithRetry(rh, CC.APIDeleteGroupLink(groupId)) if (r is API.Result && r.res is CR.GroupLinkDeleted) return true + if (r == null) return false if (!(networkErrorAlert(r))) { apiErrorAlert("apiDeleteGroupLink", generalGetString(MR.strings.error_deleting_link_for_group), r) } return false } - suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair? { + suspend fun apiGetGroupLink(rh: Long?, groupId: Long): GroupLink? { val r = sendCmd(rh, CC.APIGetGroupLink(groupId)) - if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole + if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}") return null } + suspend fun apiAddGroupShortLink(rh: Long?, groupId: Long): GroupLink? { + val r = sendCmdWithRetry(rh, CC.ApiAddGroupShortLink(groupId)) + if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink + if (r == null) return null + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiAddGroupShortLink", generalGetString(MR.strings.error_creating_link_for_group), r) + } + return null + } + suspend fun apiCreateMemberContact(rh: Long?, groupId: Long, groupMemberId: Long): Contact? { val r = sendCmd(rh, CC.APICreateMemberContact(groupId, groupMemberId)) if (r is API.Result && r.res is CR.NewMemberContact) return r.res.contact @@ -2012,6 +2236,16 @@ object ChatController { return null } + suspend fun apiAcceptMemberContact(rh: Long?, contactId: Long): Contact? { + val r = sendCmdWithRetry(rh, CC.APIAcceptMemberContact(contactId)) + if (r is API.Result && r.res is CR.MemberContactAccepted) return r.res.contact + if (r != null) { + Log.e(TAG, "apiAcceptMemberContact bad response: ${r.responseType} ${r.details}") + apiConnectResponseAlert(r) + } + return null + } + suspend fun allowFeatureToContact(rh: Long?, contact: Contact, feature: ChatFeature, param: Int? = null) { val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param) val toContact = apiSetContactPrefs(rh, contact.contactId, prefs) @@ -2151,7 +2385,10 @@ object ChatController { && e.agentError.brokerErr is BrokerErrorType.NETWORK -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error), - String.format(generalGetString(MR.strings.network_error_desc), serverHostname(e.agentError.brokerAddress)) + String.format( + generalGetString(if (e.agentError.brokerErr.networkError is NetworkError.UnknownCAError) MR.strings.network_error_unknown_ca else MR.strings.network_error_desc), + serverHostname(e.agentError.brokerAddress) + ) ) true } @@ -2205,7 +2442,10 @@ object ChatController { && pe.brokerErr is BrokerErrorType.NETWORK -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - String.format(generalGetString(MR.strings.smp_proxy_error_connecting), serverHostname(srvAddr)) + String.format( + generalGetString(if (pe.brokerErr.networkError is NetworkError.UnknownCAError) MR.strings.smp_proxy_error_unknown_ca else MR.strings.smp_proxy_error_connecting), + serverHostname(srvAddr) + ) ) true } @@ -2244,7 +2484,8 @@ object ChatController { && pe.brokerErr is BrokerErrorType.NETWORK -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(proxyServer), serverHostname(relayServer)) + if (pe.brokerErr.networkError is NetworkError.UnknownCAError) String.format(generalGetString(MR.strings.proxy_destination_error_unknown_ca), serverHostname(relayServer)) + else String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(proxyServer), serverHostname(relayServer)) ) true } @@ -2339,17 +2580,25 @@ object ChatController { } is CR.ReceivedContactRequest -> { val contactRequest = r.contactRequest - val cInfo = ChatInfo.ContactRequest(contactRequest) if (active(r.user)) { withContext(Dispatchers.Main) { - if (chatModel.chatsContext.hasChat(rhId, contactRequest.id)) { - chatModel.chatsContext.updateChatInfo(rhId, cInfo) + if (r.chat_ != null) { // means contact request was created with contact, so we need to add/update contact chat + if (chatModel.chatsContext.hasChat(rhId, r.chat_.id)) { + chatModel.chatsContext.updateChatInfo(rhId, r.chat_.chatInfo) + } else { + chatModel.chatsContext.addChat(r.chat_) + } } else { - chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf())) + val cInfo = ChatInfo.ContactRequest(contactRequest) + if (chatModel.chatsContext.hasChat(rhId, contactRequest.id)) { + chatModel.chatsContext.updateChatInfo(rhId, cInfo) + } else { + chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf())) + } } } } - ntfManager.notifyContactRequestReceived(r.user, cInfo) + ntfManager.notifyContactRequestReceived(r.user, ChatInfo.ContactRequest(contactRequest)) } is CR.ContactUpdated -> { if (active(r.user) && chatModel.chatsContext.hasChat(rhId, r.toContact.id)) { @@ -2408,6 +2657,12 @@ object ChatController { chatModel.networkStatuses[s.agentConnId] = s.networkStatus } } + is CR.ChatInfoUpdated -> + if (active(r.user)) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatInfo(rhId, r.chatInfo) + } + } is CR.NewChatItems -> withBGApi { r.chatItems.forEach { chatItem -> val cInfo = chatItem.chatInfo @@ -2418,11 +2673,7 @@ object ChatController { if (cItem.isActiveReport) { chatModel.chatsContext.increaseGroupReportsCounter(rhId, cInfo.id) } - } - withContext(Dispatchers.Main) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem) - } + chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem) } } else if (cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) { withContext(Dispatchers.Main) { @@ -2448,12 +2699,10 @@ object ChatController { val cItem = chatItem.chatItem if (!cItem.isDeletedContent && active(r.user)) { withContext(Dispatchers.Main) { - chatModel.chatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + chatModel.chatsContext.upsertChatItem(rhId, cInfo, cItem) } withContext(Dispatchers.Main) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) - } + chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, cItem) } } } @@ -2465,9 +2714,7 @@ object ChatController { chatModel.chatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) } withContext(Dispatchers.Main) { - if (r.reaction.chatReaction.chatItem.isReport) { - chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) - } + chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) } } } @@ -2510,15 +2757,18 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (cItem.isReport) { - if (toChatItem == null) { - chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem) - } else { - chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem) - } + if (toChatItem == null) { + chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem) + } else { + chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem) } } } + r.chatItemDeletions.lastOrNull()?.deletedChatItem?.chatInfo?.let { updatedChatInfo -> + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatInfo(rhId, updatedChatInfo) + } + } } is CR.GroupChatItemsDeleted -> { groupChatItemsDeleted(rhId, r) @@ -2575,6 +2825,13 @@ object ChatController { chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } } + is CR.MemberAcceptedByOther -> + if (active(r.user)) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) + } + } is CR.DeletedMemberUser -> // TODO update user member if (active(r.user)) { withContext(Dispatchers.Main) { @@ -2592,6 +2849,7 @@ object ChatController { is CR.DeletedMember -> if (active(r.user)) { withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember) if (r.withMessages) { chatModel.chatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) @@ -2607,6 +2865,7 @@ object ChatController { is CR.LeftMember -> if (active(r.user)) { withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } withContext(Dispatchers.Main) { @@ -2655,6 +2914,20 @@ object ChatController { withContext(Dispatchers.Main) { chatModel.chatsContext.updateGroup(rhId, r.groupInfo) } + if ( + chatModel.chatId.value == r.groupInfo.id + && !r.groupInfo.membership.memberPending + && ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT) + && chatModel.secondaryChatsContext.value?.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext + ) { + CoroutineScope(Dispatchers.Default).launch { + delay(1000L) + withContext(Dispatchers.Main) { + chatModel.secondaryChatsContext.value = null + } + ModalManager.end.closeModals() + } + } } is CR.JoinedGroupMember -> if (active(r.user)) { @@ -3016,11 +3289,11 @@ object ChatController { if (activeUser(rh, user)) { val cInfo = aChatItem.chatInfo val cItem = aChatItem.chatItem - withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) } withContext(Dispatchers.Main) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem) - } + chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) + } + withContext(Dispatchers.Main) { + chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem) } } } @@ -3032,7 +3305,7 @@ object ChatController { chatModel.users.addAll(users) return } - val cInfo = ChatInfo.Group(r.groupInfo) + val cInfo = ChatInfo.Group(r.groupInfo, groupChatScope = null) // TODO [knocking] get scope from items? withContext(Dispatchers.Main) { val chatsCtx = chatModel.chatsContext r.chatItemIDs.forEach { itemId -> @@ -3087,11 +3360,11 @@ object ChatController { if (!activeUser(rh, user)) { notify() } else { - val createdChat = withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) } + val createdChat = withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) + } withContext(Dispatchers.Main) { - if (cItem.content.msgContent is MsgContent.MCReport) { - chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem) - } + chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem) } if (createdChat) { notify() @@ -3188,8 +3461,14 @@ object ChatController { val smpProxyMode = appPrefs.networkSMPProxyMode.get() val smpProxyFallback = appPrefs.networkSMPProxyFallback.get() val smpWebPortServers = appPrefs.networkSMPWebPortServers.get() - val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get() - val tcpTimeout = appPrefs.networkTCPTimeout.get() + val tcpConnectTimeout = NetworkTimeout( + backgroundTimeout = appPrefs.networkTCPConnectTimeoutBackground.get(), + interactiveTimeout = appPrefs.networkTCPConnectTimeoutInteractive.get() + ) + val tcpTimeout = NetworkTimeout( + backgroundTimeout = appPrefs.networkTCPTimeoutBackground.get(), + interactiveTimeout = appPrefs.networkTCPTimeoutInteractive.get() + ) val tcpTimeoutPerKb = appPrefs.networkTCPTimeoutPerKb.get() val rcvConcurrency = appPrefs.networkRcvConcurrency.get() val smpPingInterval = appPrefs.networkSMPPingInterval.get() @@ -3232,8 +3511,10 @@ object ChatController { appPrefs.networkSMPProxyMode.set(cfg.smpProxyMode) appPrefs.networkSMPProxyFallback.set(cfg.smpProxyFallback) appPrefs.networkSMPWebPortServers.set(cfg.smpWebPortServers) - appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout) - appPrefs.networkTCPTimeout.set(cfg.tcpTimeout) + appPrefs.networkTCPConnectTimeoutBackground.set(cfg.tcpConnectTimeout.backgroundTimeout) + appPrefs.networkTCPConnectTimeoutInteractive.set(cfg.tcpConnectTimeout.interactiveTimeout) + appPrefs.networkTCPTimeoutBackground.set(cfg.tcpTimeout.backgroundTimeout) + appPrefs.networkTCPTimeoutInteractive.set(cfg.tcpTimeout.interactiveTimeout) appPrefs.networkTCPTimeoutPerKb.set(cfg.tcpTimeoutPerKb) appPrefs.networkRcvConcurrency.set(cfg.rcvConcurrency) appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval) @@ -3276,6 +3557,7 @@ sealed class CC { class SetAllContactReceipts(val enable: Boolean): CC() class ApiSetUserContactReceipts(val userId: Long, val userMsgReceiptSettings: UserMsgReceiptSettings): CC() class ApiSetUserGroupReceipts(val userId: Long, val userMsgReceiptSettings: UserMsgReceiptSettings): CC() + class ApiSetUserAutoAcceptMemberContacts(val userId: Long, val enable: Boolean): CC() class ApiHideUser(val userId: Long, val viewPwd: String): CC() class ApiUnhideUser(val userId: Long, val viewPwd: String): CC() class ApiMuteUser(val userId: Long): CC() @@ -3296,9 +3578,9 @@ sealed class CC { class ApiGetSettings(val settings: AppSettings): CC() class ApiGetChatTags(val userId: Long): CC() class ApiGetChats(val userId: Long): CC() - class ApiGetChat(val type: ChatType, val id: Long, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC() - class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC() - class ApiSendMessages(val type: ChatType, val id: Long, val live: Boolean, val ttl: Int?, val composedMessages: List): CC() + class ApiGetChat(val type: ChatType, val id: Long, val scope: GroupChatScope?, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC() + class ApiGetChatItemInfo(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long): CC() + class ApiSendMessages(val type: ChatType, val id: Long, val scope: GroupChatScope?, val live: Boolean, val ttl: Int?, val composedMessages: List): CC() class ApiCreateChatTag(val tag: ChatTagData): CC() class ApiSetChatTags(val type: ChatType, val id: Long, val tagIds: List): CC() class ApiDeleteChatTag(val tagId: Long): CC() @@ -3306,30 +3588,34 @@ sealed class CC { class ApiReorderChatTags(val tagIds: List): CC() class ApiCreateChatItems(val noteFolderId: Long, val composedMessages: List): CC() class ApiReportMessage(val groupId: Long, val chatItemId: Long, val reportReason: ReportReason, val reportText: String): CC() - class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC() - class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC() + class ApiUpdateChatItem(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC() + class ApiDeleteChatItem(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemIds: List, val mode: CIDeleteMode): CC() class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC() class ApiArchiveReceivedReports(val groupId: Long): CC() class ApiDeleteReceivedReports(val groupId: Long, val itemIds: List, val mode: CIDeleteMode): CC() - class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() + class ApiChatItemReaction(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() class ApiGetReactionMembers(val userId: Long, val groupId: Long, val itemId: Long, val reaction: MsgReaction): CC() - class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List): CC() - class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC() + class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val fromScope: GroupChatScope?, val chatItemIds: List): CC() + class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val toScope: GroupChatScope?, val fromChatType: ChatType, val fromChatId: Long, val fromScope: GroupChatScope?, val itemIds: List, val ttl: Int?): CC() class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC() class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC() class ApiJoinGroup(val groupId: Long): CC() + class ApiAcceptMember(val groupId: Long, val groupMemberId: Long, val memberRole: GroupMemberRole): CC() + class ApiDeleteMemberSupportChat(val groupId: Long, val groupMemberId: Long): CC() class ApiMembersRole(val groupId: Long, val memberIds: List, val memberRole: GroupMemberRole): CC() class ApiBlockMembersForAll(val groupId: Long, val memberIds: List, val blocked: Boolean): CC() class ApiRemoveMembers(val groupId: Long, val memberIds: List, val withMessages: Boolean): CC() class ApiLeaveGroup(val groupId: Long): CC() class ApiListMembers(val groupId: Long): CC() class ApiUpdateGroupProfile(val groupId: Long, val groupProfile: GroupProfile): CC() - class APICreateGroupLink(val groupId: Long, val memberRole: GroupMemberRole, val short: Boolean): CC() + class APICreateGroupLink(val groupId: Long, val memberRole: GroupMemberRole): CC() class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC() class APIDeleteGroupLink(val groupId: Long): CC() class APIGetGroupLink(val groupId: Long): CC() + class ApiAddGroupShortLink(val groupId: Long): CC() class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC() class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC() + class APIAcceptMemberContact(val contactId: Long): CC() class APITestProtoServer(val userId: Long, val server: String): CC() class ApiGetServerOperators(): CC() class ApiSetServerOperators(val operators: List): CC() @@ -3363,10 +3649,16 @@ sealed class CC { class APIGetGroupMemberCode(val groupId: Long, val groupMemberId: Long): CC() class APIVerifyContact(val contactId: Long, val connectionCode: String?): CC() class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC() - class APIAddContact(val userId: Long, val short: Boolean, val incognito: Boolean): CC() + class APIAddContact(val userId: Long, val incognito: Boolean): CC() class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC() class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC() class APIConnectPlan(val userId: Long, val connLink: String): CC() + class APIPrepareContact(val userId: Long, val connLink: CreatedConnLink, val contactShortLinkData: ContactShortLinkData): CC() + class APIPrepareGroup(val userId: Long, val connLink: CreatedConnLink, val groupShortLinkData: GroupShortLinkData): CC() + class APIChangePreparedContactUser(val contactId: Long, val newUserId: Long): CC() + class APIChangePreparedGroupUser(val groupId: Long, val newUserId: Long): CC() + class APIConnectPreparedContact(val contactId: Long, val incognito: Boolean, val msg: MsgContent?): CC() + class APIConnectPreparedGroup(val groupId: Long, val incognito: Boolean, val msg: MsgContent?): CC() class APIConnect(val userId: Long, val incognito: Boolean, val connLink: CreatedConnLink): CC() class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC() class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC() @@ -3379,11 +3671,12 @@ sealed class CC { class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC() class ApiSetUserUIThemes(val userId: Long, val themes: ThemeModeOverrides?): CC() class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC() - class ApiCreateMyAddress(val userId: Long, val short: Boolean): CC() + class ApiCreateMyAddress(val userId: Long): CC() class ApiDeleteMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC() + class ApiAddMyAddressShortLink(val userId: Long): CC() class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC() - class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC() + class ApiSetAddressSettings(val userId: Long, val addressSettings: AddressSettings): CC() class ApiGetCallInvitations: CC() class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC() class ApiRejectCall(val contact: Contact): CC() @@ -3395,8 +3688,8 @@ sealed class CC { class ApiGetNetworkStatuses(): CC() class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC() class ApiRejectContact(val contactReqId: Long): CC() - class ApiChatRead(val type: ChatType, val id: Long): CC() - class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List): CC() + class ApiChatRead(val type: ChatType, val id: Long, val scope: GroupChatScope?): CC() + class ApiChatItemsRead(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemIds: List): CC() class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC() class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC() class CancelFile(val fileId: Long): CC() @@ -3443,6 +3736,7 @@ sealed class CC { val mrs = userMsgReceiptSettings "/_set receipts groups $userId ${onOff(mrs.enable)} clear_overrides=${onOff(mrs.clearOverrides)}" } + is ApiSetUserAutoAcceptMemberContacts -> "/_set accept member contacts $userId ${onOff(enable)}" is ApiHideUser -> "/_hide user $userId ${json.encodeToString(viewPwd)}" is ApiUnhideUser -> "/_unhide user $userId ${json.encodeToString(viewPwd)}" is ApiMuteUser -> "/_mute user $userId" @@ -3468,16 +3762,16 @@ sealed class CC { } else { " content=${contentTag.name.lowercase()}" } - "/_get chat ${chatRef(type, id)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search") + "/_get chat ${chatRef(type, id, scope)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search") } - is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId" + is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id, scope)} $itemId" is ApiSendMessages -> { val msgs = json.encodeToString(composedMessages) val ttlStr = if (ttl != null) "$ttl" else "default" - "/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json $msgs" + "/_send ${chatRef(type, id, scope)} live=${onOff(live)} ttl=${ttlStr} json $msgs" } is ApiCreateChatTag -> "/_create tag ${json.encodeToString(tag)}" - is ApiSetChatTags -> "/_tags ${chatRef(type, id)} ${tagIds.joinToString(",")}" + is ApiSetChatTags -> "/_tags ${chatRef(type, id, scope = null)} ${tagIds.joinToString(",")}" is ApiDeleteChatTag -> "/_delete tag $tagId" is ApiUpdateChatTag -> "/_update tag $tagId ${json.encodeToString(tagData)}" is ApiReorderChatTags -> "/_reorder tags ${tagIds.joinToString(",")}" @@ -3486,35 +3780,39 @@ sealed class CC { "/_create *$noteFolderId json $msgs" } is ApiReportMessage -> "/_report #$groupId $chatItemId reason=${json.encodeToString(reportReason).trim('"')} $reportText" - is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}" - is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}" + is ApiUpdateChatItem -> "/_update item ${chatRef(type, id, scope)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}" + is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id, scope)} ${itemIds.joinToString(",")} ${mode.deleteMode}" is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}" is ApiArchiveReceivedReports -> "/_archive reports #$groupId" is ApiDeleteReceivedReports -> "/_delete reports #$groupId ${itemIds.joinToString(",")} ${mode.deleteMode}" - is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}" + is ApiChatItemReaction -> "/_reaction ${chatRef(type, id, scope)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}" is ApiGetReactionMembers -> "/_reaction members $userId #$groupId $itemId ${json.encodeToString(reaction)}" is ApiForwardChatItems -> { val ttlStr = if (ttl != null) "$ttl" else "default" - "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}" + "/_forward ${chatRef(toChatType, toChatId, toScope)} ${chatRef(fromChatType, fromChatId, fromScope)} ${itemIds.joinToString(",")} ttl=${ttlStr}" } is ApiPlanForwardChatItems -> { - "/_forward plan ${chatRef(fromChatType, fromChatId)} ${chatItemIds.joinToString(",")}" + "/_forward plan ${chatRef(fromChatType, fromChatId, fromScope)} ${chatItemIds.joinToString(",")}" } is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" is ApiJoinGroup -> "/_join #$groupId" + is ApiAcceptMember -> "/_accept member #$groupId $groupMemberId ${memberRole.memberRole}" + is ApiDeleteMemberSupportChat -> "/_delete member chat #$groupId $groupMemberId" is ApiMembersRole -> "/_member role #$groupId ${memberIds.joinToString(",")} ${memberRole.memberRole}" is ApiBlockMembersForAll -> "/_block #$groupId ${memberIds.joinToString(",")} blocked=${onOff(blocked)}" is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")} messages=${onOff(withMessages)}" is ApiLeaveGroup -> "/_leave #$groupId" is ApiListMembers -> "/_members #$groupId" is ApiUpdateGroupProfile -> "/_group_profile #$groupId ${json.encodeToString(groupProfile)}" - is APICreateGroupLink -> "/_create link #$groupId ${memberRole.name.lowercase()} short=${onOff(short)}" + is APICreateGroupLink -> "/_create link #$groupId ${memberRole.name.lowercase()}" is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}" is APIDeleteGroupLink -> "/_delete link #$groupId" is APIGetGroupLink -> "/_get link #$groupId" + is ApiAddGroupShortLink -> "/_short link #$groupId" is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId" is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}" + is APIAcceptMemberContact -> "/_accept member contact @$contactId" is APITestProtoServer -> "/_server test $userId $server" is ApiGetServerOperators -> "/_operators" is ApiSetServerOperators -> "/_operators ${json.encodeToString(operators)}" @@ -3526,13 +3824,13 @@ sealed class CC { is ApiAcceptConditions -> "/_accept_conditions ${conditionsId} ${operatorIds.joinToString(",")}" is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}" is APIGetChatItemTTL -> "/_ttl $userId" - is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id)} ${chatItemTTLStr(seconds)}" + is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id, scope = null)} ${chatItemTTLStr(seconds)}" is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}" is APIGetNetworkConfig -> "/network" is APISetNetworkInfo -> "/_network info ${json.encodeToString(networkInfo)}" is ReconnectServer -> "/reconnect $userId $server" is ReconnectAllServers -> "/reconnect" - is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}" + is APISetChatSettings -> "/_settings ${chatRef(type, id, scope = null)} ${json.encodeToString(chatSettings)}" is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}" is APIContactInfo -> "/_info @$contactId" is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId" @@ -3548,14 +3846,20 @@ sealed class CC { is APIGetGroupMemberCode -> "/_get code #$groupId $groupMemberId" is APIVerifyContact -> "/_verify code @$contactId" + if (connectionCode != null) " $connectionCode" else "" is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else "" - is APIAddContact -> "/_connect $userId short=${onOff(short)} incognito=${onOff(incognito)}" + is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}" is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}" is ApiChangeConnectionUser -> "/_set conn user :$connId $userId" is APIConnectPlan -> "/_connect plan $userId $connLink" + is APIPrepareContact -> "/_prepare contact $userId ${connLink.connFullLink} ${connLink.connShortLink ?: ""} ${json.encodeToString(contactShortLinkData)}" + is APIPrepareGroup -> "/_prepare group $userId ${connLink.connFullLink} ${connLink.connShortLink ?: ""} ${json.encodeToString(groupShortLinkData)}" + is APIChangePreparedContactUser -> "/_set contact user @$contactId $newUserId" + is APIChangePreparedGroupUser -> "/_set group user #$groupId $newUserId" + is APIConnectPreparedContact -> "/_connect contact @$contactId incognito=${onOff(incognito)}${maybeContent(msg)}" + is APIConnectPreparedGroup -> "/_connect group #$groupId incognito=${onOff(incognito)}${maybeContent(msg)}" is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}" is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId" - is ApiDeleteChat -> "/_delete ${chatRef(type, id)} ${chatDeleteMode.cmdString}" - is ApiClearChat -> "/_clear chat ${chatRef(type, id)}" + is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}" + is ApiClearChat -> "/_clear chat ${chatRef(type, id, scope = null)}" is ApiListContacts -> "/_contacts $userId" is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}" is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}" @@ -3564,11 +3868,12 @@ sealed class CC { is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}" is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}" is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}" - is ApiCreateMyAddress -> "/_address $userId short=${onOff(short)}" + is ApiCreateMyAddress -> "/_address $userId" is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiShowMyAddress -> "/_show_address $userId" + is ApiAddMyAddressShortLink -> "/_short_link_address $userId" is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}" - is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}" + is ApiSetAddressSettings -> "/_address_settings $userId ${json.encodeToString(addressSettings)}" is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId" is ApiRejectContact -> "/_reject $contactReqId" is ApiGetCallInvitations -> "/_call get" @@ -3580,9 +3885,9 @@ sealed class CC { is ApiEndCall -> "/_call end @${contact.apiId}" is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}" is ApiGetNetworkStatuses -> "/_network_statuses" - is ApiChatRead -> "/_read chat ${chatRef(type, id)}" - is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}" - is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" + is ApiChatRead -> "/_read chat ${chatRef(type, id, scope)}" + is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id, scope)} ${itemIds.joinToString(",")}" + is ApiChatUnread -> "/_unread chat ${chatRef(type, id, scope = null)} ${onOff(unreadChat)}" is ReceiveFile -> "/freceive $fileId" + " approved_relays=${onOff(userApprovedRelays)}" + @@ -3625,6 +3930,7 @@ sealed class CC { is SetAllContactReceipts -> "setAllContactReceipts" is ApiSetUserContactReceipts -> "apiSetUserContactReceipts" is ApiSetUserGroupReceipts -> "apiSetUserGroupReceipts" + is ApiSetUserAutoAcceptMemberContacts -> "apiSetUserAutoAcceptMemberContacts" is ApiHideUser -> "apiHideUser" is ApiUnhideUser -> "apiUnhideUser" is ApiMuteUser -> "apiMuteUser" @@ -3666,6 +3972,8 @@ sealed class CC { is ApiNewGroup -> "apiNewGroup" is ApiAddMember -> "apiAddMember" is ApiJoinGroup -> "apiJoinGroup" + is ApiAcceptMember -> "apiAcceptMember" + is ApiDeleteMemberSupportChat -> "apiDeleteMemberSupportChat" is ApiMembersRole -> "apiMembersRole" is ApiBlockMembersForAll -> "apiBlockMembersForAll" is ApiRemoveMembers -> "apiRemoveMembers" @@ -3676,8 +3984,10 @@ sealed class CC { is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole" is APIDeleteGroupLink -> "apiDeleteGroupLink" is APIGetGroupLink -> "apiGetGroupLink" + is ApiAddGroupShortLink -> "apiAddGroupShortLink" is APICreateMemberContact -> "apiCreateMemberContact" is APISendMemberContactInvitation -> "apiSendMemberContactInvitation" + is APIAcceptMemberContact -> "apiAcceptMemberContact" is APITestProtoServer -> "testProtoServer" is ApiGetServerOperators -> "apiGetServerOperators" is ApiSetServerOperators -> "apiSetServerOperators" @@ -3715,6 +4025,12 @@ sealed class CC { is ApiSetConnectionIncognito -> "apiSetConnectionIncognito" is ApiChangeConnectionUser -> "apiChangeConnectionUser" is APIConnectPlan -> "apiConnectPlan" + is APIPrepareContact -> "apiPrepareContact" + is APIPrepareGroup -> "apiPrepareGroup" + is APIChangePreparedContactUser -> "apiChangePreparedContactUser" + is APIChangePreparedGroupUser -> "apiChangePreparedGroupUser" + is APIConnectPreparedContact -> "apiConnectPreparedContact" + is APIConnectPreparedGroup -> "apiConnectPreparedGroup" is APIConnect -> "apiConnect" is ApiConnectContactViaAddress -> "apiConnectContactViaAddress" is ApiDeleteChat -> "apiDeleteChat" @@ -3730,8 +4046,9 @@ sealed class CC { is ApiCreateMyAddress -> "apiCreateMyAddress" is ApiDeleteMyAddress -> "apiDeleteMyAddress" is ApiShowMyAddress -> "apiShowMyAddress" + is ApiAddMyAddressShortLink -> "apiAddMyAddressShortLink" is ApiSetProfileAddress -> "apiSetProfileAddress" - is ApiAddressAutoAccept -> "apiAddressAutoAccept" + is ApiSetAddressSettings -> "apiSetAddressSettings" is ApiAcceptContact -> "apiAcceptContact" is ApiRejectContact -> "apiRejectContact" is ApiGetCallInvitations -> "apiGetCallInvitations" @@ -3801,8 +4118,22 @@ sealed class CC { private fun maybePwd(pwd: String?): String = if (pwd == "" || pwd == null) "" else " " + json.encodeToString(pwd) + private fun maybeContent(mc: MsgContent?): String { + return when { + mc is MsgContent.MCText && mc.text.isEmpty() -> "" + mc != null -> " " + mc.cmdString + else -> "" + } + } + companion object { - fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}" + fun chatRef(chatType: ChatType, id: Long, scope: GroupChatScope?) = when (scope) { + null -> "${chatType.type}${id}" + is GroupChatScope.MemberSupport -> when (scope.groupMemberId_) { + null -> "${chatType.type}${id}(_support)" + else -> "${chatType.type}${id}(_support:${scope.groupMemberId_})" + } + } } } @@ -4277,9 +4608,10 @@ data class ProtocolTestFailure( err + " " + generalGetString(MR.strings.error_smp_test_server_auth) testError is AgentErrorType.XFTP && testError.xftpErr is XFTPErrorType.AUTH -> err + " " + generalGetString(MR.strings.error_xftp_test_server_auth) - testError is AgentErrorType.BROKER && testError.brokerErr is BrokerErrorType.NETWORK -> + testError is AgentErrorType.BROKER && testError.brokerErr is BrokerErrorType.NETWORK && testError.brokerErr.networkError is NetworkError.UnknownCAError -> err + " " + generalGetString(MR.strings.error_smp_test_certificate) - else -> err + else -> + err + " " + String.format(generalGetString(MR.strings.error_with_info), testError.toString()) } } } @@ -4330,6 +4662,19 @@ data class ParsedServerAddress ( var parseError: String ) +fun parseSanitizeUri(s: String, safe: Boolean): ParsedUri? { + val parsed = chatParseUri(s, if (safe) 1 else 0) + return runCatching { json.decodeFromString(ParsedUri.serializer(), parsed) } + .onFailure { Log.d(TAG, "parseSanitizeUri decode error: $it") } + .getOrNull() +} + +@Serializable +data class ParsedUri(val uriInfo: UriInfo?, val parseError: String) + +@Serializable +data class UriInfo(val scheme: String, val sanitized: String?) + @Serializable data class NetCfg( val socksProxy: String?, @@ -4340,8 +4685,8 @@ data class NetCfg( val smpProxyMode: SMPProxyMode = SMPProxyMode.default, val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.default, val smpWebPortServers: SMPWebPortServers = SMPWebPortServers.default, - val tcpConnectTimeout: Long, // microseconds - val tcpTimeout: Long, // microseconds + val tcpConnectTimeout: NetworkTimeout, + val tcpTimeout: NetworkTimeout, val tcpTimeoutPerKb: Long, // microseconds val rcvConcurrency: Int, // pool size val tcpKeepAlive: KeepAliveOpts? = KeepAliveOpts.defaults, @@ -4360,8 +4705,8 @@ data class NetCfg( val defaults: NetCfg = NetCfg( socksProxy = null, - tcpConnectTimeout = 25_000_000, - tcpTimeout = 15_000_000, + tcpConnectTimeout = NetworkTimeout(backgroundTimeout = 45_000_000, interactiveTimeout = 15_000_000), + tcpTimeout = NetworkTimeout(backgroundTimeout = 30_000_000, interactiveTimeout = 10_000_000), tcpTimeoutPerKb = 10_000, rcvConcurrency = 12, smpPingInterval = 1200_000_000 @@ -4370,8 +4715,8 @@ data class NetCfg( val proxyDefaults: NetCfg = NetCfg( socksProxy = ":9050", - tcpConnectTimeout = 35_000_000, - tcpTimeout = 20_000_000, + tcpConnectTimeout = NetworkTimeout(backgroundTimeout = 60_000_000, interactiveTimeout = 30_000_000), + tcpTimeout = NetworkTimeout(backgroundTimeout = 40_000_000, interactiveTimeout = 20_000_000), tcpTimeoutPerKb = 15_000, rcvConcurrency = 8, smpPingInterval = 1200_000_000 @@ -4395,6 +4740,12 @@ data class NetCfg( } } +@Serializable +data class NetworkTimeout( + val backgroundTimeout: Long, // microseconds + val interactiveTimeout: Long // microseconds +) + @Serializable data class NetworkProxy( val username: String = "", @@ -4575,14 +4926,18 @@ data class FullChatPreferences( val fullDelete: SimpleChatPreference, val reactions: SimpleChatPreference, val voice: SimpleChatPreference, + val files: SimpleChatPreference, val calls: SimpleChatPreference, + val commands: List ) { fun toPreferences(): ChatPreferences = ChatPreferences( timedMessages = timedMessages, fullDelete = fullDelete, reactions = reactions, voice = voice, - calls = calls + files = files, + calls = calls, + commands = commands, ) companion object { @@ -4591,7 +4946,9 @@ data class FullChatPreferences( fullDelete = SimpleChatPreference(allow = FeatureAllowed.NO), reactions = SimpleChatPreference(allow = FeatureAllowed.YES), voice = SimpleChatPreference(allow = FeatureAllowed.YES), + files = SimpleChatPreference(allow = FeatureAllowed.YES), calls = SimpleChatPreference(allow = FeatureAllowed.YES), + commands = listOf(), ) } } @@ -4602,7 +4959,9 @@ data class ChatPreferences( val fullDelete: SimpleChatPreference?, val reactions: SimpleChatPreference?, val voice: SimpleChatPreference?, + val files: SimpleChatPreference?, val calls: SimpleChatPreference?, + val commands: List?, ) { fun setAllowed(feature: ChatFeature, allowed: FeatureAllowed = FeatureAllowed.YES, param: Int? = null): ChatPreferences = when (feature) { @@ -4610,6 +4969,7 @@ data class ChatPreferences( ChatFeature.FullDelete -> this.copy(fullDelete = SimpleChatPreference(allow = allowed)) ChatFeature.Reactions -> this.copy(reactions = SimpleChatPreference(allow = allowed)) ChatFeature.Voice -> this.copy(voice = SimpleChatPreference(allow = allowed)) + ChatFeature.Files -> this.copy(files = SimpleChatPreference(allow = allowed)) ChatFeature.Calls -> this.copy(calls = SimpleChatPreference(allow = allowed)) } @@ -4619,7 +4979,9 @@ data class ChatPreferences( fullDelete = SimpleChatPreference(allow = FeatureAllowed.NO), reactions = SimpleChatPreference(allow = FeatureAllowed.YES), voice = SimpleChatPreference(allow = FeatureAllowed.YES), + files = SimpleChatPreference(allow = FeatureAllowed.YES), calls = SimpleChatPreference(allow = FeatureAllowed.YES), + commands = null, ) } } @@ -4641,9 +5003,18 @@ data class TimedMessagesPreference( companion object { val ttlValues: List get() = listOf(600, 3600, 86400, 7 * 86400, 30 * 86400, 3 * 30 * 86400, null) + + val profileLevelTTLValues: List + get() = listOf(7 * 86400, 30 * 86400, 3 * 30 * 86400, null) } } +@Serializable +sealed class ChatBotCommand { + @Serializable @SerialName("command") class Command(val keyword: String, val label: String, val params: String?): ChatBotCommand() + @Serializable @SerialName("menu") class Menu(val label: String, val commands: List): ChatBotCommand() +} + @Serializable data class PresentedServersSummary( val statsStartedAt: Instant, @@ -4894,14 +5265,18 @@ data class ContactUserPreferences( val fullDelete: ContactUserPreference, val reactions: ContactUserPreference, val voice: ContactUserPreference, + val files: ContactUserPreference, val calls: ContactUserPreference, + val commands: List?, ) { fun toPreferences(): ChatPreferences = ChatPreferences( timedMessages = timedMessages.userPreference.pref, fullDelete = fullDelete.userPreference.pref, reactions = reactions.userPreference.pref, voice = voice.userPreference.pref, - calls = calls.userPreference.pref + files = files.userPreference.pref, + calls = calls.userPreference.pref, + commands = commands, ) companion object { @@ -4926,11 +5301,17 @@ data class ContactUserPreferences( userPreference = ContactUserPref.User(preference = SimpleChatPreference(allow = FeatureAllowed.YES)), contactPreference = SimpleChatPreference(allow = FeatureAllowed.YES) ), + files = ContactUserPreference( + enabled = FeatureEnabled(forUser = true, forContact = true), + userPreference = ContactUserPref.User(preference = SimpleChatPreference(allow = FeatureAllowed.YES)), + contactPreference = SimpleChatPreference(allow = FeatureAllowed.YES) + ), calls = ContactUserPreference( enabled = FeatureEnabled(forUser = true, forContact = true), userPreference = ContactUserPref.User(preference = SimpleChatPreference(allow = FeatureAllowed.YES)), contactPreference = SimpleChatPreference(allow = FeatureAllowed.YES) ), + commands = null, ) } } @@ -5020,6 +5401,7 @@ enum class ChatFeature: Feature { @SerialName("fullDelete") FullDelete, @SerialName("reactions") Reactions, @SerialName("voice") Voice, + @SerialName("files") Files, @SerialName("calls") Calls; val asymmetric: Boolean get() = when (this) { @@ -5039,6 +5421,7 @@ enum class ChatFeature: Feature { FullDelete -> generalGetString(MR.strings.full_deletion) Reactions -> generalGetString(MR.strings.message_reactions) Voice -> generalGetString(MR.strings.voice_messages) + Files -> generalGetString(MR.strings.files_and_media) Calls -> generalGetString(MR.strings.audio_video_calls) } @@ -5048,6 +5431,7 @@ enum class ChatFeature: Feature { FullDelete -> painterResource(MR.images.ic_delete_forever) Reactions -> painterResource(MR.images.ic_add_reaction) Voice -> painterResource(MR.images.ic_keyboard_voice) + Files -> painterResource(MR.images.ic_draft) Calls -> painterResource(MR.images.ic_call) } @@ -5057,6 +5441,7 @@ enum class ChatFeature: Feature { FullDelete -> painterResource(MR.images.ic_delete_forever_filled) Reactions -> painterResource(MR.images.ic_add_reaction_filled) Voice -> painterResource(MR.images.ic_keyboard_voice_filled) + Files -> painterResource(MR.images.ic_draft_filled) Calls -> painterResource(MR.images.ic_call_filled) } @@ -5077,11 +5462,16 @@ enum class ChatFeature: Feature { FeatureAllowed.YES -> generalGetString(MR.strings.allow_message_reactions_only_if) FeatureAllowed.NO -> generalGetString(MR.strings.prohibit_message_reactions) } - Voice -> when (allowed) { + Voice -> when (allowed) { FeatureAllowed.ALWAYS -> generalGetString(MR.strings.allow_your_contacts_to_send_voice_messages) FeatureAllowed.YES -> generalGetString(MR.strings.allow_voice_messages_only_if) FeatureAllowed.NO -> generalGetString(MR.strings.prohibit_sending_voice_messages) } + Files -> when (allowed) { + FeatureAllowed.ALWAYS -> generalGetString(MR.strings.allow_your_contacts_to_send_files_and_media) + FeatureAllowed.YES -> generalGetString(MR.strings.allow_files_and_media_only_if) + FeatureAllowed.NO -> generalGetString(MR.strings.prohibit_sending_files_and_media) + } Calls -> when (allowed) { FeatureAllowed.ALWAYS -> generalGetString(MR.strings.allow_your_contacts_to_call) FeatureAllowed.YES -> generalGetString(MR.strings.allow_calls_only_if) @@ -5115,6 +5505,12 @@ enum class ChatFeature: Feature { enabled.forContact -> generalGetString(MR.strings.only_your_contact_can_send_voice) else -> generalGetString(MR.strings.voice_prohibited_in_this_chat) } + Files -> when { + enabled.forUser && enabled.forContact -> generalGetString(MR.strings.both_you_and_your_contact_can_send_files) + enabled.forUser -> generalGetString(MR.strings.only_you_can_send_files) + enabled.forContact -> generalGetString(MR.strings.only_your_contact_can_send_files) + else -> generalGetString(MR.strings.files_prohibited_in_this_chat) + } Calls -> when { enabled.forUser && enabled.forContact -> generalGetString(MR.strings.both_you_and_your_contact_can_make_calls) enabled.forUser -> generalGetString(MR.strings.only_you_can_make_calls) @@ -5309,7 +5705,9 @@ data class ContactFeaturesAllowed( val fullDelete: ContactFeatureAllowed, val reactions: ContactFeatureAllowed, val voice: ContactFeatureAllowed, + val files: ContactFeatureAllowed, val calls: ContactFeatureAllowed, + val commands: List?, ) { companion object { val sampleData = ContactFeaturesAllowed( @@ -5318,7 +5716,9 @@ data class ContactFeaturesAllowed( fullDelete = ContactFeatureAllowed.UserDefault(FeatureAllowed.NO), reactions = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES), voice = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES), + files = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES), calls = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES), + commands = null, ) } } @@ -5332,7 +5732,9 @@ fun contactUserPrefsToFeaturesAllowed(contactUserPreferences: ContactUserPrefere fullDelete = contactUserPrefToFeatureAllowed(contactUserPreferences.fullDelete), reactions = contactUserPrefToFeatureAllowed(contactUserPreferences.reactions), voice = contactUserPrefToFeatureAllowed(contactUserPreferences.voice), + files = contactUserPrefToFeatureAllowed(contactUserPreferences.files), calls = contactUserPrefToFeatureAllowed(contactUserPreferences.calls), + commands = contactUserPreferences.commands, ) } @@ -5352,7 +5754,9 @@ fun contactFeaturesAllowedToPrefs(contactFeaturesAllowed: ContactFeaturesAllowed fullDelete = contactFeatureAllowedToPref(contactFeaturesAllowed.fullDelete), reactions = contactFeatureAllowedToPref(contactFeaturesAllowed.reactions), voice = contactFeatureAllowedToPref(contactFeaturesAllowed.voice), + files = contactFeatureAllowedToPref(contactFeaturesAllowed.files), calls = contactFeatureAllowedToPref(contactFeaturesAllowed.calls), + commands = contactFeaturesAllowed.commands, ) fun contactFeatureAllowedToPref(contactFeatureAllowed: ContactFeatureAllowed): SimpleChatPreference? = @@ -5388,6 +5792,7 @@ data class FullGroupPreferences( val simplexLinks: RoleGroupPreference, val reports: GroupPreference, val history: GroupPreference, + val commands: List, ) { fun toGroupPreferences(): GroupPreferences = GroupPreferences( @@ -5400,6 +5805,7 @@ data class FullGroupPreferences( simplexLinks = simplexLinks, reports = reports, history = history, + commands = commands, ) companion object { @@ -5413,6 +5819,7 @@ data class FullGroupPreferences( simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), reports = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON), + commands = listOf() ) } } @@ -5428,6 +5835,7 @@ data class GroupPreferences( val simplexLinks: RoleGroupPreference? = null, val reports: GroupPreference? = null, val history: GroupPreference? = null, + val commands: List? = null ) { companion object { val sampleData = GroupPreferences( @@ -5440,6 +5848,7 @@ data class GroupPreferences( simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), reports = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON), + commands = null, ) } } @@ -5758,12 +6167,18 @@ sealed class CR { @Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR() @Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR() @Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connLink: CreatedConnLink, val connectionPlan: ConnectionPlan): CR() + @Serializable @SerialName("newPreparedChat") class NewPreparedChat(val user: UserRef, val chat: Chat): CR() + @Serializable @SerialName("contactUserChanged") class ContactUserChanged(val user: UserRef, val fromContact: Contact, val newUser: UserRef, val toContact: Contact): CR() + @Serializable @SerialName("groupUserChanged") class GroupUserChanged(val user: UserRef, val fromGroup: GroupInfo, val newUser: UserRef, val toGroup: GroupInfo): CR() @Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR() + @Serializable @SerialName("startedConnectionToContact") class StartedConnectionToContact(val user: UserRef, val contact: Contact): CR() + @Serializable @SerialName("startedConnectionToGroup") class StartedConnectionToGroup(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("sentInvitationToContact") class SentInvitationToContact(val user: UserRef, val contact: Contact, val customUserProfile: Profile?): CR() @Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("contactDeletedByContact") class ContactDeletedByContact(val user: UserRef, val contact: Contact): CR() + @Serializable @SerialName("itemsReadForChat") class ItemsReadForChat(val user: UserRef, val chatInfo: ChatInfo): CR() @Serializable @SerialName("chatCleared") class ChatCleared(val user: UserRef, val chatInfo: ChatInfo): CR() @Serializable @SerialName("userProfileNoChange") class UserProfileNoChange(val user: User): CR() @Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val user: User, val fromProfile: Profile, val toProfile: Profile, val updateSummary: UserProfileUpdateSummary): CR() @@ -5779,9 +6194,9 @@ sealed class CR { @Serializable @SerialName("contactConnected") class ContactConnected(val user: UserRef, val contact: Contact, val userCustomProfile: Profile? = null): CR() @Serializable @SerialName("contactConnecting") class ContactConnecting(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("contactSndReady") class ContactSndReady(val user: UserRef, val contact: Contact): CR() - @Serializable @SerialName("receivedContactRequest") class ReceivedContactRequest(val user: UserRef, val contactRequest: UserContactRequest): CR() + @Serializable @SerialName("receivedContactRequest") class ReceivedContactRequest(val user: UserRef, val contactRequest: UserContactRequest, val chat_: Chat?): CR() @Serializable @SerialName("acceptingContactRequest") class AcceptingContactRequest(val user: UserRef, val contact: Contact): CR() - @Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef): CR() + @Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef, val contactRequest: UserContactRequest, val contact_: Contact?): CR() @Serializable @SerialName("contactUpdated") class ContactUpdated(val user: UserRef, val toContact: Contact): CR() @Serializable @SerialName("groupMemberUpdated") class GroupMemberUpdated(val user: UserRef, val groupInfo: GroupInfo, val fromMember: GroupMember, val toMember: GroupMember): CR() // TODO remove below @@ -5791,6 +6206,7 @@ sealed class CR { // TODO remove above @Serializable @SerialName("networkStatus") class NetworkStatusResp(val networkStatus: NetworkStatus, val connections: List): CR() @Serializable @SerialName("networkStatuses") class NetworkStatuses(val user_: UserRef?, val networkStatuses: List): CR() + @Serializable @SerialName("chatInfoUpdated") class ChatInfoUpdated(val user: UserRef, val chatInfo: ChatInfo): CR() @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @@ -5812,6 +6228,10 @@ sealed class CR { @Serializable @SerialName("receivedGroupInvitation") class ReceivedGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val memberRole: GroupMemberRole): CR() @Serializable @SerialName("groupDeletedUser") class GroupDeletedUser(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("joinedGroupMemberConnecting") class JoinedGroupMemberConnecting(val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val member: GroupMember): CR() + @Serializable @SerialName("memberAccepted") class MemberAccepted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() + @Serializable @SerialName("memberSupportChatRead") class MemberSupportChatRead(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() + @Serializable @SerialName("memberSupportChatDeleted") class MemberSupportChatDeleted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() + @Serializable @SerialName("memberAcceptedByOther") class MemberAcceptedByOther(val user: UserRef, val groupInfo: GroupInfo, val acceptingMember: GroupMember, val member: GroupMember): CR() @Serializable @SerialName("memberRole") class MemberRole(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR() @Serializable @SerialName("membersRoleUser") class MembersRoleUser(val user: UserRef, val groupInfo: GroupInfo, val members: List, val toRole: GroupMemberRole): CR() @Serializable @SerialName("memberBlockedForAll") class MemberBlockedForAll(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val blocked: Boolean): CR() @@ -5825,11 +6245,12 @@ sealed class CR { @Serializable @SerialName("joinedGroupMember") class JoinedGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("connectedToGroupMember") class ConnectedToGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val memberContact: Contact? = null): CR() @Serializable @SerialName("groupUpdated") class GroupUpdated(val user: UserRef, val toGroup: GroupInfo): CR() - @Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR() - @Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR() + @Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink): CR() + @Serializable @SerialName("groupLink") class CRGroupLink(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink): CR() @Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("newMemberContact") class NewMemberContact(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("newMemberContactSentInv") class NewMemberContactSentInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR() + @Serializable @SerialName("memberContactAccepted") class MemberContactAccepted(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("newMemberContactReceivedInv") class NewMemberContactReceivedInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR() // receiving file events @Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted(val user: UserRef, val chatItem: AChatItem): CR() @@ -5850,12 +6271,10 @@ sealed class CR { @Serializable @SerialName("sndFileRcvCancelled") class SndFileRcvCancelled(val user: UserRef, val chatItem_: AChatItem?, val sndFileTransfer: SndFileTransfer): CR() @Serializable @SerialName("sndFileCancelled") class SndFileCancelled(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta, val sndFileTransfers: List): CR() @Serializable @SerialName("sndStandaloneFileCreated") class SndStandaloneFileCreated(val user: UserRef, val fileTransferMeta: FileTransferMeta): CR() // returned by _upload - @Serializable @SerialName("sndFileStartXFTP") class SndFileStartXFTP(val user: UserRef, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta): CR() // not used @Serializable @SerialName("sndFileProgressXFTP") class SndFileProgressXFTP(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta, val sentSize: Long, val totalSize: Long): CR() @Serializable @SerialName("sndFileRedirectStartXFTP") class SndFileRedirectStartXFTP(val user: UserRef, val fileTransferMeta: FileTransferMeta, val redirectMeta: FileTransferMeta): CR() @Serializable @SerialName("sndFileCompleteXFTP") class SndFileCompleteXFTP(val user: UserRef, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta): CR() @Serializable @SerialName("sndStandaloneFileComplete") class SndStandaloneFileComplete(val user: UserRef, val fileTransferMeta: FileTransferMeta, val rcvURIs: List): CR() - @Serializable @SerialName("sndFileCancelledXFTP") class SndFileCancelledXFTP(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta): CR() @Serializable @SerialName("sndFileError") class SndFileError(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta, val errorMessage: String): CR() @Serializable @SerialName("sndFileWarning") class SndFileWarning(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta, val errorMessage: String): CR() // call events @@ -5936,12 +6355,18 @@ sealed class CR { is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated" is ConnectionUserChanged -> "ConnectionUserChanged" is CRConnectionPlan -> "connectionPlan" + is NewPreparedChat -> "newPreparedChat" + is ContactUserChanged -> "contactUserChanged" + is GroupUserChanged -> "groupUserChanged" is SentConfirmation -> "sentConfirmation" is SentInvitation -> "sentInvitation" + is StartedConnectionToContact -> "startedConnectionToContact" + is StartedConnectionToGroup -> "startedConnectionToGroup" is SentInvitationToContact -> "sentInvitationToContact" is ContactAlreadyExists -> "contactAlreadyExists" is ContactDeleted -> "contactDeleted" is ContactDeletedByContact -> "contactDeletedByContact" + is ItemsReadForChat -> "itemsReadForChat" is ChatCleared -> "chatCleared" is UserProfileNoChange -> "userProfileNoChange" is UserProfileUpdated -> "userProfileUpdated" @@ -5967,6 +6392,7 @@ sealed class CR { is ContactSubSummary -> "contactSubSummary" is NetworkStatusResp -> "networkStatus" is NetworkStatuses -> "networkStatuses" + is ChatInfoUpdated -> "chatInfoUpdated" is NewChatItems -> "newChatItems" is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated" is ChatItemUpdated -> "chatItemUpdated" @@ -5987,6 +6413,10 @@ sealed class CR { is ReceivedGroupInvitation -> "receivedGroupInvitation" is GroupDeletedUser -> "groupDeletedUser" is JoinedGroupMemberConnecting -> "joinedGroupMemberConnecting" + is MemberAccepted -> "memberAccepted" + is MemberSupportChatRead -> "memberSupportChatRead" + is MemberSupportChatDeleted -> "memberSupportChatDeleted" + is MemberAcceptedByOther -> "memberAcceptedByOther" is MemberRole -> "memberRole" is MembersRoleUser -> "membersRoleUser" is MemberBlockedForAll -> "memberBlockedForAll" @@ -6001,10 +6431,11 @@ sealed class CR { is ConnectedToGroupMember -> "connectedToGroupMember" is GroupUpdated -> "groupUpdated" is GroupLinkCreated -> "groupLinkCreated" - is GroupLink -> "groupLink" + is CRGroupLink -> "groupLink" is GroupLinkDeleted -> "groupLinkDeleted" is NewMemberContact -> "newMemberContact" is NewMemberContactSentInv -> "newMemberContactSentInv" + is MemberContactAccepted -> "memberContactAccepted" is NewMemberContactReceivedInv -> "newMemberContactReceivedInv" is RcvFileAcceptedSndCancelled -> "rcvFileAcceptedSndCancelled" is StandaloneFileInfo -> "standaloneFileInfo" @@ -6015,7 +6446,6 @@ sealed class CR { is RcvStandaloneFileComplete -> "rcvStandaloneFileComplete" is RcvFileCancelled -> "rcvFileCancelled" is SndStandaloneFileCreated -> "sndStandaloneFileCreated" - is SndFileStartXFTP -> "sndFileStartXFTP" is RcvFileSndCancelled -> "rcvFileSndCancelled" is RcvFileProgressXFTP -> "rcvFileProgressXFTP" is SndFileRedirectStartXFTP -> "sndFileRedirectStartXFTP" @@ -6028,7 +6458,6 @@ sealed class CR { is SndFileProgressXFTP -> "sndFileProgressXFTP" is SndFileCompleteXFTP -> "sndFileCompleteXFTP" is SndStandaloneFileComplete -> "sndStandaloneFileComplete" - is SndFileCancelledXFTP -> "sndFileCancelledXFTP" is SndFileError -> "sndFileError" is SndFileWarning -> "sndFileWarning" is CallInvitations -> "callInvitations" @@ -6104,12 +6533,18 @@ sealed class CR { is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection)) is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" ) is CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nconnectionPlan: ${json.encodeToString(connectionPlan)}") + is NewPreparedChat -> withUser(user, json.encodeToString(chat)) + is ContactUserChanged -> withUser(user, "fromContact: ${json.encodeToString(fromContact)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoContact: ${json.encodeToString(toContact)}") + is GroupUserChanged -> withUser(user, "fromGroup: ${json.encodeToString(fromGroup)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoGroup: ${json.encodeToString(toGroup)}") is SentConfirmation -> withUser(user, json.encodeToString(connection)) is SentInvitation -> withUser(user, json.encodeToString(connection)) + is StartedConnectionToContact -> withUser(user, json.encodeToString(contact)) + is StartedConnectionToGroup -> withUser(user, json.encodeToString(groupInfo)) is SentInvitationToContact -> withUser(user, json.encodeToString(contact)) is ContactAlreadyExists -> withUser(user, json.encodeToString(contact)) is ContactDeleted -> withUser(user, json.encodeToString(contact)) is ContactDeletedByContact -> withUser(user, json.encodeToString(contact)) + is ItemsReadForChat -> withUser(user, json.encodeToString(chatInfo)) is ChatCleared -> withUser(user, json.encodeToString(chatInfo)) is UserProfileNoChange -> withUser(user, noDetails()) is UserProfileUpdated -> withUser(user, json.encodeToString(toProfile)) @@ -6118,16 +6553,16 @@ sealed class CR { is GroupAliasUpdated -> withUser(user, json.encodeToString(toGroup)) is ConnectionAliasUpdated -> withUser(user, json.encodeToString(toConnection)) is ContactPrefsUpdated -> withUser(user, "fromContact: $fromContact\ntoContact: \n${json.encodeToString(toContact)}") - is UserContactLink -> withUser(user, contactLink.responseDetails) - is UserContactLinkUpdated -> withUser(user, contactLink.responseDetails) + is UserContactLink -> withUser(user, json.encodeToString(contactLink)) + is UserContactLinkUpdated -> withUser(user, json.encodeToString(contactLink)) is UserContactLinkCreated -> withUser(user, json.encodeToString(connLinkContact)) is UserContactLinkDeleted -> withUser(user, noDetails()) is ContactConnected -> withUser(user, json.encodeToString(contact)) is ContactConnecting -> withUser(user, json.encodeToString(contact)) is ContactSndReady -> withUser(user, json.encodeToString(contact)) - is ReceivedContactRequest -> withUser(user, json.encodeToString(contactRequest)) + is ReceivedContactRequest -> withUser(user, "contactRequest: ${json.encodeToString(contactRequest)}\nchat_: ${json.encodeToString(chat_)}") is AcceptingContactRequest -> withUser(user, json.encodeToString(contact)) - is ContactRequestRejected -> withUser(user, noDetails()) + is ContactRequestRejected -> withUser(user, "contactRequest: ${json.encodeToString(contactRequest)}\ncontact_: ${json.encodeToString(contact_)}") is ContactUpdated -> withUser(user, json.encodeToString(toContact)) is GroupMemberUpdated -> withUser(user, "groupInfo: $groupInfo\nfromMember: $fromMember\ntoMember: $toMember") is ContactsSubscribed -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}" @@ -6135,6 +6570,7 @@ sealed class CR { is ContactSubSummary -> withUser(user, json.encodeToString(contactSubscriptions)) is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections" is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses)) + is ChatInfoUpdated -> withUser(user, json.encodeToString(chatInfo)) is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) @@ -6155,6 +6591,10 @@ sealed class CR { is ReceivedGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmemberRole: $memberRole") is GroupDeletedUser -> withUser(user, json.encodeToString(groupInfo)) is JoinedGroupMemberConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nmember: $member") + is MemberAccepted -> withUser(user, "groupInfo: $groupInfo\nmember: $member") + is MemberSupportChatRead -> withUser(user, "groupInfo: $groupInfo\nmember: $member") + is MemberSupportChatDeleted -> withUser(user, "groupInfo: $groupInfo\nmember: $member") + is MemberAcceptedByOther -> withUser(user, "groupInfo: $groupInfo\nacceptingMember: $acceptingMember\nmember: $member") is MemberRole -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole") is MembersRoleUser -> withUser(user, "groupInfo: $groupInfo\nmembers: $members\ntoRole: $toRole") is MemberBlockedForAll -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nblocked: $blocked") @@ -6168,11 +6608,12 @@ sealed class CR { is JoinedGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is ConnectedToGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nmemberContact: $memberContact") is GroupUpdated -> withUser(user, json.encodeToString(toGroup)) - is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole") - is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole") + is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink") + is CRGroupLink -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink") is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo)) is NewMemberContact -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") + is MemberContactAccepted -> withUser(user, "contact: $contact") is NewMemberContactReceivedInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") is RcvFileAcceptedSndCancelled -> withUser(user, noDetails()) is StandaloneFileInfo -> json.encodeToString(fileMeta) @@ -6188,7 +6629,6 @@ sealed class CR { is RcvFileWarning -> withUser(user, "chatItem_: ${json.encodeToString(chatItem_)}\nagentError: ${agentError.string}\nrcvFileTransfer: $rcvFileTransfer") is SndFileCancelled -> json.encodeToString(chatItem_) is SndStandaloneFileCreated -> noDetails() - is SndFileStartXFTP -> withUser(user, json.encodeToString(chatItem)) is SndFileComplete -> withUser(user, json.encodeToString(chatItem)) is SndFileRcvCancelled -> withUser(user, json.encodeToString(chatItem_)) is SndFileStart -> withUser(user, json.encodeToString(chatItem)) @@ -6196,7 +6636,6 @@ sealed class CR { is SndFileRedirectStartXFTP -> withUser(user, json.encodeToString(redirectMeta)) is SndFileCompleteXFTP -> withUser(user, json.encodeToString(chatItem)) is SndStandaloneFileComplete -> withUser(user, rcvURIs.size.toString()) - is SndFileCancelledXFTP -> withUser(user, json.encodeToString(chatItem_)) is SndFileError -> withUser(user, "errorMessage: ${json.encodeToString(errorMessage)}\nchatItem: ${json.encodeToString(chatItem_)}") is SndFileWarning -> withUser(user, "errorMessage: ${json.encodeToString(errorMessage)}\nchatItem: ${json.encodeToString(chatItem_)}") is CallInvitations -> "callInvitations: ${json.encodeToString(callInvitations)}" @@ -6278,17 +6717,6 @@ data class CreatedConnLink(val connFullLink: String, val connShortLink: String?) fun simplexChatUri(short: Boolean): String = if (short) connShortLink ?: simplexChatLink(connFullLink) else simplexChatLink(connFullLink) - - companion object { - val nullableStateSaver: Saver> = Saver( - save = { link -> link?.connFullLink to link?.connShortLink }, - restore = { saved -> - val connFullLink = saved.first - if (connFullLink == null) null - else CreatedConnLink(connFullLink = connFullLink, connShortLink = saved.second) - } - ) - } } fun simplexChatLink(uri: String): String = @@ -6305,7 +6733,7 @@ sealed class ConnectionPlan { @Serializable sealed class InvitationLinkPlan { - @Serializable @SerialName("ok") object Ok: InvitationLinkPlan() + @Serializable @SerialName("ok") class Ok(val contactSLinkData_: ContactShortLinkData? = null): InvitationLinkPlan() @Serializable @SerialName("ownLink") object OwnLink: InvitationLinkPlan() @Serializable @SerialName("connecting") class Connecting(val contact_: Contact? = null): InvitationLinkPlan() @Serializable @SerialName("known") class Known(val contact: Contact): InvitationLinkPlan() @@ -6313,7 +6741,7 @@ sealed class InvitationLinkPlan { @Serializable sealed class ContactAddressPlan { - @Serializable @SerialName("ok") object Ok: ContactAddressPlan() + @Serializable @SerialName("ok") class Ok(val contactSLinkData_: ContactShortLinkData? = null): ContactAddressPlan() @Serializable @SerialName("ownLink") object OwnLink: ContactAddressPlan() @Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: ContactAddressPlan() @Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan() @@ -6323,7 +6751,7 @@ sealed class ContactAddressPlan { @Serializable sealed class GroupLinkPlan { - @Serializable @SerialName("ok") object Ok: GroupLinkPlan() + @Serializable @SerialName("ok") class Ok(val groupSLinkData_: GroupShortLinkData? = null): GroupLinkPlan() @Serializable @SerialName("ownLink") class OwnLink(val groupInfo: GroupInfo): GroupLinkPlan() @Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: GroupLinkPlan() @Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val groupInfo_: GroupInfo? = null): GroupLinkPlan() @@ -6432,25 +6860,80 @@ enum class RatchetSyncState { @SerialName("agreed") Agreed } -@Serializable -class UserContactLinkRec(val connLinkContact: CreatedConnLink, val autoAccept: AutoAccept? = null) { - val responseDetails: String get() = "connLinkContact: ${connLinkContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}" +interface SimplexAddress { + val connLinkContact: CreatedConnLink + val shortLinkDataSet: Boolean + val shortLinkLargeDataSet: Boolean + + val shouldBeUpgraded: Boolean get() = connLinkContact.connShortLink == null || !shortLinkDataSet || !shortLinkLargeDataSet } @Serializable -class AutoAccept(val businessAddress: Boolean, val acceptIncognito: Boolean, val autoReply: MsgContent?) { +data class UserContactLinkRec( + override val connLinkContact: CreatedConnLink, + override val shortLinkDataSet: Boolean, + override val shortLinkLargeDataSet: Boolean, + val addressSettings: AddressSettings +): SimplexAddress + +@Serializable +data class AddressSettings( + val businessAddress: Boolean, + val autoAccept: AutoAccept?, + val autoReply: MsgContent? +) + +@Serializable +data class AutoAccept(val acceptIncognito: Boolean) + +@Serializable +data class GroupLink( + val userContactLinkId: Long, + override val connLinkContact: CreatedConnLink, + override val shortLinkDataSet: Boolean, + override val shortLinkLargeDataSet: Boolean, + val groupLinkId: String, + val acceptMemberRole: GroupMemberRole +): SimplexAddress { companion object { - fun cmdString(autoAccept: AutoAccept?): String { - if (autoAccept == null) return "off" - var s = "on" - if (autoAccept.acceptIncognito) { - s += " incognito=on" - } else if (autoAccept.businessAddress) { - s += " business" + val nullableStateSaver: Saver> = Saver( + save = { groupLink -> + if (groupLink == null) return@Saver null + + val conn = groupLink.connLinkContact + val connData = conn.connFullLink to conn.connShortLink + + listOf( + groupLink.userContactLinkId, + connData, + groupLink.shortLinkDataSet, + groupLink.shortLinkLargeDataSet, + groupLink.groupLinkId, + groupLink.acceptMemberRole.name + ) + }, + restore = { saved -> + val list = saved as? List<*> ?: return@Saver null + + val userContactLinkId = list.getOrNull(0) as? Long ?: return@Saver null + val connPair = list.getOrNull(1) as? Pair<*, *> ?: return@Saver null + val connFullLink = connPair.first as? String ?: return@Saver null + val connShortLink = connPair.second as? String + val shortLinkDataSet = list.getOrNull(2) as? Boolean ?: return@Saver null + val shortLinkLargeDataSet = list.getOrNull(3) as? Boolean ?: return@Saver null + val groupLinkId = list.getOrNull(4) as? String ?: return@Saver null + val roleName = list.getOrNull(5) as? String ?: return@Saver null + + GroupLink( + userContactLinkId = userContactLinkId, + connLinkContact = CreatedConnLink(connFullLink, connShortLink), + shortLinkDataSet = shortLinkDataSet, + shortLinkLargeDataSet = shortLinkLargeDataSet, + groupLinkId = groupLinkId, + acceptMemberRole = GroupMemberRole.valueOf(roleName) + ) } - val msg = autoAccept.autoReply ?: return s - return s + " " + msg.cmdString - } + ) } } @@ -6538,6 +7021,7 @@ sealed class ChatErrorType { is InvalidConnReq -> "invalidConnReq" is UnsupportedConnReq -> "unsupportedConnReq" is InvalidChatMessage -> "invalidChatMessage" + is ConnReqMessageProhibited -> "connReqMessageProhibited" is ContactNotReady -> "contactNotReady" is ContactNotActive -> "contactNotActive" is ContactDisabled -> "contactDisabled" @@ -6553,6 +7037,7 @@ sealed class ChatErrorType { is GroupMemberNotActive -> "groupMemberNotActive" is GroupMemberUserRemoved -> "groupMemberUserRemoved" is GroupMemberNotFound -> "groupMemberNotFound" + is GroupHostMemberNotFound -> "groupHostMemberNotFound" is GroupCantResendInvitation -> "groupCantResendInvitation" is GroupInternal -> "groupInternal" is FileNotFound -> "fileNotFound" @@ -6617,6 +7102,7 @@ sealed class ChatErrorType { @Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType() @Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType() @Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType() + @Serializable @SerialName("connReqMessageProhibited") object ConnReqMessageProhibited: ChatErrorType() @Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType() @Serializable @SerialName("contactNotActive") class ContactNotActive(val contact: Contact): ChatErrorType() @Serializable @SerialName("contactDisabled") class ContactDisabled(val contact: Contact): ChatErrorType() @@ -6632,6 +7118,7 @@ sealed class ChatErrorType { @Serializable @SerialName("groupMemberNotActive") object GroupMemberNotActive: ChatErrorType() @Serializable @SerialName("groupMemberUserRemoved") object GroupMemberUserRemoved: ChatErrorType() @Serializable @SerialName("groupMemberNotFound") object GroupMemberNotFound: ChatErrorType() + @Serializable @SerialName("groupHostMemberNotFound") class GroupHostMemberNotFound(val groupId: Long): ChatErrorType() @Serializable @SerialName("groupCantResendInvitation") class GroupCantResendInvitation(val groupInfo: GroupInfo, val contactName: String): ChatErrorType() @Serializable @SerialName("groupInternal") class GroupInternal(val message: String): ChatErrorType() @Serializable @SerialName("fileNotFound") class FileNotFound(val message: String): ChatErrorType() @@ -6692,6 +7179,8 @@ sealed class StoreError { is UserContactLinkNotFound -> "userContactLinkNotFound" is ContactRequestNotFound -> "contactRequestNotFound $contactRequestId" is ContactRequestNotFoundByName -> "contactRequestNotFoundByName $contactName" + is InvalidContactRequestEntity -> "invalidContactRequestEntity $contactRequestId" + is InvalidBusinessChatContactRequest -> "invalidBusinessChatContactRequest" is GroupNotFound -> "groupNotFound $groupId" is GroupNotFoundByName -> "groupNotFoundByName $groupName" is GroupMemberNameNotFound -> "groupMemberNameNotFound $groupId $groupMemberName" @@ -6768,6 +7257,8 @@ sealed class StoreError { @Serializable @SerialName("userContactLinkNotFound") object UserContactLinkNotFound: StoreError() @Serializable @SerialName("contactRequestNotFound") class ContactRequestNotFound(val contactRequestId: Long): StoreError() @Serializable @SerialName("contactRequestNotFoundByName") class ContactRequestNotFoundByName(val contactName: String): StoreError() + @Serializable @SerialName("invalidContactRequestEntity") class InvalidContactRequestEntity(val contactRequestId: Long): StoreError() + @Serializable @SerialName("invalidBusinessChatContactRequest") object InvalidBusinessChatContactRequest: StoreError() @Serializable @SerialName("groupNotFound") class GroupNotFound(val groupId: Long): StoreError() @Serializable @SerialName("groupNotFoundByName") class GroupNotFoundByName(val groupName: String): StoreError() @Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError() @@ -6855,7 +7346,7 @@ sealed class SQLiteError { sealed class AgentErrorType { val string: String get() = when (this) { is CMD -> "CMD ${cmdErr.string} $errContext" - is CONN -> "CONN ${connErr.string}" + is CONN -> "CONN ${connErr.string} $errContext" is SMP -> "SMP ${smpErr.string}" // is NTF -> "NTF ${ntfErr.string}" is XFTP -> "XFTP ${xftpErr.string}" @@ -6868,7 +7359,7 @@ sealed class AgentErrorType { is INACTIVE -> "INACTIVE" } @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType, val errContext: String): AgentErrorType() - @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() + @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType, val errContext: String): AgentErrorType() @Serializable @SerialName("SMP") class SMP(val serverAddress: String, val smpErr: SMPErrorType): AgentErrorType() // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() @Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType() @@ -6925,7 +7416,7 @@ sealed class BrokerErrorType { } @Serializable @SerialName("RESPONSE") class RESPONSE(val smpErr: String): BrokerErrorType() @Serializable @SerialName("UNEXPECTED") object UNEXPECTED: BrokerErrorType() - @Serializable @SerialName("NETWORK") object NETWORK: BrokerErrorType() + @Serializable @SerialName("NETWORK") class NETWORK(val networkError: NetworkError): BrokerErrorType() @Serializable @SerialName("HOST") object HOST: BrokerErrorType() @Serializable @SerialName("TRANSPORT") class TRANSPORT(val transportErr: SMPTransportError): BrokerErrorType() @Serializable @SerialName("TIMEOUT") object TIMEOUT: BrokerErrorType() @@ -7012,6 +7503,16 @@ sealed class ProtocolCommandError { @Serializable @SerialName("NO_QUEUE") object NO_QUEUE: ProtocolCommandError() } +@Serializable +sealed class NetworkError { + @Serializable @SerialName("connectError") class ConnectError(val connectError: String): NetworkError() + @Serializable @SerialName("tLSError") class TLSError(val tlsError: String): NetworkError() + @Serializable @SerialName("unknownCAError") object UnknownCAError: NetworkError() + @Serializable @SerialName("failedError") object FailedError: NetworkError() + @Serializable @SerialName("timeoutError") object TimeoutError: NetworkError() + @Serializable @SerialName("subscribeError") class SubscribeError(val subscribeError: String): NetworkError() +} + @Serializable sealed class SMPTransportError { val string: String get() = when (this) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index 780f8c25b4..7a96bd99d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -32,7 +32,7 @@ val databaseBackend: String = if (appPlatform == AppPlatform.ANDROID) "sqlite" e class FifoQueue(private var capacity: Int) : LinkedList() { override fun add(element: E): Boolean { - if(size > capacity) removeFirst() + if (size > capacity) removeFirstOrNull() return super.add(element) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index a9f2dcaffc..959e4749dc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -22,12 +22,13 @@ external fun pipeStdOutToSocket(socketName: String) : Int typealias ChatCtrl = Long external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array external fun chatCloseStore(ctrl: ChatCtrl): String -external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String -external fun chatSendRemoteCmd(ctrl: ChatCtrl, rhId: Int, msg: String): String +external fun chatSendCmdRetry(ctrl: ChatCtrl, msg: String, retryNum: Int): String +external fun chatSendRemoteCmdRetry(ctrl: ChatCtrl, rhId: Int, msg: String, retryNum: Int): String external fun chatRecvMsg(ctrl: ChatCtrl): String external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String external fun chatParseMarkdown(str: String): String external fun chatParseServer(str: String): String +external fun chatParseUri(str: String, safe: Int): String external fun chatPasswordHash(pwd: String, salt: String): String external fun chatValidName(name: String): String external fun chatJsonLength(str: String): Int diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt index fca69d5398..19e40ab0a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt @@ -16,7 +16,7 @@ expect fun compressImageData(bitmap: ImageBitmap, usePng: Boolean): ByteArrayOut expect fun GrayU8.toImageBitmap(): ImageBitmap expect fun ImageBitmap.hasAlpha(): Boolean -expect fun ImageBitmap.addLogo(): ImageBitmap +expect fun ImageBitmap.addLogo(size: Float): ImageBitmap expect fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap expect fun isImage(uri: URI): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt index 5b9e63963c..d906ef7baf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -10,8 +10,7 @@ import chat.simplex.res.MR import kotlinx.coroutines.delay enum class NotificationAction { - ACCEPT_CONTACT_REQUEST, - ACCEPT_CONTACT_REQUEST_INCOGNITO + ACCEPT_CONTACT_REQUEST } lateinit var ntfManager: NtfManager @@ -31,8 +30,7 @@ abstract class NtfManager { msgText = generalGetString(MR.strings.notification_new_contact_request), image = cInfo.image, listOf( - NotificationAction.ACCEPT_CONTACT_REQUEST to { acceptContactRequestAction(user.userId, incognito = false, cInfo.id) }, - NotificationAction.ACCEPT_CONTACT_REQUEST_INCOGNITO to { acceptContactRequestAction(user.userId, incognito = true, cInfo.id) } + NotificationAction.ACCEPT_CONTACT_REQUEST to { acceptContactRequestAction(user.userId, incognito = false, cInfo.id) } ) ) @@ -51,14 +49,9 @@ abstract class NtfManager { fun acceptContactRequestAction(userId: Long?, incognito: Boolean, chatId: ChatId) { val isCurrentUser = ChatModel.currentUser.value?.userId == userId - val cInfo: ChatInfo.ContactRequest? = if (isCurrentUser) { - (ChatModel.getChat(chatId)?.chatInfo as? ChatInfo.ContactRequest) ?: return - } else { - null - } val apiId = chatId.replace("<@", "").toLongOrNull() ?: return // TODO include remote host in notification - acceptContactRequest(null, incognito, apiId, cInfo, isCurrentUser, ChatModel) + acceptContactRequest(null, incognito, apiId, isCurrentUser, ChatModel) cancelNotificationsForChat(chatId) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/SimplexService.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/SimplexService.kt new file mode 100644 index 0000000000..0644b5dd30 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/SimplexService.kt @@ -0,0 +1,3 @@ +package chat.simplex.common.platform + +expect fun getWakeLock(timeout: Long): (() -> Unit) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 37aa7fc1d1..1bfe88ecf7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -101,7 +101,7 @@ fun TerminalLayout( sendMsgEnabled = true, userCantSendReason = null, sendButtonEnabled = true, - nextSendGrpInv = false, + nextConnect = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = false, allowVoiceToContact = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 8317c6cf6c..6ec124048c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -34,6 +34,12 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +const val MAX_BIO_LENGTH_BYTES = 160 + +fun bioFitsLimit(bio: String): Boolean { + return chatJsonLength(bio) <= MAX_BIO_LENGTH_BYTES +} + @Composable fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { val scope = rememberCoroutineScope() @@ -46,6 +52,7 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { .padding(top = 20.dp) ) { val displayName = rememberSaveable { mutableStateOf("") } + val shortDescr = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } ColumnWithScrollBar { @@ -66,16 +73,34 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { } } ProfileNameField(displayName, "", { it.trim() == mkValidName(it) }, focusRequester) + + Spacer(Modifier.height(DEFAULT_PADDING)) + + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(MR.strings.short_descr), + fontSize = 16.sp + ) + Spacer(Modifier.height(20.dp)) + if (!bioFitsLimit(shortDescr.value)) { + IconButton( + onClick = { AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.bio_too_large)) }, + Modifier.size(20.dp)) { + Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error) + } + } + } + ProfileNameField(shortDescr, "", isValid = { bioFitsLimit(it) }) } SettingsActionItem( painterResource(MR.images.ic_check), stringResource(MR.strings.create_another_profile_button), - disabled = !canCreateProfile(displayName.value), + disabled = !canCreateProfile(displayName.value) || !bioFitsLimit(shortDescr.value), textColor = MaterialTheme.colors.primary, iconColor = MaterialTheme.colors.primary, click = { if (chatModel.localUserCreated.value == true) { - createProfileInProfiles(chatModel, displayName.value, close) + createProfileInProfiles(chatModel, displayName.value, shortDescr.value, close) } else { createProfileInNoProfileSetup(displayName.value, close) } @@ -162,7 +187,7 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { withBGApi { - val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withBGApi + val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, null)) ?: return@withBGApi if (!chatModel.connectedToRemote()) { chatModel.localUserCreated.value = true } @@ -173,11 +198,11 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { } } -fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) { +fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, close: () -> Unit) { withBGApi { val rhId = chatModel.remoteHostId() val user = chatModel.controller.apiCreateActiveUser( - rhId, Profile(displayName.trim(), "", null) + rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, null) ) ?: return@withBGApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { @@ -196,7 +221,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) { withBGApi { chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser( - null, Profile(displayName.trim(), "", null) + null, Profile(displayName.trim(), "", null, null) ) ?: return@withBGApi chatModel.localUserCreated.value = true val onboardingStage = chatModel.controller.appPrefs.onboardingStage diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 2a77d0a6dc..c7b8cb2c81 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -40,9 +40,9 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* -import chat.simplex.common.views.chat.group.ChatTTLSection +import chat.simplex.common.views.chat.group.ChatTTLOption +import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.chatlist.updateChatSettings -import chat.simplex.common.views.database.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.* @@ -617,7 +617,10 @@ fun ChatInfoLayout( } SectionDividerSpaced(maxBottomPadding = false) - ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems) + SectionView { + ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems) + SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer)) + } SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) val conn = contact.activeConn @@ -704,15 +707,16 @@ fun ChatInfoLayout( @Composable fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { Column( - Modifier.padding(horizontal = 8.dp), + Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally ) { ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) + val displayName = contact.profile.displayName.trim() val text = buildAnnotatedString { if (contact.verified) { appendInlineContent(id = "shieldIcon") } - append(contact.profile.displayName) + append(displayName) } val inlineContent: Map = mapOf( "shieldIcon" to InlineTextContent( @@ -722,10 +726,11 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { } ) val clipboard = LocalClipboardManager.current - val copyNameToClipboard = { - clipboard.setText(AnnotatedString(contact.profile.displayName)) + val copyNameToClipboard = fun (name: String) { + clipboard.setText(AnnotatedString(name)) showToast(generalGetString(MR.strings.copied)) } + val copyDisplayName = { copyNameToClipboard(displayName) } Text( text, inlineContent = inlineContent, @@ -733,18 +738,39 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { textAlign = TextAlign.Center, maxLines = 3, overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName) + ) + ChatInfoDescription(cInfo, displayName, copyNameToClipboard) + } +} + +@Composable +fun ChatInfoDescription(c: NamedChat, displayName: String, copyNameToClipboard: (String) -> Unit) { + val fullName = c.fullName.trim() + if (fullName != "" && fullName != displayName && fullName != c.displayName.trim()) { + val copyFullName = { copyNameToClipboard(fullName) } + Text( + fullName, style = MaterialTheme.typography.h2, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF).combinedClickable(onClick = copyFullName, onLongClick = copyFullName).onRightClick(copyFullName) + ) + } + val descr = c.shortDescr?.trim() + if (descr != null && descr != "") { + MarkdownText( + descr, + parseToMarkdown(descr), + toggleSecrets = true, + style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onBackground, lineHeight = 21.sp, textAlign = TextAlign.Center), + maxLines = 4, + overflow = TextOverflow.Ellipsis, + uriHandler = LocalUriHandler.current, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF), + linkMode = chatModel.simplexLinkMode.value ) - if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName) { - Text( - cInfo.fullName, style = MaterialTheme.typography.h2, - color = MaterialTheme.colors.onBackground, - textAlign = TextAlign.Center, - maxLines = 4, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) - ) - } } } @@ -932,7 +958,7 @@ fun CallButton( } } } } - contact.nextSendGrpInv -> { { showCantCallContactSendMessageAlert() } } + contact.sendMsgToConnect -> { { showCantCallContactSendMessageAlert() } } !contact.active -> { { showCantCallContactDeletedAlert() } } !contact.ready -> { { showCantCallContactConnectingAlert() } } needToAllowCallsToContact -> { { showNeedToAllowCallsAlert(onConfirm = { allowCallsToContact(chat) }) } } @@ -1384,7 +1410,7 @@ private fun setChatTTL( private suspend fun afterSetChatTTL(chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatInfo: ChatInfo, progressIndicator: MutableState) { try { val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) - val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return + val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, scope = null, contentTag = null, pagination) ?: return if (chat.chatItems.isEmpty()) { // replacing old chat with the same old chat but without items. Less intrusive way of clearing a preview withContext(Dispatchers.Main) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt index eabe9cb60a..ed40150cb1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -17,7 +17,7 @@ suspend fun apiLoadSingleMessage( apiId: Long, itemId: Long ): ChatItem? = coroutineScope { - val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null + val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null chat.chatItems.firstOrNull() } @@ -31,7 +31,7 @@ suspend fun apiLoadMessages( openAroundItemId: Long? = null, visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 } ) = coroutineScope { - val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, pagination, search) ?: return@coroutineScope + val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, pagination, search) ?: return@coroutineScope // For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes /** When [openAroundItemId] is provided, chatId can be different too */ if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last && openAroundItemId == null) @@ -54,7 +54,7 @@ suspend fun processLoadedChat( when (pagination) { is ChatPagination.Initial -> { val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList() - if (chatsCtx.contentTag == null) { + if (chatsCtx.secondaryContextFilter == null) { // update main chats, not content tagged withContext(Dispatchers.Main) { val oldChat = chatModel.chatsContext.getChat(chat.id) @@ -68,7 +68,6 @@ suspend fun processLoadedChat( } } withContext(Dispatchers.Main) { - chatsCtx.chatItemStatuses.clear() chatsCtx.chatItems.replaceAll(chat.chatItems) chatModel.chatId.value = chat.id splits.value = newSplits @@ -326,7 +325,8 @@ private fun removeDuplicatesAndUpperSplits( if (idsToTrim.last().isNotEmpty()) { // it has some elements to trim from currently visible range which means the items shouldn't be trimmed // Otherwise, the last set would be empty - idsToTrim.removeLast() + // note: removeLast() produce NoSuchMethodError on Android but removeLastOrNull() works + idsToTrim.removeLastOrNull() } val allItemsToDelete = idsToTrim.flatten() if (allItemsToDelete.isNotEmpty()) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 6d7cdcdebe..b69c98887d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -42,7 +42,9 @@ import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* import chat.simplex.common.platform.AudioPlayer import chat.simplex.common.views.newchat.ContactConnectionInfoView +import chat.simplex.common.views.newchat.alertProfileImageSize import chat.simplex.res.MR +import dev.icerock.moko.resources.ImageResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.datetime.* @@ -53,6 +55,40 @@ import kotlin.math.* @Stable data class ItemSeparation(val timestamp: Boolean, val largeGap: Boolean, val date: Instant?) +@Composable +fun ConnectInProgressView(s: String) { + Surface(color = MaterialTheme.colors.background) { + Divider() + Row( + Modifier + .height(60.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Row( + Modifier + .padding(start = 10.dp) + .fillMaxWidth() + .weight(1F), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + ComposeProgressIndicator() + Text(s) + } + + IconButton(onClick = { connectProgressManager.cancelConnectProgress() }) { + Icon( + painterResource(MR.images.ic_close), + contentDescription = stringResource(MR.strings.cancel_verb), + tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(10.dp) + ) + } + } + } +} + @Composable // staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat // to chat list smooth. Otherwise, chat view will become blank right before the transition starts @@ -63,19 +99,32 @@ fun ChatView( onComposed: suspend (chatId: String) -> Unit ) { val showSearch = rememberSaveable { mutableStateOf(false) } + val chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value } // They have their own iterator inside for a reason to prevent crash "Reading a state that was created after the snapshot..." val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } } - val activeChatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo } } + val activeChat = remember { derivedStateOf { + var chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value } + val chatInfo = chat?.chatInfo + if ( + chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext + && chat != null + && chatInfo is ChatInfo.Group + ) { + val scopeInfo = chatsCtx.secondaryContextFilter.groupScopeInfo + chat = chat.copy(chatInfo = chatInfo.copy(groupChatScope = scopeInfo)) + } + chat + } } val user = chatModel.currentUser.value - val chatInfo = activeChatInfo.value - if (chatInfo == null || user == null) { + val chatInfo = activeChat.value?.chatInfo + if (chat == null || chatInfo == null || user == null) { LaunchedEffect(Unit) { chatModel.chatId.value = null ModalManager.end.closeModals() } } else { val searchText = rememberSaveable { mutableStateOf("") } - val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() + val useLinkPreviews = true val composeState = rememberSaveable(saver = ComposeState.saver()) { val draft = chatModel.draft.value val sharedContent = chatModel.sharedContent.value @@ -91,6 +140,14 @@ fun ChatView( val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val scope = rememberCoroutineScope() val selectedChatItems = rememberSaveable { mutableStateOf(null as Set?) } + val showCommandsMenu = rememberSaveable { mutableStateOf(false) } + if (appPlatform.isAndroid) { + DisposableEffect(Unit) { + onDispose { + connectProgressManager.cancelConnectProgress() + } + } + } LaunchedEffect(Unit) { // snapshotFlow here is because it reacts much faster on changes in chatModel.chatId.value. // With LaunchedEffect(chatModel.chatId.value) there is a noticeable delay before reconstruction of the view @@ -99,7 +156,10 @@ fun ChatView( .distinctUntilChanged() .filterNotNull() .collect { chatId -> - if (chatsCtx.contentTag == null) { + if (appPlatform.isAndroid) { + connectProgressManager.cancelConnectProgress() + } + if (chatsCtx.secondaryContextFilter == null) { markUnreadChatAsRead(chatId) } showSearch.value = false @@ -108,6 +168,18 @@ fun ChatView( } } } + if (chatsCtx.secondaryContextFilter == null && chatInfo is ChatInfo.Group && chatInfo.groupInfo.membership.memberPending) { + LaunchedEffect(Unit) { + val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null) + val supportChatInfo = ChatInfo.Group(chatInfo.groupInfo, groupChatScope = scopeInfo) + showMemberSupportChatView( + chatModel.chatId, + scrollToItemId, + supportChatInfo, + scopeInfo + ) + } + } val view = LocalMultiplatformView() val chatRh = remoteHostId.value // We need to have real unreadCount value for displaying it inside top right button @@ -133,7 +205,7 @@ fun ChatView( val sameText = searchText.value == value // showSearch can be false with empty text when it was closed manually after clicking on message from search to load .around it // (required on Android to have this check to prevent call to search with old text) - val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.contentTag == null + val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.secondaryContextFilter == null val c = chatModel.getChat(chatInfo.id) if (sameText || emptyAndClosedSearch || c == null || chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged withBGApi { @@ -144,7 +216,7 @@ fun ChatView( ChatLayout( chatsCtx = chatsCtx, remoteHostId = remoteHostId, - chatInfo = activeChatInfo, + chat = activeChat, unreadCount, composeState, composeView = { focusRequester -> @@ -153,21 +225,21 @@ fun ChatView( Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - if ( - chatInfo is ChatInfo.Direct - && !chatInfo.contact.sndReady - && chatInfo.contact.active - && !chatInfo.contact.nextSendGrpInv - ) { + val connectInProgressText = connectProgressManager.showConnectProgress + if (appPlatform.isAndroid && connectInProgressText != null) { + ConnectInProgressView(connectInProgressText) + } + val connectingText = connectingText(chatInfo) + if (connectingText != null) { Text( - generalGetString(MR.strings.contact_connection_pending), + connectingText, Modifier.padding(top = 4.dp), fontSize = 14.sp, color = MaterialTheme.colors.secondary ) } ComposeView( - chatModel, Chat(remoteHostId = chatRh, chatInfo = chatInfo, chatItems = emptyList()), composeState, attachmentOption, + rhId = remoteHostId.value, chatModel, chatsCtx, Chat(remoteHostId = chatRh, chatInfo = chatInfo, chatItems = emptyList()), composeState, showCommandsMenu, attachmentOption, showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }, focusRequester = focusRequester ) @@ -220,6 +292,7 @@ fun ChatView( rh = chatRh, fromChatType = chatInfo.chatType, fromChatId = chatInfo.apiId, + fromScope = chatInfo.groupChatScope(), chatItemIds = chatItemIds ) @@ -262,7 +335,7 @@ fun ChatView( // The idea is to preload information before showing a modal because large groups can take time to load all members var preloadedContactInfo: Pair? = null var preloadedCode: String? = null - var preloadedLink: Pair? = null + var preloadedLink: GroupLink? = null if (chatInfo is ChatInfo.Direct) { preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId) preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second @@ -276,7 +349,7 @@ fun ChatView( ModalManager.end.showCustomModal { close -> val appBar = remember { mutableStateOf(null as @Composable (BoxScope.() -> Unit)?) } ModalView(close, appBar = appBar.value) { - val chatInfo = remember { activeChatInfo }.value + val chatInfo = remember { activeChat }.value?.chatInfo if (chatInfo is ChatInfo.Direct) { var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } var code: String? by remember { mutableStateOf(preloadedCode) } @@ -290,13 +363,13 @@ fun ChatView( showSearch.value = true } } else if (chatInfo is ChatInfo.Group) { - var link: Pair? by remember(chatInfo.id) { mutableStateOf(preloadedLink) } + var link: GroupLink? by remember(chatInfo.id) { mutableStateOf(preloadedLink) } KeyChangeEffect(chatInfo.id) { setGroupMembers(chatRh, chatInfo.groupInfo, chatModel) link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId) preloadedLink = link } - GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link?.first, link?.second, selectedItems, appBar, scrollToItemId, { + GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link, selectedItems, appBar, scrollToItemId, { link = it preloadedLink = it }, close, { showSearch.value = true }) @@ -306,7 +379,7 @@ fun ChatView( } } LaunchedEffect(Unit) { - snapshotFlow { activeChatInfo.value?.id } + snapshotFlow { activeChat.value?.id } .drop(1) .collect { appBar.value = null @@ -317,15 +390,47 @@ fun ChatView( } } }, - showGroupReports = { - val info = activeChatInfo.value ?: return@ChatLayout + showReports = { + val cInfo = activeChat.value?.chatInfo ?: return@ChatLayout if (ModalManager.end.hasModalsOpen()) { ModalManager.end.closeModals() return@ChatLayout } hideKeyboard(view) scope.launch { - showGroupReportsView(staleChatId, scrollToItemId, info) + showGroupReportsView(staleChatId, scrollToItemId, cInfo) + } + }, + showSupportChats = { + val cInfo = activeChat.value?.chatInfo ?: return@ChatLayout + if (ModalManager.end.hasModalsOpen()) { + ModalManager.end.closeModals() + return@ChatLayout + } + hideKeyboard(view) + scope.launch { + if (cInfo is ChatInfo.Group && cInfo.groupInfo.membership.memberRole >= GroupMemberRole.Moderator) { + ModalManager.end.showCustomModal { close -> + MemberSupportView( + chatRh, + chat, + cInfo.groupInfo, + scrollToItemId, + close + ) + } + } else if (cInfo is ChatInfo.Group) { + val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null) + val supportChatInfo = ChatInfo.Group(cInfo.groupInfo, groupChatScope = scopeInfo) + scope.launch { + showMemberSupportChatView( + chatModel.chatId, + scrollToItemId = scrollToItemId, + supportChatInfo, + scopeInfo + ) + } + } } }, showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> @@ -343,12 +448,12 @@ fun ChatView( setGroupMembers(chatRh, groupInfo, chatModel) if (!isActive) return@launch - if (chatsCtx.contentTag == null) { + if (chatsCtx.secondaryContextFilter == null) { ModalManager.end.closeModals() } ModalManager.end.showModalCloseable(true) { close -> remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> - GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) + GroupMemberInfoView(chatRh, groupInfo, mem, scrollToItemId, stats, code, chatModel, openedFromSupportChat = false, close, close) } } } @@ -379,6 +484,7 @@ fun ChatView( chatRh, type = chatInfo.chatType, id = chatInfo.apiId, + scope = chatInfo.groupChatScope(), itemIds = listOf(itemId), mode = mode ) @@ -397,14 +503,13 @@ fun ChatView( if (deletedItem.isActiveReport) { chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id) } + chatModel.chatsContext.updateChatInfo(chatRh, deleted.deletedChatItem.chatInfo) } withContext(Dispatchers.Main) { - if (deletedChatItem.isReport) { - if (toChatItem != null) { - chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) - } else { - chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, deletedChatItem) - } + if (toChatItem != null) { + chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) + } else { + chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, deletedChatItem) } } } @@ -512,6 +617,7 @@ fun ChatView( rh = chatRh, type = cInfo.chatType, id = cInfo.apiId, + scope = cInfo.groupChatScope(), itemId = cItem.id, add = add, reaction = reaction @@ -521,16 +627,14 @@ fun ChatView( chatModel.chatsContext.updateChatItem(cInfo, updatedCI) } withContext(Dispatchers.Main) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, updatedCI) - } + chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, updatedCI) } } } }, showItemDetails = { cInfo, cItem -> suspend fun loadChatItemInfo(): ChatItemInfo? = coroutineScope { - val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) + val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cInfo.groupChatScope(), cItem.id) if (ciInfo != null) { if (chatInfo is ChatInfo.Group) { setGroupMembers(chatRh, chatInfo.groupInfo, chatModel) @@ -579,12 +683,16 @@ fun ChatView( withContext(Dispatchers.Main) { chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds) ntfManager.cancelNotificationsForChat(chatInfo.id) - chatModel.controller.apiChatItemsRead( + val updatedChatInfo = chatModel.controller.apiChatItemsRead( chatRh, chatInfo.chatType, chatInfo.apiId, + chatInfo.groupChatScope(), itemsIds ) + if (updatedChatInfo != null) { + chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo) + } } withContext(Dispatchers.Main) { chatModel.secondaryChatsContext.value?.markChatItemsRead(chatRh, chatInfo.id, itemsIds) @@ -616,7 +724,8 @@ fun ChatView( onComposed, developerTools = chatModel.controller.appPrefs.developerTools.get(), showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(), - showSearch = showSearch + showSearch = showSearch, + showCommandsMenu = showCommandsMenu ) } } @@ -652,6 +761,35 @@ fun ChatView( } } +private fun connectingText(chatInfo: ChatInfo): String? { + return when (chatInfo) { + is ChatInfo.Direct -> + if ( + !chatInfo.contact.sndReady + && chatInfo.contact.active + && !chatInfo.contact.sendMsgToConnect + && !chatInfo.contact.nextAcceptContactRequest + ) { + if ((chatInfo.contact.preparedContact?.uiConnLinkType == ConnectionMode.Con && !chatInfo.contact.isBot) || chatInfo.contact.contactGroupMemberId != null) { + generalGetString(MR.strings.contact_should_accept) + } else { + generalGetString(MR.strings.contact_connection_pending) + } + } else { + null + } + + is ChatInfo.Group -> + when (chatInfo.groupInfo.membership.memberStatus) { + GroupMemberStatus.MemUnknown -> if (chatInfo.groupInfo.preparedGroup?.connLinkStartedConnection == true) generalGetString(MR.strings.group_connection_pending) else null + GroupMemberStatus.MemAccepted -> generalGetString(MR.strings.group_connection_pending) + else -> null + } + + else -> null + } +} + fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) { withBGApi { if (chatInfo is ChatInfo.Direct) { @@ -669,7 +807,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) fun ChatLayout( chatsCtx: ChatModel.ChatsContext, remoteHostId: State, - chatInfo: State, + chat: State, unreadCount: State, composeState: MutableState, composeView: (@Composable (FocusRequester?) -> Unit), @@ -682,7 +820,8 @@ fun ChatLayout( selectedChatItems: MutableState?>, back: () -> Unit, info: () -> Unit, - showGroupReports: () -> Unit, + showReports: () -> Unit, + showSupportChats: () -> Unit, showMemberInfo: (GroupInfo, GroupMember) -> Unit, loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, @@ -715,8 +854,10 @@ fun ChatLayout( onComposed: suspend (chatId: String) -> Unit, developerTools: Boolean, showViaProxy: Boolean, - showSearch: MutableState + showSearch: MutableState, + showCommandsMenu: MutableState ) { + val chatInfo = remember { derivedStateOf { chat.value?.chatInfo } } val scope = rememberCoroutineScope() val attachmentDisabled = remember { derivedStateOf { composeState.value.attachmentDisabled } } Box( @@ -745,21 +886,22 @@ fun ChatLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { val composeViewHeight = remember { mutableStateOf(0.dp) } - Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.contentTag == null)) { + Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.secondaryContextFilter == null)) { val remoteHostId = remember { remoteHostId }.value - val chatInfo = remember { chatInfo }.value + val chat = remember { chat }.value + val chatInfo = chat?.chatInfo val oneHandUI = remember { appPrefs.oneHandUI.state } val chatBottomBar = remember { appPrefs.chatBottomBar.state } val composeViewFocusRequester = remember { if (appPlatform.isDesktop) FocusRequester() else null } AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) { - if (chatInfo != null) { + if (chat != null) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { // disables scrolling to top of chat item on click inside the bubble CompositionLocalProvider(LocalBringIntoViewSpec provides object : BringIntoViewSpec { override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f }) { ChatItemsList( - chatsCtx, remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue, + chatsCtx, remoteHostId, chat, unreadCount, composeState, composeViewHeight, searchValue, useLinkPreviews, linkMode, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages, archiveReports, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, @@ -773,6 +915,7 @@ fun ChatLayout( .padding(bottom = composeViewHeight.value) ) { GroupMentions( + chatsCtx = chatsCtx, rhId = remoteHostId, composeState = composeState, composeViewFocusRequester = composeViewFocusRequester, @@ -780,6 +923,11 @@ fun ChatLayout( ) } } + if (chatInfo != null && chatInfo.menuCommands.isNotEmpty()) { + Column(Modifier.align(Alignment.BottomStart).padding(bottom = composeViewHeight.value)) { + CommandsMenuView(chatsCtx, chat, composeState, showCommandsMenu) + } + } } } if (chatsCtx.contentTag == MsgContentTag.Report) { @@ -832,41 +980,78 @@ fun ChatLayout( } } val reportsCount = reportsCount(chatInfo?.id) + val supportUnreadCount = supportUnreadCount(chatInfo?.id) if (oneHandUI.value && chatBottomBar.value) { - if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate && chatsCtx.contentTag == null && reportsCount > 0) { - ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports) + if ( + chatInfo is ChatInfo.Group + && chatsCtx.secondaryContextFilter == null + && (reportsCount > 0 || supportUnreadCount > 0) + ) { + SupportChatsCountToolbar(chatInfo, reportsCount, supportUnreadCount, withStatusBar = true, showReports, showSupportChats) } else { StatusBarBackground() } } else { NavigationBarBackground(true, oneHandUI.value, noAlpha = true) } - if (chatsCtx.contentTag == MsgContentTag.Report) { - if (oneHandUI.value) { - StatusBarBackground() - } - Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { - Box { - if (selectedChatItems.value == null) { - GroupReportsAppBar(chatsCtx, { ModalManager.end.closeModal() }, onSearchValueChanged) - } else { - SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value) - } - } - } - } else { - Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { - Box { - if (selectedChatItems.value == null) { - if (chatInfo != null) { - ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) + when (chatsCtx.secondaryContextFilter) { + is SecondaryContextFilter.GroupChatScopeContext -> { + when (chatsCtx.secondaryContextFilter.groupScopeInfo) { + is GroupChatScopeInfo.MemberSupport -> { + if (oneHandUI.value) { + StatusBarBackground() + } + Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + Box { + if (selectedChatItems.value == null) { + if (chat != null) { + MemberSupportChatAppBar(chatsCtx, remoteHostId, chat, chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_, scrollToItemId, { ModalManager.end.closeModal() }, onSearchValueChanged) + } + } else { + SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value) + } + } } - } else { - SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value) } } - if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate && chatsCtx.contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) { - ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports) + } + is SecondaryContextFilter.MsgContentTagContext -> { + when (chatsCtx.secondaryContextFilter.contentTag) { + MsgContentTag.Report -> { + if (oneHandUI.value) { + StatusBarBackground() + } + Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + Box { + if (selectedChatItems.value == null) { + GroupReportsAppBar(chatsCtx, { ModalManager.end.closeModal() }, onSearchValueChanged) + } else { + SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value) + } + } + } + } + else -> TODO() + } + } + null -> { + Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + Box { + if (selectedChatItems.value == null) { + if (chatInfo != null) { + ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) + } + } else { + SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value) + } + } + if ( + chatInfo is ChatInfo.Group + && (reportsCount > 0 || supportUnreadCount > 0) + && (!oneHandUI.value || !chatBottomBar.value) + ) { + SupportChatsCountToolbar(chatInfo, reportsCount, supportUnreadCount, withStatusBar = false, showReports, showSupportChats) + } } } } @@ -900,7 +1085,7 @@ fun BoxScope.ChatInfoToolbar( showSearch.value = false } } - if (appPlatform.isAndroid && chatsCtx.contentTag == null) { + if (appPlatform.isAndroid && chatsCtx.secondaryContextFilter == null) { BackHandler(onBack = onBackClicked) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() @@ -1097,33 +1282,79 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo } @Composable -private fun ReportedCountToolbar( +private fun SupportChatsCountToolbar( + chatInfo: ChatInfo, reportsCount: Int, + supportUnreadCount: Int, withStatusBar: Boolean, - showGroupReports: () -> Unit + showReports: () -> Unit, + showSupportChats: () -> Unit ) { Box { val statusBarPadding = if (withStatusBar) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else 0.dp Row( Modifier - .fillMaxWidth() - .height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding) - .background(MaterialTheme.colors.background) - .clickable(onClick = showGroupReports) - .padding(top = statusBarPadding), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, ) { - Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error) - Spacer(Modifier.width(4.dp)) - Text( - if (reportsCount == 1) { - stringResource(MR.strings.group_reports_active_one) - } else { - stringResource(MR.strings.group_reports_active).format(reportsCount) - }, - style = MaterialTheme.typography.button - ) + if ( + chatInfo is ChatInfo.Group + && chatInfo.groupInfo.canModerate + && reportsCount > 0 + ) { + Row( + Modifier + .fillMaxWidth() + .weight(1F) + .height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding) + .background(MaterialTheme.colors.background) + .clickable(onClick = showReports) + .padding(top = statusBarPadding), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error) + Spacer(Modifier.width(4.dp)) + Text( + if (reportsCount == 1) { + stringResource(MR.strings.group_reports_active_one) + } else { + stringResource(MR.strings.group_reports_active).format(reportsCount) + }, + style = MaterialTheme.typography.button + ) + } + } + + if (supportUnreadCount > 0) { + Row( + Modifier + .fillMaxWidth() + .weight(1F) + .height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding) + .background(MaterialTheme.colors.background) + .clickable(onClick = showSupportChats) + .padding(top = statusBarPadding), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.primary) + Spacer(Modifier.width(4.dp)) + Text( + if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) { + if (appPlatform.isAndroid) + stringResource(MR.strings.group_new_support_chats_short).format(supportUnreadCount) + else if (supportUnreadCount == 1) + stringResource(MR.strings.group_new_support_chat_one) + else + stringResource(MR.strings.group_new_support_chats).format(supportUnreadCount) + } else { + stringResource(MR.strings.group_new_support_messages).format(supportUnreadCount) + }, + style = MaterialTheme.typography.button + ) + } + } } Divider(Modifier.align(Alignment.BottomStart)) } @@ -1141,7 +1372,7 @@ private var reportsListState: LazyListState? = null fun BoxScope.ChatItemsList( chatsCtx: ChatModel.ChatsContext, remoteHostId: Long?, - chatInfo: ChatInfo, + chat: Chat, unreadCount: State, composeState: MutableState, composeViewHeight: State, @@ -1178,6 +1409,7 @@ fun BoxScope.ChatItemsList( developerTools: Boolean, showViaProxy: Boolean ) { + val chatInfo = chat.chatInfo val loadingTopItems = remember { mutableStateOf(false) } val loadingBottomItems = remember { mutableStateOf(false) } // just for changing local var here based on request @@ -1198,298 +1430,281 @@ fun BoxScope.ChatItemsList( val searchValueIsNotBlank = remember { derivedStateOf { searchValue.value.isNotBlank() } } val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } // not using reversedChatItems inside to prevent possible derivedState bug in Compose when one derived state access can cause crash asking another derived state - if (chatsCtx != null) { - val mergedItems = remember { - derivedStateOf { - MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState) - } + val mergedItems = remember { + derivedStateOf { + MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState) } - val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } } - val reportsCount = reportsCount(chatInfo.id) - val topPaddingToContent = topPaddingToContent( - chatView = chatsCtx.contentTag == null, - additionalTopBar = chatsCtx.contentTag == null && reportsCount > 0 - ) - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() }) - val numberOfBottomAppBars = numberOfBottomAppBars() + } + val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } } + val reportsCount = reportsCount(chatInfo.id) + val supportUnreadCount = supportUnreadCount(chatInfo.id) + val topPaddingToContent = topPaddingToContent( + chatView = chatsCtx.secondaryContextFilter == null, + additionalTopBar = chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0) + ) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() }) + val numberOfBottomAppBars = numberOfBottomAppBars() - /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of - * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears - * */ - val maxHeightForList = rememberUpdatedState( - with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * numberOfBottomAppBars).roundToPx() } - ) - val resetListState = remember { mutableStateOf(false) } - remember(chatModel.openAroundItemId.value) { - if (chatModel.openAroundItemId.value != null) { - closeSearch() - resetListState.value = !resetListState.value - } + /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of + * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears + * */ + val maxHeightForList = rememberUpdatedState( + with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * numberOfBottomAppBars).roundToPx() } + ) + val resetListState = remember { mutableStateOf(false) } + remember(chatModel.openAroundItemId.value) { + if (chatModel.openAroundItemId.value != null) { + closeSearch() + resetListState.value = !resetListState.value } - val highlightedItems = remember { mutableStateOf(setOf()) } - val hoveredItemId = remember { mutableStateOf(null as Long?) } - val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) { - val openAroundItemId = chatModel.openAroundItemId.value - val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() } - val reportsState = reportsListState - if (openAroundItemId != null) { - highlightedItems.value += openAroundItemId - chatModel.openAroundItemId.value = null - } - hoveredItemId.value = null - if (reportsState != null) { - reportsListState = null - reportsState - } else if (index <= 0 || !searchValueIsEmpty.value) { - LazyListState(0, 0) - } else { - LazyListState(index + 1, -maxHeightForList.value) - } - }) - SaveReportsStateOnDispose(chatsCtx, listState) - val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } - val loadingMoreItems = remember { mutableStateOf(false) } - val animatedScrollingInProgress = remember { mutableStateOf(false) } - val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() } - LaunchedEffect(chatInfo.id, searchValueIsEmpty.value) { - if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT) - ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect) + } + val highlightedItems = remember { mutableStateOf(setOf()) } + val hoveredItemId = remember { mutableStateOf(null as Long?) } + val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) { + val openAroundItemId = chatModel.openAroundItemId.value + val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() } + val reportsState = reportsListState + if (openAroundItemId != null) { + highlightedItems.value += openAroundItemId + chatModel.openAroundItemId.value = null } - PreloadItems(chatsCtx, chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> - if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false - loadingMoreItems.value = true - withContext(NonCancellable) { - try { - loadMessages(chatId, pagination) { - visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) - } - } finally { - loadingMoreItems.value = false + hoveredItemId.value = null + if (reportsState != null) { + reportsListState = null + reportsState + } else if (index <= 0 || !searchValueIsEmpty.value) { + LazyListState(0, 0) + } else { + LazyListState(index + 1, -maxHeightForList.value) + } + }) + SaveReportsStateOnDispose(chatsCtx, listState) + val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } + val loadingMoreItems = remember { mutableStateOf(false) } + val animatedScrollingInProgress = remember { mutableStateOf(false) } + val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() } + LaunchedEffect(chatInfo.id, searchValueIsEmpty.value) { + if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT) + ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect) + } + PreloadItems(chatsCtx, chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> + if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false + loadingMoreItems.value = true + withContext(NonCancellable) { + try { + loadMessages(chatId, pagination) { + visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) } + } finally { + loadingMoreItems.value = false } - true } - val remoteHostIdUpdated = rememberUpdatedState(remoteHostId) - val chatInfoUpdated = rememberUpdatedState(chatInfo) - val scope = rememberCoroutineScope() - val scrollToItem: (Long) -> Unit = remember { - // In group reports just set the itemId to scroll to so the main ChatView will handle scrolling - if (chatsCtx.contentTag == MsgContentTag.Report) return@remember { scrollToItemId.value = it } - scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) - } - val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(chatsCtx, remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) } - if (chatsCtx.contentTag == null) { - LaunchedEffect(Unit) { - snapshotFlow { scrollToItemId.value }.filterNotNull().collect { - if (appPlatform.isAndroid) { - ModalManager.end.closeModals() - } - scrollToItem(it) - scrollToItemId.value = null + true + } + val remoteHostIdUpdated = rememberUpdatedState(remoteHostId) + val chatInfoUpdated = rememberUpdatedState(chatInfo) + val scope = rememberCoroutineScope() + val scrollToItem: (Long) -> Unit = remember { + scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) + } + val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(chatsCtx, remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, scrollToItemId) } + if (chatsCtx.secondaryContextFilter == null) { + LaunchedEffect(Unit) { + snapshotFlow { scrollToItemId.value }.filterNotNull().collect { + if (appPlatform.isAndroid) { + ModalManager.end.closeModals() } + scrollToItem(it) + scrollToItemId.value = null } } - SmallScrollOnNewMessage(listState, reversedChatItems) - val finishedInitialComposition = remember { mutableStateOf(false) } - NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed) + } + SmallScrollOnNewMessage(listState, reversedChatItems) + val finishedInitialComposition = remember { mutableStateOf(false) } + NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed) - DisposableEffectOnGone( - whenGone = { - VideoPlayerHolder.releaseAll() - } - ) + DisposableEffectOnGone( + whenGone = { + VideoPlayerHolder.releaseAll() + } + ) - @Composable - fun ChatViewListItem( - itemAtZeroIndexInWholeList: Boolean, - range: State, - showAvatar: Boolean, - cItem: ChatItem, - itemSeparation: ItemSeparation, - previousItemSeparationLargeGap: Boolean, - revealed: State, - reveal: (Boolean) -> Unit + @Composable + fun ChatViewListItem( + itemAtZeroIndexInWholeList: Boolean, + range: State, + showAvatar: Boolean, + cItem: ChatItem, + itemSeparation: ItemSeparation, + previousItemSeparationLargeGap: Boolean, + revealed: State, + reveal: (Boolean) -> Unit + ) { + val itemScope = rememberCoroutineScope() + CompositionLocalProvider( + // Makes horizontal and vertical scrolling to coexist nicely. + // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view + LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop() ) { - val itemScope = rememberCoroutineScope() - CompositionLocalProvider( - // Makes horizontal and vertical scrolling to coexist nicely. - // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view - LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop() - ) { - val provider = { - providerForGallery(reversedChatItems.value.asReversed(), cItem.id) { indexInReversed -> + val provider = { + providerForGallery(reversedChatItems.value.asReversed(), cItem.id) { indexInReversed -> + itemScope.launch { + listState.value.scrollToItem( + min(reversedChatItems.value.lastIndex, indexInReversed + 1), + -maxHeight.value + ) + } + } + } + + @Composable + fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true) { + tryOrShowError("${cItem.id}ChatItem", error = { + CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) + }) { + val highlighted = remember { derivedStateOf { highlightedItems.value.contains(cItem.id) } } + LaunchedEffect(Unit) { + snapshotFlow { highlighted.value } + .distinctUntilChanged() + .filter { it } + .collect { + delay(500) + highlightedItems.value = setOf() + } + } + ChatItemView(chatsCtx, remoteHostId, chat, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToItemId = scrollToItemId, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + } + } + + @Composable + fun ChatItemView(cItem: ChatItem, range: State, itemSeparation: ItemSeparation, previousItemSeparationLargeGap: Boolean) { + val dismissState = rememberDismissState(initialValue = DismissValue.Default) { + if (it == DismissValue.DismissedToStart) { itemScope.launch { - listState.value.scrollToItem( - min(reversedChatItems.value.lastIndex, indexInReversed + 1), - -maxHeight.value - ) - } - } - } - - @Composable - fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true) { - tryOrShowError("${cItem.id}ChatItem", error = { - CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) - }) { - val highlighted = remember { derivedStateOf { highlightedItems.value.contains(cItem.id) } } - LaunchedEffect(Unit) { - snapshotFlow { highlighted.value } - .distinctUntilChanged() - .filter { it } - .collect { - delay(500) - highlightedItems.value = setOf() - } - } - ChatItemView(chatsCtx, remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) - } - } - - @Composable - fun ChatItemView(cItem: ChatItem, range: State, itemSeparation: ItemSeparation, previousItemSeparationLargeGap: Boolean) { - val dismissState = rememberDismissState(initialValue = DismissValue.Default) { - if (it == DismissValue.DismissedToStart) { - itemScope.launch { - if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local && !cItem.isReport) { - if (composeState.value.editing) { - composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) - } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { - composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) - } + if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local && !cItem.isReport) { + if (composeState.value.editing) { + composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) + } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { + composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) } } } - false } - val swipeableModifier = SwipeToDismissModifier( - state = dismissState, - directions = setOf(DismissDirection.EndToStart), - swipeDistance = with(LocalDensity.current) { 30.dp.toPx() }, - ) - val sent = cItem.chatDir.sent + false + } + val swipeableModifier = SwipeToDismissModifier( + state = dismissState, + directions = setOf(DismissDirection.EndToStart), + swipeDistance = with(LocalDensity.current) { 30.dp.toPx() }, + ) + val sent = cItem.chatDir.sent - @Composable - fun ChatItemBox(modifier: Modifier = Modifier, content: @Composable () -> Unit = { }) { - Box( - modifier = modifier.padding( - bottom = if (itemSeparation.largeGap) { - if (itemAtZeroIndexInWholeList) { - 8.dp - } else { - 4.dp - } - } else 1.dp, top = if (previousItemSeparationLargeGap) 4.dp else 1.dp - ), - contentAlignment = Alignment.CenterStart - ) { - content() - } + @Composable + fun ChatItemBox(modifier: Modifier = Modifier, content: @Composable () -> Unit = { }) { + Box( + modifier = modifier.padding( + bottom = if (itemSeparation.largeGap) { + if (itemAtZeroIndexInWholeList) { + 8.dp + } else { + 4.dp + } + } else 1.dp, top = if (previousItemSeparationLargeGap) 4.dp else 1.dp + ), + contentAlignment = Alignment.CenterStart + ) { + content() } + } - @Composable - fun adjustTailPaddingOffset(originalPadding: Dp, start: Boolean): Dp { - val chatItemTail = remember { appPreferences.chatItemTail.state } - val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true) - val tailRendered = style is ShapeStyle.Bubble && style.tailVisible + @Composable + fun adjustTailPaddingOffset(originalPadding: Dp, start: Boolean): Dp { + val chatItemTail = remember { appPreferences.chatItemTail.state } + val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true) + val tailRendered = style is ShapeStyle.Bubble && style.tailVisible - return originalPadding + (if (tailRendered) 0.dp else if (start) msgTailWidthDp * 2 else msgTailWidthDp) - } + return originalPadding + (if (tailRendered) 0.dp else if (start) msgTailWidthDp * 2 else msgTailWidthDp) + } + + Box { + val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null + val selectionVisible = selectedChatItems.value != null && cItem.canBeDeletedForSelf + val selectionOffset by animateDpAsState(if (selectionVisible && !sent) 4.dp + 22.dp * fontSizeMultiplier else 0.dp) + val swipeableOrSelectionModifier = (if (selectionVisible) Modifier else swipeableModifier).graphicsLayer { translationX = selectionOffset.toPx() } + if (chatInfo is ChatInfo.Group) { + if (cItem.chatDir is CIDirection.GroupRcv) { + if (showAvatar) { + Column( + Modifier + .padding(top = 8.dp) + .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false)) + .fillMaxWidth() + .then(swipeableModifier), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.Start + ) { + @Composable + fun MemberNameAndRole(range: State) { + Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) { + val member = cItem.chatDir.groupMember + val rangeValue = range.value + val (prevMember, memCount) = + if (rangeValue != null) { + chatModel.getPrevHiddenMember(member, rangeValue, reversedChatItems.value) + } else { + null to 1 + } + Text( + memberNames(member, prevMember, memCount), + Modifier + .padding(start = (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + DEFAULT_PADDING_HALF) + .weight(1f, false), + fontSize = 13.5.sp, + color = MaterialTheme.colors.secondary, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { + val chatItemTail = remember { appPreferences.chatItemTail.state } + val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true) + val tailRendered = style is ShapeStyle.Bubble && style.tailVisible - Box { - val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null - val selectionVisible = selectedChatItems.value != null && cItem.canBeDeletedForSelf - val selectionOffset by animateDpAsState(if (selectionVisible && !sent) 4.dp + 22.dp * fontSizeMultiplier else 0.dp) - val swipeableOrSelectionModifier = (if (selectionVisible) Modifier else swipeableModifier).graphicsLayer { translationX = selectionOffset.toPx() } - if (chatInfo is ChatInfo.Group) { - if (cItem.chatDir is CIDirection.GroupRcv) { - if (showAvatar) { - Column( - Modifier - .padding(top = 8.dp) - .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false)) - .fillMaxWidth() - .then(swipeableModifier), - verticalArrangement = Arrangement.spacedBy(4.dp), - horizontalAlignment = Alignment.Start - ) { - @Composable - fun MemberNameAndRole(range: State) { - Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) { - val member = cItem.chatDir.groupMember - val rangeValue = range.value - val (prevMember, memCount) = - if (rangeValue != null) { - chatModel.getPrevHiddenMember(member, rangeValue, reversedChatItems.value) - } else { - null to 1 - } Text( - memberNames(member, prevMember, memCount), - Modifier - .padding(start = (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + DEFAULT_PADDING_HALF) - .weight(1f, false), + member.memberRole.text, + Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF + if (tailRendered) msgTailWidthDp else 0.dp), fontSize = 13.5.sp, + fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, - overflow = TextOverflow.Ellipsis, maxLines = 1 ) - if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { - val chatItemTail = remember { appPreferences.chatItemTail.state } - val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true) - val tailRendered = style is ShapeStyle.Bubble && style.tailVisible - - Text( - member.memberRole.text, - Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF + if (tailRendered) msgTailWidthDp else 0.dp), - fontSize = 13.5.sp, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colors.secondary, - maxLines = 1 - ) - } } } - - @Composable - fun Item() { - ChatItemBox(Modifier.layoutId(CHAT_BUBBLE_LAYOUT_ID)) { - androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { - SelectedListItem(Modifier, cItem.id, selectedChatItems) - } - Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }) { - val member = cItem.chatDir.groupMember - Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { - MemberImage(member) - } - Box(modifier = Modifier.padding(top = 2.dp, start = 4.dp).chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)) { - ChatItemViewShortHand(cItem, itemSeparation, range, false) - } - } - } - } - if (cItem.content.showMemberName) { - DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) { - MemberNameAndRole(range) - Item() - } - } else { - Item() - } } - } else { - ChatItemBox { - AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { - SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) + + @Composable + fun Item() { + ChatItemBox(Modifier.layoutId(CHAT_BUBBLE_LAYOUT_ID)) { + androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { + SelectedListItem(Modifier, cItem.id, selectedChatItems) + } + Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }) { + val member = cItem.chatDir.groupMember + Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { + MemberImage(member) + } + Box(modifier = Modifier.padding(top = 2.dp, start = 4.dp).chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)) { + ChatItemViewShortHand(cItem, itemSeparation, range, false) + } + } } - Row( - Modifier - .padding(start = 8.dp + (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + 4.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false)) - .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) - .then(swipeableOrSelectionModifier) - ) { - ChatItemViewShortHand(cItem, itemSeparation, range) + } + if (cItem.content.showMemberName) { + DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) { + MemberNameAndRole(range) + Item() } + } else { + Item() } } } else { @@ -1497,72 +1712,230 @@ fun BoxScope.ChatItemsList( AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) } - Box( + Row( Modifier - .padding(start = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(104.dp, start = true), end = 12.dp) + .padding(start = 8.dp + (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + 4.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false)) .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) - .then(if (selectionVisible) Modifier else swipeableModifier) + .then(swipeableOrSelectionModifier) ) { ChatItemViewShortHand(cItem, itemSeparation, range) } } } - } else { // direct message + } else { ChatItemBox { AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) } - Box( - Modifier.padding( - start = if (sent && !voiceWithTransparentBack) adjustTailPaddingOffset(76.dp, start = true) else 12.dp, - end = if (sent || voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(76.dp, start = false), - ) + Modifier + .padding(start = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(104.dp, start = true), end = 12.dp) .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) - .then(if (!selectionVisible || !sent) swipeableOrSelectionModifier else Modifier) + .then(if (selectionVisible) Modifier else swipeableModifier) ) { ChatItemViewShortHand(cItem, itemSeparation, range) } } } - if (selectionVisible) { - Box(Modifier.matchParentSize().clickable { - val checked = selectedChatItems.value?.contains(cItem.id) == true - selectUnselectChatItem(select = !checked, cItem, revealed, selectedChatItems, reversedChatItems) - }) + } else { // direct message + ChatItemBox { + AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { + SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) + } + + Box( + Modifier.padding( + start = if (sent && !voiceWithTransparentBack) adjustTailPaddingOffset(76.dp, start = true) else 12.dp, + end = if (sent || voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(76.dp, start = false), + ) + .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value) + .then(if (!selectionVisible || !sent) swipeableOrSelectionModifier else Modifier) + ) { + ChatItemViewShortHand(cItem, itemSeparation, range) + } } } + if (selectionVisible) { + Box(Modifier.matchParentSize().clickable { + val checked = selectedChatItems.value?.contains(cItem.id) == true + selectUnselectChatItem(select = !checked, cItem, revealed, selectedChatItems, reversedChatItems) + }) + } } - if (itemSeparation.date != null) { - DateSeparator(itemSeparation.date) + } + if (itemSeparation.date != null) { + DateSeparator(itemSeparation.date) + } + ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap) + } + } + + @Composable + fun ChatBannerView() { + fun chatContext(): String? { + return when (chatInfo) { + is ChatInfo.Direct -> { + val contact = chatInfo.contact + val preparedLinkType = contact.preparedContact?.uiConnLinkType + if (contact.nextConnectPrepared && preparedLinkType != null) { + when (preparedLinkType) { + ConnectionMode.Inv -> generalGetString(MR.strings.chat_banner_connect_to_chat) + ConnectionMode.Con -> generalGetString(if (contact.isBot) MR.strings.chat_banner_connect_to_use_bot else MR.strings.chat_banner_send_request_to_connect) + } + } else if (contact.nextAcceptContactRequest) { + generalGetString(MR.strings.chat_banner_accept_contact_request) + } else if (contact.isBot) { + generalGetString(MR.strings.chat_banner_bot) + } else { + generalGetString(MR.strings.chat_banner_your_contact) + } } - ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap) + + is ChatInfo.Group -> { + val groupInfo = chatInfo.groupInfo + when (groupInfo.businessChat?.chatType) { + null -> { + if (groupInfo.nextConnectPrepared) { + generalGetString(MR.strings.chat_banner_join_group) + } else { + when (groupInfo.membership.memberStatus) { + GroupMemberStatus.MemInvited -> generalGetString(MR.strings.chat_banner_join_group) + GroupMemberStatus.MemCreator -> generalGetString(MR.strings.chat_banner_your_group) + else -> generalGetString(MR.strings.chat_banner_group) + } + } + } + + BusinessChatType.Business -> + if (groupInfo.nextConnectPrepared) { + generalGetString(MR.strings.chat_banner_connect_to_chat) + } else { + generalGetString(MR.strings.chat_banner_business_connection) + } + BusinessChatType.Customer -> + generalGetString(MR.strings.chat_banner_your_business_contact) + } + } + + else -> null } } - LazyColumnWithScrollBar( - Modifier.align(Alignment.BottomCenter), - state = listState.value, - contentPadding = PaddingValues( - top = topPaddingToContent, - bottom = composeViewHeight.value - ), - reverseLayout = true, - additionalBarOffset = composeViewHeight, - additionalTopBar = rememberUpdatedState(chatsCtx.contentTag == null && reportsCount > 0), - chatBottomBar = remember { appPrefs.chatBottomBar.state } + + Box( + Modifier + .clipChatItem() + .background(MaterialTheme.appColors.receivedMessage) ) { - val mergedItemsValue = mergedItems.value - itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> + val bannerModifier = if (appPlatform.isDesktop) Modifier.width(400.dp) else Modifier.fillMaxWidth() + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = bannerModifier + .padding(horizontal = DEFAULT_PADDING) + .padding(bottom = DEFAULT_PADDING) + // ChatInfoImage has its own padding somewhere, + // also not doing verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF) because of it + .padding(top = DEFAULT_PADDING_HALF) + .background(MaterialTheme.appColors.receivedMessage) + ) { + ChatInfoImage(chatInfo, size = alertProfileImageSize, iconColor = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) + Text( + chatInfo.displayName, + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .widthIn(max = 240.dp) + ) + + val fullName = chatInfo.fullName.trim() + if (fullName.isNotEmpty() && fullName != chatInfo.displayName && fullName != chatInfo.displayName.trim()) { + Text( + fullName, + style = MaterialTheme.typography.h4, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .widthIn(max = 260.dp) + .padding(top = DEFAULT_PADDING_HALF) + ) + } + + val descr = chatInfo.shortDescr?.trim() + if (descr != null && descr != "") { + MarkdownText( + descr, + parseToMarkdown(descr), + toggleSecrets = true, + style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onBackground, lineHeight = 21.sp, textAlign = TextAlign.Center), + maxLines = 4, + overflow = TextOverflow.Ellipsis, + uriHandler = LocalUriHandler.current, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF), + linkMode = linkMode + ) + } + + val contextStr = chatContext() + if (contextStr != null) { + Text( + contextStr, + style = MaterialTheme.typography.body2, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.secondary, + modifier = Modifier.padding(top = DEFAULT_PADDING) + ) + } + } + } + } + + LazyColumnWithScrollBar( + Modifier.align(Alignment.BottomCenter), + state = listState.value, + contentPadding = PaddingValues( + top = topPaddingToContent, + bottom = composeViewHeight.value + ), + reverseLayout = true, + additionalBarOffset = composeViewHeight, + additionalTopBar = rememberUpdatedState(chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0)), + chatBottomBar = remember { appPrefs.chatBottomBar.state } + ) { + val mergedItemsValue = mergedItems.value + itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> + val listItem = merged.newest() + val item = listItem.item + + if (item.content is CIContent.ChatBanner) { + Column { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .padding(horizontal = DEFAULT_PADDING) + .padding(bottom = 90.dp, top = DEFAULT_PADDING) + ) { + ChatBannerView() + } + + val prevItem = listItem.prevItem + if (prevItem != null) { + DateSeparator(prevItem.meta.itemTs) + } + } + } else { val isLastItem = index == mergedItemsValue.items.lastIndex val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null - val listItem = merged.newest() - val item = listItem.item val range = if (merged is MergedItem.Grouped) { merged.rangeInReversed.value } else { null } - val showAvatar = shouldShowAvatar(item, listItem.nextItem) + val showAvatar = shouldShowAvatar(item, merged.oldest().nextItem) val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } val itemSeparation: ItemSeparation val prevItemSeparationLargeGap: Boolean @@ -1592,42 +1965,42 @@ fun BoxScope.ChatItemsList( } } } - FloatingButtons( - chatsCtx, - reversedChatItems, - chatInfoUpdated, - topPaddingToContent, - topPaddingToContentPx, - loadingMoreItems, - loadingTopItems, - loadingBottomItems, - animatedScrollingInProgress, - mergedItems, - unreadCount, - maxHeight, - composeViewHeight, - searchValue, - markChatRead, - listState, - loadMessages - ) - FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState) + } + FloatingButtons( + chatsCtx, + reversedChatItems, + chatInfoUpdated, + topPaddingToContent, + topPaddingToContentPx, + loadingMoreItems, + loadingTopItems, + loadingBottomItems, + animatedScrollingInProgress, + mergedItems, + unreadCount, + maxHeight, + composeViewHeight, + searchValue, + markChatRead, + listState, + loadMessages + ) + FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState) - LaunchedEffect(Unit) { - snapshotFlow { listState.value.isScrollInProgress } - .collect { - chatViewScrollState.value = it + LaunchedEffect(Unit) { + snapshotFlow { listState.value.isScrollInProgress } + .collect { + chatViewScrollState.value = it + } + } + LaunchedEffect(Unit) { + snapshotFlow { listState.value.isScrollInProgress } + .filter { !it } + .collect { + if (animatedScrollingInProgress.value) { + animatedScrollingInProgress.value = false } - } - LaunchedEffect(Unit) { - snapshotFlow { listState.value.isScrollInProgress } - .filter { !it } - .collect { - if (animatedScrollingInProgress.value) { - animatedScrollingInProgress.value = false - } - } - } + } } } @@ -2042,7 +2415,12 @@ private fun FloatingDate( if (listState.value.layoutInfo.visibleItemsInfo.lastIndex >= 0) { val lastVisibleChatItem = lastFullyVisibleIemInListState(topPaddingToContentPx, density, fontSizeSqrtMultiplier, mergedItems, listState) val timeZone = TimeZone.currentSystemDefault() - lastVisibleChatItem?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone) + val itemTs = lastVisibleChatItem?.meta?.itemTs + if (itemTs != null && itemTs.epochSeconds > 0) { + itemTs.toLocalDateTime(timeZone).date.atStartOfDayIn(timeZone) + } else { + null + } } else { null } @@ -2234,6 +2612,15 @@ fun reportsCount(staleChatId: String?): Int { } } +@Composable +fun supportUnreadCount(staleChatId: String?): Int { + return if (staleChatId?.startsWith("#") != true) { + 0 + } else { + remember(staleChatId) { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId } } }.value?.supportUnreadCount ?: 0 + } +} + private fun reversedChatItemsStatic(chatsCtx: ChatModel.ChatsContext): List = chatsCtx.chatItems.value.asReversed() @@ -2315,7 +2702,8 @@ private fun findQuotedItemFromItem( rhId: State, chatInfo: State, scope: CoroutineScope, - scrollToItem: (Long) -> Unit + scrollToItem: (Long) -> Unit, + scrollToItemId: MutableState ): (Long) -> Unit = { itemId: Long -> scope.launch(Dispatchers.Default) { val item = apiLoadSingleMessage(chatsCtx, rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId) @@ -2327,7 +2715,11 @@ private fun findQuotedItemFromItem( chatModel.secondaryChatsContext.value?.updateChatItem(chatInfo.value, item) } if (item.quotedItem?.itemId != null) { - scrollToItem(item.quotedItem.itemId) + if (item.isReport && chatsCtx.secondaryContextFilter != null) { + scrollToItemId.value = item.quotedItem.itemId + } else { + scrollToItem(item.quotedItem.itemId) + } } else { showQuotedItemDoesNotExistAlert() } @@ -2354,7 +2746,7 @@ fun openGroupLink(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: ( val link = chatModel.controller.apiGetGroupLink(rhId, groupInfo.groupId) close?.invoke() ModalManager.end.showModalCloseable(true) { - GroupLinkView(chatModel, rhId, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) + GroupLinkView(chatModel, rhId, groupInfo, link, onGroupLinkUpdated = null) } } } @@ -2499,6 +2891,7 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List + chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo) + } } withContext(Dispatchers.Main) { for (di in deleted) { - if (di.deletedChatItem.chatItem.isReport) { - val toChatItem = di.toChatItem?.chatItem - if (toChatItem != null) { - chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) - } else { - chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem) - } + val toChatItem = di.toChatItem?.chatItem + if (toChatItem != null) { + chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) + } else { + chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem) } } } @@ -2548,27 +2942,22 @@ private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List + chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo) + } } withContext(Dispatchers.Main) { for (di in deleted) { - if (di.deletedChatItem.chatItem.isReport) { - val toChatItem = di.toChatItem?.chatItem - if (toChatItem != null) { - chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) - } else { - chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem) - } + val toChatItem = di.toChatItem?.chatItem + if (toChatItem != null) { + chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) + } else { + chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem) } } } @@ -2920,9 +3309,9 @@ fun PreviewChatLayout() { val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) } val searchValue = remember { mutableStateOf("") } ChatLayout( - chatsCtx = ChatModel.ChatsContext(contentTag = null), + chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null), remoteHostId = remember { mutableStateOf(null) }, - chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) }, + chat = remember { mutableStateOf(Chat.sampleData) }, unreadCount = unreadCount, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, composeView = { _ -> }, @@ -2935,7 +3324,8 @@ fun PreviewChatLayout() { selectedChatItems = remember { mutableStateOf(setOf()) }, back = {}, info = {}, - showGroupReports = {}, + showReports = {}, + showSupportChats = {}, showMemberInfo = { _, _ -> }, loadMessages = { _, _, _ -> }, deleteMessage = { _, _ -> }, @@ -2968,7 +3358,8 @@ fun PreviewChatLayout() { onComposed = {}, developerTools = false, showViaProxy = false, - showSearch = remember { mutableStateOf(false) } + showSearch = remember { mutableStateOf(false) }, + showCommandsMenu = remember { mutableStateOf(false) } ) } } @@ -2998,9 +3389,9 @@ fun PreviewGroupChatLayout() { val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) } val searchValue = remember { mutableStateOf("") } ChatLayout( - chatsCtx = ChatModel.ChatsContext(contentTag = null), + chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null), remoteHostId = remember { mutableStateOf(null) }, - chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) }, + chat = remember { mutableStateOf(Chat.sampleData) }, unreadCount = unreadCount, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, composeView = { _ -> }, @@ -3013,7 +3404,8 @@ fun PreviewGroupChatLayout() { selectedChatItems = remember { mutableStateOf(setOf()) }, back = {}, info = {}, - showGroupReports = {}, + showReports = {}, + showSupportChats = {}, showMemberInfo = { _, _ -> }, loadMessages = { _, _, _ -> }, deleteMessage = { _, _ -> }, @@ -3046,7 +3438,8 @@ fun PreviewGroupChatLayout() { onComposed = {}, developerTools = false, showViaProxy = false, - showSearch = remember { mutableStateOf(false) } + showSearch = remember { mutableStateOf(false) }, + showCommandsMenu = remember { mutableStateOf(false) } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt new file mode 100644 index 0000000000..26b1fce741 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/CommandsMenuView.kt @@ -0,0 +1,244 @@ +package chat.simplex.common.views.chat + +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF +import chat.simplex.common.views.chat.group.* +import chat.simplex.common.views.chat.item.sendCommandMsg +import chat.simplex.common.views.helpers.commandMenuAnimSpec +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import kotlinx.coroutines.launch + +private val COMMAND_MENU_ROW_SIZE = 48.dp +private val MAX_COMMAND_MENU_HEIGHT = COMMAND_MENU_ROW_SIZE * 6 - 8.dp + +@Composable +fun CommandsMenuView( + chatsCtx: ChatModel.ChatsContext, + chat: Chat, + composeState: MutableState, + showCommandsMenu: MutableState +) { + val maxHeightInPx = with(LocalDensity.current) { windowHeight().toPx() } + val offsetY = remember { Animatable(maxHeightInPx) } + val scope = rememberCoroutineScope() + + val currentCommands = remember { mutableStateOf>(emptyList()) } + val menuTreeBackPath = remember { mutableStateOf>>>(emptyList()) } + + fun filterShownCommands(commands: List, msg: CharSequence): List { + val cmds = mutableListOf() + for (cmd in commands) { + when (cmd) { + is ChatBotCommand.Command -> + if (cmd.keyword.startsWith(msg)) { + cmds.add(cmd) + } + is ChatBotCommand.Menu -> + cmds.addAll(filterShownCommands(cmd.commands, msg)) + } + } + return cmds + } + + suspend fun closeCommandsMenu() { + showCommandsMenu.value = false + currentCommands.value = emptyList() + menuTreeBackPath.value = emptyList() + if (offsetY.value != 0f) { + return + } + offsetY.animateTo( + targetValue = maxHeightInPx, + animationSpec = commandMenuAnimSpec() + ) + } + + fun messageChanged(message: String) { + val msg = message.trim() + menuTreeBackPath.value = emptyList() + if (msg == "/") { + currentCommands.value = chat.chatInfo.menuCommands + } else if (msg.startsWith("/")) { + currentCommands.value = filterShownCommands(chat.chatInfo.menuCommands, msg.drop(1)) + } else { + scope.launch { closeCommandsMenu() } + } + } + + LaunchedEffect(currentCommands.value.isNotEmpty()) { + if (currentCommands.value.isNotEmpty()) { + offsetY.animateTo( + targetValue = 0f, + animationSpec = commandMenuAnimSpec() + ) + } + } + + LaunchedEffect(composeState.value.message) { + messageChanged(composeState.value.message.text) + } + + LaunchedEffect(showCommandsMenu.value) { + if (showCommandsMenu.value) { + currentCommands.value = chat.chatInfo.menuCommands + menuTreeBackPath.value = emptyList() + } else { + closeCommandsMenu() + } + } + + @Composable + fun MenuLabelRow(prev: Pair>) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(COMMAND_MENU_ROW_SIZE) + .clickable { + if (menuTreeBackPath.value.isNotEmpty()) { + currentCommands.value = menuTreeBackPath.value.last().second + menuTreeBackPath.value = menuTreeBackPath.value.dropLast(1) + } + }, + contentAlignment = Alignment.Center + ) { + Row(Modifier.padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { + Icon( + painterResource(MR.images.ic_arrow_back_ios_new), + contentDescription = null, + tint = MaterialTheme.colors.secondary + ) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + Text( + text = prev.first, + style = MaterialTheme.typography.body2, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Medium, + maxLines = 1, + modifier = Modifier.weight(1f), + overflow = TextOverflow.Ellipsis + ) + } + } + } + + @Composable + fun CommandRow(cmd: ChatBotCommand) { + when (cmd) { + is ChatBotCommand.Command -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(COMMAND_MENU_ROW_SIZE) + .clickable { + if (cmd.params != null) { + val msg = "/${cmd.keyword} ${cmd.params}" + composeState.value = ComposeState(message = ComposeMessage(msg, TextRange(msg.length)), useLinkPreviews = true) + } else { + composeState.value = ComposeState(message = ComposeMessage(), useLinkPreviews = true) + sendCommandMsg(chatsCtx, chat,"/${cmd.keyword}") + } + scope.launch { closeCommandsMenu() } + }, + contentAlignment = Alignment.Center + ) { + Row(Modifier.padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { + Text( + text = cmd.label, + style = MaterialTheme.typography.body1, + maxLines = 1, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Start, + overflow = TextOverflow.Ellipsis + ) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + Text( + text = "/${cmd.keyword}", + style = MaterialTheme.typography.body2, + maxLines = 1, + color = MaterialTheme.colors.secondary + ) + } + } + } + is ChatBotCommand.Menu -> + Box( + modifier = Modifier + .fillMaxWidth() + .height(COMMAND_MENU_ROW_SIZE) + .clickable { + menuTreeBackPath.value += Pair(cmd.label, currentCommands.value) + currentCommands.value = cmd.commands + }, + contentAlignment = Alignment.Center + ) { + Row(Modifier.padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { + Text( + text = cmd.label, + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Medium, + maxLines = 1, + modifier = Modifier.weight(1f), + overflow = TextOverflow.Ellipsis + ) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + Icon( + painterResource(MR.images.ic_chevron_right), + contentDescription = null, + tint = MaterialTheme.colors.secondary + ) + } + } + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .offset { IntOffset(0, offsetY.value.toInt()) } + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { + scope.launch { closeCommandsMenu() } + }, + contentAlignment = Alignment.BottomStart + ) { + LazyColumnWithScrollBarNoAppBar( + Modifier + .heightIn(max = MAX_COMMAND_MENU_HEIGHT) + .background(MaterialTheme.colors.surface), + maxHeight = remember { mutableStateOf(MAX_COMMAND_MENU_HEIGHT) }, + containerAlignment = Alignment.BottomEnd + ) { + itemsIndexed(currentCommands.value, key = { i, cmd -> "$i ${cmd.hashCode()}" }) { i, command -> + if (i == 0) { + val prev = menuTreeBackPath.value.lastOrNull() + if (prev != null) { + Divider() + MenuLabelRow(prev) + } + } + Divider() + Box(Modifier.fillMaxWidth()) { CommandRow(command) } + } + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt new file mode 100644 index 0000000000..106c975226 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextContactRequestActionsView.kt @@ -0,0 +1,165 @@ +package chat.simplex.common.views.chat + +import SectionItemView +import TextIconSpaced +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import chat.simplex.common.platform.chatModel +import chat.simplex.common.views.chatlist.acceptContactRequest +import chat.simplex.common.views.chatlist.rejectContactRequest +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.delay + +@Composable +fun ComposeContextContactRequestActionsView( + rhId: Long?, + contactRequestId: Long +) { + val inProgress = rememberSaveable { mutableStateOf(false) } + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + KeyChangeEffect(chatModel.chatId.value) { + if (inProgress.value) { + inProgress.value = false + progressByTimeout = false + } + } + + LaunchedEffect(inProgress.value) { + progressByTimeout = if (inProgress.value) { + delay(1000) + inProgress.value + } else { + false + } + } + + Box( + Modifier.height(60.dp), + contentAlignment = Alignment.Center + ) { + Column( + Modifier + .background(MaterialTheme.colors.surface) + .alpha(if (progressByTimeout) 0.6f else 1f) + ) { + Divider() + + Row( + Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + var rejectButtonModifier = Modifier.fillMaxWidth().fillMaxHeight().weight(1F) + rejectButtonModifier = + if (inProgress.value) rejectButtonModifier + else rejectButtonModifier.clickable { showRejectRequestAlert(rhId, contactRequestId) } + Row( + rejectButtonModifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_close), + contentDescription = null, + tint = if (inProgress.value) MaterialTheme.colors.secondary else Color.Red, + ) + TextIconSpaced(false) + Text( + stringResource(MR.strings.reject_contact_button), + color = if (inProgress.value) MaterialTheme.colors.secondary else Color.Red + ) + } + var acceptButtonModifier = Modifier.fillMaxWidth().fillMaxHeight().weight(1F) + acceptButtonModifier = + if (inProgress.value) acceptButtonModifier + else + acceptButtonModifier.clickable { + if (chatModel.addressShortLinkDataSet()) { + acceptContactRequest(rhId, incognito = false, contactRequestId, isCurrentUser = true, chatModel = chatModel, close = null, inProgress = inProgress) + } else { + showAcceptRequestAlert(rhId, contactRequestId, inProgress = inProgress) + } + } + Row( + acceptButtonModifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_check), + contentDescription = null, + tint = if (inProgress.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + ) + TextIconSpaced(false) + Text( + stringResource(MR.strings.accept_contact_button), + color = if (inProgress.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + } + } + } + + if (progressByTimeout) { + ComposeProgressIndicator() + } + } +} + +fun showRejectRequestAlert(rhId: Long?, contactRequestId: Long) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.reject_contact_request), + text = generalGetString(MR.strings.the_sender_will_not_be_notified), + confirmText = generalGetString(MR.strings.reject_contact_button), + onConfirm = { + AlertManager.shared.hideAlert() + rejectContactRequest(rhId, contactRequestId, chatModel, dismissToChatList = true) + }, + destructive = true, + hostDevice = hostDevice(rhId), + ) +} + +fun showAcceptRequestAlert(rhId: Long?, contactRequestId: Long, inProgress: MutableState) { + AlertManager.shared.showAlertDialogButtonsColumn( + title = generalGetString(MR.strings.accept_contact_request), + buttons = { + Column { + // Accept + SectionItemView({ + AlertManager.shared.hideAlert() + acceptContactRequest(rhId, incognito = false, contactRequestId, isCurrentUser = true, chatModel = chatModel, close = null, inProgress = inProgress) + }) { + Text(generalGetString(MR.strings.accept_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + // Accept incognito + SectionItemView({ + AlertManager.shared.hideAlert() + acceptContactRequest(rhId, incognito = true, contactRequestId, isCurrentUser = true, chatModel = chatModel, close = null, inProgress = inProgress) + }) { + Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + // Cancel + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + }, + hostDevice = hostDevice(rhId), + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt new file mode 100644 index 0000000000..5c8ac3bc5d --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextGroupDirectInvitationActionsView.kt @@ -0,0 +1,172 @@ +package chat.simplex.common.views.chat + +import TextIconSpaced +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.platform.chatModel +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.* + +@Composable +fun ComposeContextMemberContactActionsView( + rhId: Long?, + contact: Contact, + groupDirectInv: GroupDirectInvitation +) { + val inProgress = rememberSaveable { mutableStateOf(false) } + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + KeyChangeEffect(chatModel.chatId.value) { + if (inProgress.value) { + inProgress.value = false + progressByTimeout = false + } + } + + LaunchedEffect(inProgress.value) { + progressByTimeout = if (inProgress.value) { + delay(1000) + inProgress.value + } else { + false + } + } + + Box( + Modifier.height(60.dp), + contentAlignment = Alignment.Center + ) { + Column( + Modifier + .background(MaterialTheme.colors.surface) + .alpha(if (progressByTimeout) 0.6f else 1f) + ) { + Divider() + + if (groupDirectInv.memberRemoved) { + Row( + Modifier + .fillMaxSize() + .padding(horizontal = DEFAULT_PADDING_HALF), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) + ) { + Icon(painterResource(MR.images.ic_info), contentDescription = null, tint = MaterialTheme.colors.secondary) + Text(generalGetString(MR.strings.member_is_deleted_cant_accept_request), color = MaterialTheme.colors.secondary) + } + } else { + Row( + Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + var rejectButtonModifier = Modifier.fillMaxWidth().fillMaxHeight().weight(1F) + rejectButtonModifier = + if (inProgress.value) rejectButtonModifier + else rejectButtonModifier.clickable { showRejectMemberContactRequestAlert(rhId, contact) } + Row( + rejectButtonModifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_close), + contentDescription = null, + tint = if (inProgress.value) MaterialTheme.colors.secondary else Color.Red, + ) + TextIconSpaced(false) + Text( + stringResource(MR.strings.reject_contact_button), + color = if (inProgress.value) MaterialTheme.colors.secondary else Color.Red + ) + } + var acceptButtonModifier = Modifier.fillMaxWidth().fillMaxHeight().weight(1F) + acceptButtonModifier = + if (inProgress.value) acceptButtonModifier + else acceptButtonModifier.clickable { acceptMemberContact(rhId, contact.contactId, inProgress = inProgress) } + Row( + acceptButtonModifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_check), + contentDescription = null, + tint = if (inProgress.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + ) + TextIconSpaced(false) + Text( + stringResource(MR.strings.accept_contact_button), + color = if (inProgress.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + } + } + } + } + + if (progressByTimeout) { + ComposeProgressIndicator() + } + } +} + +fun showRejectMemberContactRequestAlert(rhId: Long?, contact: Contact) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.reject_contact_request), + text = generalGetString(MR.strings.the_sender_will_not_be_notified), + confirmText = generalGetString(MR.strings.reject_contact_button), + onConfirm = { + AlertManager.shared.hideAlert() + deleteMemberContact(rhId, contact) + }, + destructive = true, + hostDevice = hostDevice(rhId), + ) +} + +private fun deleteMemberContact(rhId: Long?, contact: Contact) { + withBGApi { + chatModel.controller.apiDeleteContact(rhId, contact.contactId, chatDeleteMode = ContactDeleteMode.Full().toChatDeleteMode(notify = false)) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, contact.id) + chatModel.chatId.value = null + } + } +} + +fun acceptMemberContact( + rhId: Long?, + contactId: Long, + close: ((chat: Chat) -> Unit)? = null, + inProgress: MutableState? = null +) { + withBGApi { + inProgress?.value = true + val contact = chatModel.controller.apiAcceptMemberContact(rhId, contactId) + if (contact != null) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, contact) + inProgress?.value = false + } + chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected()) + val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf()) + close?.invoke(chat) + } else { + inProgress?.value = false + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt deleted file mode 100644 index bc82bc593f..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt +++ /dev/null @@ -1,39 +0,0 @@ -package chat.simplex.common.views.chat - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.generalGetString -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource - -@Composable -fun ComposeContextInvitingContactMemberView() { - val sentColor = MaterialTheme.appColors.sentMessage - Row( - Modifier - .height(60.dp) - .fillMaxWidth() - .padding(top = 8.dp) - .background(sentColor), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painterResource(MR.images.ic_chat), - stringResource(MR.strings.button_send_direct_message), - modifier = Modifier - .padding(start = 12.dp, end = 8.dp) - .height(20.dp) - .width(20.dp), - tint = MaterialTheme.colors.secondary - ) - Text(generalGetString(MR.strings.compose_send_direct_message_to_connect)) - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt new file mode 100644 index 0000000000..3c3f99ad94 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt @@ -0,0 +1,126 @@ +package chat.simplex.common.views.chat + +import SectionItemView +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.platform.chatModel +import chat.simplex.common.views.chat.group.removeMember +import chat.simplex.common.views.chat.group.removeMemberDialog +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Composable +fun ComposeContextPendingMemberActionsView( + rhId: Long?, + groupInfo: GroupInfo, + member: GroupMember +) { + Column( + Modifier + .height(60.dp) + .background(MaterialTheme.colors.surface) + ) { + Divider() + + Row( + Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Column( + Modifier + .fillMaxWidth() + .fillMaxHeight() + .weight(1F) + .clickable { + rejectMemberDialog(rhId, member, chatModel, close = { ModalManager.end.closeModal() }) + }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(stringResource(MR.strings.reject_pending_member_button), color = Color.Red) + } + + Column( + Modifier + .fillMaxWidth() + .fillMaxHeight() + .weight(1F) + .clickable { + acceptMemberDialog(rhId, groupInfo, member, close = { ModalManager.end.closeModal() }) + }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(stringResource(MR.strings.accept_pending_member_button), color = MaterialTheme.colors.primary) + } + } + } +} + +fun rejectMemberDialog(rhId: Long?, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.reject_pending_member_alert_title), + confirmText = generalGetString(MR.strings.reject_pending_member_button), + onConfirm = { + removeMember(rhId, member, chatModel, close) + }, + destructive = true, + ) +} + +fun acceptMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, close: (() -> Unit)? = null) { + AlertManager.shared.showAlertDialogButtonsColumn( + title = generalGetString(MR.strings.accept_pending_member_alert_title), + text = generalGetString(MR.strings.accept_pending_member_alert_question), + buttons = { + Column { + // Accept as member + SectionItemView({ + AlertManager.shared.hideAlert() + acceptMember(rhId, groupInfo, member, GroupMemberRole.Member, close) + }) { + Text(generalGetString(MR.strings.accept_pending_member_alert_confirmation_as_member), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + // Accept as observer + SectionItemView({ + AlertManager.shared.hideAlert() + acceptMember(rhId, groupInfo, member, GroupMemberRole.Observer, close) + }) { + Text(generalGetString(MR.strings.accept_pending_member_alert_confirmation_as_observer), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + // Cancel + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + ) +} + +private fun acceptMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, role: GroupMemberRole, close: (() -> Unit)?) { + withBGApi { + val r = chatModel.controller.apiAcceptMember(rhId, groupInfo.groupId, member.groupMemberId, role) + if (r != null) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second) + chatModel.chatsContext.updateGroup(rhId, r.first) + } + } + close?.invoke() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt new file mode 100644 index 0000000000..ce023e83c9 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt @@ -0,0 +1,302 @@ +package chat.simplex.common.views.chat + +import TextIconSpaced +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.IncognitoOptionImage +import chat.simplex.common.views.usersettings.IncognitoView +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +private val USER_ROW_AVATAR_SIZE = 42.dp +private val USER_ROW_VERTICAL_PADDING = 8.dp +private val USER_PICKER_ROW_SIZE = USER_ROW_AVATAR_SIZE + (USER_ROW_VERTICAL_PADDING * 2f) +private val MAX_USER_PICKER_HEIGHT = (USER_PICKER_ROW_SIZE * 4) + (USER_ROW_AVATAR_SIZE + USER_ROW_VERTICAL_PADDING - 4.dp) + +@Composable +fun ComposeContextProfilePickerView( + rhId: Long?, + chat: Chat, + currentUser: User +) { + val selectedUser = remember { mutableStateOf(currentUser) } + val incognitoDefault = chatModel.controller.appPrefs.incognito.get() + val users = chatModel.users.map { it.user }.filter { u -> u.activeUser || !u.hidden } + val listExpanded = remember { mutableStateOf(false) } + + val maxHeightInPx = with(LocalDensity.current) { windowHeight().toPx() } + val isVisible = remember { mutableStateOf(false) } + val offsetY = remember { Animatable(maxHeightInPx) } + + LaunchedEffect(isVisible.value) { + if (isVisible.value) { + offsetY.animateTo( + targetValue = 0f, + animationSpec = contextUserPickerAnimSpec() + ) + } + } + + @Composable + fun ExpandCollapseChevron() { + if (listExpanded.value) { + Icon( + painterResource( + MR.images.ic_chevron_down + ), + contentDescription = null, + Modifier.size(20.dp), + tint = MaterialTheme.colors.secondary, + ) + } else if (!chat.chatInfo.profileChangeProhibited) { + Icon( + painterResource( + MR.images.ic_chevron_up + ), + contentDescription = null, + Modifier.size(20.dp), + tint = MaterialTheme.colors.secondary, + ) + } + } + + fun changeProfile(newUser: User) { + withApi { + if (chat.chatInfo is ChatInfo.Direct) { + val updatedContact = chatModel.controller.apiChangePreparedContactUser(rhId, chat.chatInfo.contact.contactId, newUser.userId) + if (updatedContact != null) { + selectedUser.value = newUser + chatModel.controller.appPrefs.incognito.set(false) + listExpanded.value = false + chatModel.chatsContext.updateContact(rhId, updatedContact) + } + } else if (chat.chatInfo is ChatInfo.Group) { + val updatedGroup = chatModel.controller.apiChangePreparedGroupUser(rhId, chat.chatInfo.groupInfo.groupId, newUser.userId) + if (updatedGroup != null) { + selectedUser.value = newUser + chatModel.controller.appPrefs.incognito.set(false) + listExpanded.value = false + chatModel.chatsContext.updateGroup(rhId, updatedGroup) + } + } + chatModel.controller.changeActiveUser_( + rhId = newUser.remoteHostId, + toUserId = newUser.userId, + viewPwd = null, + keepingChatId = chat.id + ) + if (chatModel.currentUser.value?.userId != newUser.userId) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.switching_profile_error_title), + String.format(generalGetString(MR.strings.switching_profile_error_message), newUser.chatViewName) + ) + } + } + } + + fun showCantChangeProfileAlert() { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.context_user_picker_cant_change_profile_alert_title), + generalGetString(MR.strings.context_user_picker_cant_change_profile_alert_message) + ) + } + + @Composable + fun ProfilePickerUserOption(user: User) { + Row( + Modifier + .fillMaxWidth() + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp) + .clickable(onClick = { + if (!chat.chatInfo.profileChangeProhibited) { + if (selectedUser.value.userId == user.userId) { + if (!incognitoDefault) { + listExpanded.value = !listExpanded.value + } else { + chatModel.controller.appPrefs.incognito.set(false) + listExpanded.value = false + } + } else { + changeProfile(user) + } + } else { + showCantChangeProfileAlert() + } + }) + .padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + ProfileImage(size = USER_ROW_AVATAR_SIZE, image = user.image) + TextIconSpaced(false) + Text( + user.chatViewName, + modifier = Modifier.align(Alignment.CenterVertically), + fontWeight = if (selectedUser.value.userId == user.userId && !incognitoDefault) FontWeight.Medium else FontWeight.Normal + ) + + Spacer(Modifier.weight(1f)) + + if (selectedUser.value.userId == user.userId && !incognitoDefault) { + ExpandCollapseChevron() + } + } + } + + @Composable + fun IncognitoOption() { + Row( + Modifier + .fillMaxWidth() + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp) + .clickable(onClick = { + if (!chat.chatInfo.profileChangeProhibited) { + if (incognitoDefault) { + listExpanded.value = !listExpanded.value + } else { + chatModel.controller.appPrefs.incognito.set(true) + listExpanded.value = false + } + } else { + showCantChangeProfileAlert() + } + }) + .padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + IncognitoOptionImage() + TextIconSpaced(false) + Text( + stringResource(MR.strings.incognito), + modifier = Modifier.align(Alignment.CenterVertically), + fontWeight = if (incognitoDefault) FontWeight.Medium else FontWeight.Normal + ) + Spacer(Modifier.padding(6.dp)) + Column(Modifier + .size(48.dp) + .clip(CircleShape) + .clickable( + onClick = { + if (ModalManager.end.isLastModalOpen(ModalViewId.CONTEXT_USER_PICKER_INCOGNITO)) { + ModalManager.end.closeModal() + } else { + ModalManager.end.showModal(id = ModalViewId.CONTEXT_USER_PICKER_INCOGNITO) { IncognitoView() } + } + } + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_info), + stringResource(MR.strings.incognito), + tint = MaterialTheme.colors.primary + ) + } + + Spacer(Modifier.weight(1f)) + + if (incognitoDefault) { + ExpandCollapseChevron() + } + } + } + + @Composable + fun ProfilePicker() { + LazyColumnWithScrollBarNoAppBar( + Modifier + .heightIn(max = MAX_USER_PICKER_HEIGHT) + .background(MaterialTheme.colors.surface), + reverseLayout = true, + maxHeight = remember { mutableStateOf(MAX_USER_PICKER_HEIGHT) }, + containerAlignment = Alignment.BottomEnd + ) { + val otherUsers = users.filter { u -> u.userId != selectedUser.value.userId }.sortedByDescending { it.activeOrder } + + if (incognitoDefault) { + item { + IncognitoOption() + Divider( + Modifier.padding( + start = DEFAULT_PADDING_HALF, + end = DEFAULT_PADDING_HALF, + ) + ) + ProfilePickerUserOption(selectedUser.value) + } + } else { + item { + ProfilePickerUserOption(selectedUser.value) + Divider( + Modifier.padding( + start = DEFAULT_PADDING_HALF, + end = DEFAULT_PADDING_HALF, + ) + ) + IncognitoOption() + } + } + + items(otherUsers, key = { it.userId }) { user -> + Divider( + Modifier.padding( + start = DEFAULT_PADDING_HALF, + end = DEFAULT_PADDING_HALF, + ) + ) + ProfilePickerUserOption(user) + } + } + } + + @Composable + fun CurrentSelection() { + Column( + Modifier + .background(MaterialTheme.colors.surface), + ) { + Text( + generalGetString(MR.strings.context_user_picker_your_profile), + Modifier.padding(horizontal = 14.dp).padding(top = 8.dp), + color = MaterialTheme.colors.secondary + ) + + if (chat.chatInfo.profileChangeProhibited) { + if (chat.chatInfo.incognito) { + IncognitoOption() + } else { + ProfilePickerUserOption(selectedUser.value) + } + } else if (incognitoDefault) { + IncognitoOption() + } else { + ProfilePickerUserOption(selectedUser.value) + } + } + } + + if (!listExpanded.value || chat.chatInfo.profileChangeProhibited) { + CurrentSelection() + } else { + ProfilePicker() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 894bcf3b37..b01d07d9b8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.chat import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* @@ -16,7 +17,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.TextDecoration import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp @@ -30,6 +33,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import dev.icerock.moko.resources.ImageResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.serialization.* @@ -39,6 +43,7 @@ import kotlinx.serialization.encoding.Encoder import java.io.File import java.net.URI import java.nio.file.Files +import kotlin.math.min const val MAX_NUMBER_OF_MENTIONS = 3 @@ -95,6 +100,7 @@ data class ComposeState( val preview: ComposePreview = ComposePreview.NoPreview, val contextItem: ComposeContextItem = ComposeContextItem.NoContextItem, val inProgress: Boolean = false, + val progressByTimeout: Boolean = false, val useLinkPreviews: Boolean, val mentions: MentionedMembers = emptyMap() ) { @@ -151,7 +157,7 @@ data class ComposeState( is ComposePreview.MediaPreview -> true is ComposePreview.VoicePreview -> true is ComposePreview.FilePreview -> true - else -> message.text.isNotEmpty() || forwarding || liveMessage != null || submittingValidReport + else -> !whitespaceOnly || forwarding || liveMessage != null || submittingValidReport } hasContent && !inProgress } @@ -199,7 +205,10 @@ data class ComposeState( } val empty: Boolean - get() = message.text.isEmpty() && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem + get() = whitespaceOnly && preview is ComposePreview.NoPreview && contextItem is ComposeContextItem.NoContextItem + + val whitespaceOnly: Boolean + get() = message.text.all { it.isWhitespace() } companion object { fun saver(): Saver, *> = Saver( @@ -245,6 +254,7 @@ fun chatItemPreview(chatItem: ChatItem): ComposePreview { is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = fileName, mc.duration / 1000, true) is MsgContent.MCFile -> ComposePreview.FilePreview(fileName, getAppFileUri(fileName)) is MsgContent.MCReport -> ComposePreview.NoPreview + is MsgContent.MCChat -> ComposePreview.NoPreview is MsgContent.MCUnknown, null -> ComposePreview.NoPreview } } @@ -333,9 +343,12 @@ suspend fun MutableState.processPickedMedia(uris: List, text: @Composable fun ComposeView( + rhId: Long?, chatModel: ChatModel, + chatsCtx: ChatModel.ChatsContext, chat: Chat, composeState: MutableState, + showCommandsMenu: MutableState, attachmentOption: MutableState, showChooseAttachment: () -> Unit, focusRequester: FocusRequester?, @@ -344,19 +357,24 @@ fun ComposeView( fun isSimplexLink(link: String): Boolean = link.startsWith("https://simplex.chat", true) || link.startsWith("http://simplex.chat", true) - fun getSimplexLink(parsedMsg: List?): Pair { + fun getMessageLinks(parsedMsg: List?): Pair { if (parsedMsg == null) return null to false - val link = parsedMsg.firstOrNull { ft -> ft.format is Format.Uri && !cancelledLinks.contains(ft.text) && !isSimplexLink(ft.text) } val simplexLink = parsedMsg.any { ft -> ft.format is Format.SimplexLink } - return link?.text to simplexLink + for (ft in parsedMsg) { + val link = ft.linkUri + if (link != null && !cancelledLinks.contains(link) && !isSimplexLink(link)) { + return link to simplexLink + } + } + return null to simplexLink } val linkUrl = rememberSaveable { mutableStateOf(null) } // default value parsed because of draft - val hasSimplexLink = rememberSaveable { mutableStateOf(getSimplexLink(parseToMarkdown(composeState.value.message.text)).second) } + val hasSimplexLink = rememberSaveable { mutableStateOf(getMessageLinks(parseToMarkdown(composeState.value.message.text)).second) } val prevLinkUrl = rememberSaveable { mutableStateOf(null) } val pendingLinkUrl = rememberSaveable { mutableStateOf(null) } - val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() + val useLinkPreviews = true val saveLastDraft = chatModel.controller.appPrefs.privacySaveLastDraft.get() val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember(MaterialTheme.colors.isLight) { mutableStateOf(smallFont) } @@ -370,6 +388,7 @@ fun ComposeView( if (wait != null) delay(wait) val lp = getLinkPreview(url) if (lp != null && pendingLinkUrl.value == url) { + chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.set(false) // to avoid showing alert to current users, show alert in v6.5 composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(lp)) pendingLinkUrl.value = null } else if (pendingLinkUrl.value == url) { @@ -382,7 +401,7 @@ fun ComposeView( fun showLinkPreview(parsedMessage: List?) { prevLinkUrl.value = linkUrl.value - val linkParsed = getSimplexLink(parsedMessage) + val linkParsed = getMessageLinks(parsedMessage) linkUrl.value = linkParsed.first hasSimplexLink.value = linkParsed.second val url = linkUrl.value @@ -419,7 +438,7 @@ fun ComposeView( fun clearState(live: Boolean = false) { if (live) { - composeState.value = composeState.value.copy(inProgress = false) + composeState.value = composeState.value.copy(inProgress = false, progressByTimeout = false) } else { composeState.value = ComposeState(useLinkPreviews = useLinkPreviews) resetLinkPreview() @@ -466,6 +485,7 @@ fun ComposeView( rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, + scope = cInfo.groupChatScope(), live = live, ttl = ttl, composedMessages = listOf(ComposedMessage(file, quoted, mc, mentions)) @@ -473,7 +493,7 @@ fun ComposeView( if (!chatItems.isNullOrEmpty()) { chatItems.forEach { aChatItem -> withContext(Dispatchers.Main) { - chatModel.chatsContext.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) + chatsCtx.addChatItem(chat.remoteHostId, aChatItem.chatInfo, aChatItem.chatItem) } } return chatItems.first().chatItem @@ -482,31 +502,128 @@ fun ComposeView( return null } + // TODO [short links] connectCheckLinkPreview + fun checkLinkPreview(): MsgContent { + val msgText = composeState.value.message.text + return when (val composePreview = composeState.value.preview) { + is ComposePreview.CLinkPreview -> { + val parsedMsg = parseToMarkdown(msgText) + val url = getMessageLinks(parsedMsg).first + val lp = composePreview.linkPreview + if (lp != null && url == lp.uri) { + MsgContent.MCLink(msgText, preview = lp) + } else { + MsgContent.MCText(msgText) + } + } + + else -> MsgContent.MCText(msgText) + } + } + + fun sending() { + composeState.value = composeState.value.copy(inProgress = true) + } + + suspend fun sendMemberContactInvitation() { + val mc = checkLinkPreview() + sending() + val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc) + if (contact != null) { + withContext(Dispatchers.Main) { + chatsCtx.updateContact(chat.remoteHostId, contact) + clearState() + chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected()) + } + } else { + composeState.value = composeState.value.copy(inProgress = false) + } + } + + suspend fun sendConnectPreparedContact() { + val mc = checkLinkPreview() + sending() + val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get() + val contact = chatModel.controller.apiConnectPreparedContact( + rh = chat.remoteHostId, + contactId = chat.chatInfo.apiId, + incognito = incognito, + msg = mc + ) + if (contact != null) { + withContext(Dispatchers.Main) { + chatsCtx.updateContact(chat.remoteHostId, contact) + clearState() + chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected()) + } + } else { + composeState.value = composeState.value.copy(inProgress = false) + } + } + + fun showSendConnectPreparedContactAlert() { + val empty = composeState.value.whitespaceOnly + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(MR.strings.compose_view_send_contact_request_alert_question), + text = generalGetString(MR.strings.compose_view_send_contact_request_alert_text), + confirmText = ( + if (empty) + generalGetString(MR.strings.compose_view_send_request_without_message) + else + generalGetString(MR.strings.compose_view_send_request) + ), + onConfirm = { withApi { sendConnectPreparedContact() } }, + dismissText = ( + if (empty) + generalGetString(MR.strings.compose_view_add_message) + else + generalGetString(MR.strings.cancel_verb) + ) + ) + } + + suspend fun connectPreparedGroup() { + val mc = checkLinkPreview() + sending() + val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get() + val groupInfo = chatModel.controller.apiConnectPreparedGroup( + rh = chat.remoteHostId, + groupId = chat.chatInfo.apiId, + incognito = incognito, + msg = mc + ) + if (groupInfo != null) { + withContext(Dispatchers.Main) { + chatsCtx.updateGroup(chat.remoteHostId, groupInfo) + clearState() + } + } else { + composeState.value = composeState.value.copy(inProgress = false) + } + } + suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): List? { - val cInfo = chat.chatInfo val cs = composeState.value var sent: List? var lastMessageFailedToSend: ComposeState? = null val msgText = text ?: cs.message.text - fun sending() { - composeState.value = composeState.value.copy(inProgress = true) - } - suspend fun forwardItem(rhId: Long?, forwardedItem: List, fromChatInfo: ChatInfo, ttl: Int?): List? { val chatItems = controller.apiForwardChatItems( rh = rhId, toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, + toScope = chat.chatInfo.groupChatScope(), fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, + fromScope = fromChatInfo.groupChatScope(), itemIds = forwardedItem.map { it.id }, ttl = ttl ) withContext(Dispatchers.Main) { chatItems?.forEach { chatItem -> - chatModel.chatsContext.addChatItem(rhId, chat.chatInfo, chatItem) + chatsCtx.addChatItem(rhId, chat.chatInfo, chatItem) } } @@ -520,23 +637,6 @@ fun ComposeView( return chatItems } - fun checkLinkPreview(): MsgContent { - return when (val composePreview = cs.preview) { - is ComposePreview.CLinkPreview -> { - val parsedMsg = parseToMarkdown(msgText) - val url = getSimplexLink(parsedMsg).first - val lp = composePreview.linkPreview - if (lp != null && url == lp.uri) { - MsgContent.MCLink(msgText, preview = lp) - } else { - MsgContent.MCText(msgText) - } - } - - else -> MsgContent.MCText(msgText) - } - } - fun constructFailedMessage(cs: ComposeState): ComposeState { val preview = when (cs.preview) { is ComposePreview.MediaPreview -> { @@ -559,31 +659,28 @@ fun ComposeView( is MsgContent.MCVoice -> MsgContent.MCVoice(msgText, duration = msgContent.duration) is MsgContent.MCFile -> MsgContent.MCFile(msgText) is MsgContent.MCReport -> MsgContent.MCReport(msgText, reason = msgContent.reason) + // TODO [short links] update chat link + is MsgContent.MCChat -> MsgContent.MCChat(msgText, chatLink = msgContent.chatLink) is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = msgText, json = msgContent.json) } } - suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List? { - val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText) - if (cItems != null) { - withContext(Dispatchers.Main) { - cItems.forEach { chatItem -> - chatModel.chatsContext.addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem) - } - } - } - - return cItems?.map { it.chatItem } + fun showReportsInSupportChatAlert() { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.report_sent_alert_title), + text = generalGetString(MR.strings.report_sent_alert_msg_view_in_support_chat), + confirmText = generalGetString(MR.strings.ok), + dismissText = generalGetString(MR.strings.dont_show_again), + onDismiss = { + chatModel.controller.appPrefs.showReportsInSupportChatAlert.set(false) + }, + ) } - suspend fun sendMemberContactInvitation() { - val mc = checkLinkPreview() - val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc) - if (contact != null) { - withContext(Dispatchers.Main) { - chatModel.chatsContext.updateContact(chat.remoteHostId, contact) - } - } + suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List? { + val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText) + if (chatModel.controller.appPrefs.showReportsInSupportChatAlert.get()) showReportsInSupportChatAlert() + return cItems?.map { it.chatItem } } suspend fun updateMessage(ei: ChatItem, chat: Chat, live: Boolean): ChatItem? { @@ -594,13 +691,14 @@ fun ComposeView( rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, + scope = cInfo.groupChatScope(), itemId = ei.meta.itemId, updatedMessage = UpdatedMessage(updateMsgContent(oldMsgContent), cs.memberMentions), live = live ) if (updatedItem != null) { withContext(Dispatchers.Main) { - chatModel.chatsContext.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem) + chatsCtx.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem) } } return updatedItem?.chatItem @@ -617,10 +715,7 @@ fun ComposeView( clearCurrentDraft() } - if (chat.nextSendGrpInv) { - sendMemberContactInvitation() - sent = null - } else if (cs.contextItem is ComposeContextItem.ForwardingItems) { + if (cs.contextItem is ComposeContextItem.ForwardingItems) { sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItems, cs.contextItem.fromChatInfo, ttl = ttl) if (sent == null) { lastMessageFailedToSend = constructFailedMessage(cs) @@ -773,24 +868,70 @@ fun ComposeView( } } + fun sanitizeMessage(parsedMsg: List): Triple, Int?> { + var pos = 0 + var updatedMsg = "" + var sanitizedPos: Int? = null + val updatedParsedMsg = parsedMsg.map { ft -> + var updated = ft + when(ft.format) { + is Format.Uri -> { + val sanitized = parseSanitizeUri(ft.text, safe = true)?.uriInfo?.sanitized + if (sanitized != null) { + updated = FormattedText(text = sanitized, format = Format.Uri()) + pos += updated.text.count() + sanitizedPos = pos + } + } + is Format.HyperLink -> { + val sanitized = parseSanitizeUri(ft.format.linkUri, safe = true)?.uriInfo?.sanitized + if (sanitized != null) { + val updatedText = if (ft.format.showText == null) sanitized else "[${ft.format.showText}]($sanitized)" + updated = FormattedText(text = updatedText, format = Format.HyperLink(showText = ft.format.showText, linkUri = sanitized)) + pos += updated.text.count() + sanitizedPos = pos + } + } + else -> + pos += ft.text.count() + } + updatedMsg += updated.text + updated + } + return Triple(updatedMsg, updatedParsedMsg, sanitizedPos) + } + fun onMessageChange(s: ComposeMessage) { - val parsedMessage = parseToMarkdown(s.text) - composeState.value = composeState.value.copy(message = s, parsedMessage = parsedMessage ?: FormattedText.plain(s.text)) + var parsedMessage = parseToMarkdown(s.text) + if (chatModel.controller.appPrefs.privacySanitizeLinks.get() && parsedMessage != null) { + val (updatedMsg, updatedParsedMsg, sanitizedPos) = sanitizeMessage(parsedMessage) + if (sanitizedPos == null) { + composeState.value = composeState.value.copy(message = s, parsedMessage = parsedMessage) + } else { + val pos = min(updatedMsg.count(), if (sanitizedPos < s.selection.start) s.selection.start else sanitizedPos) + val message = s.copy(text = updatedMsg, selection = TextRange(pos)) + composeState.value = composeState.value.copy(message = message, parsedMessage = updatedParsedMsg) + parsedMessage = updatedParsedMsg + } + } else { + composeState.value = composeState.value.copy(message = s, parsedMessage = parsedMessage ?: FormattedText.plain(s.text)) + } if (isShortEmoji(s.text)) { textStyle.value = if (s.text.codePoints().count() < 4) largeEmojiFont else mediumEmojiFont } else { textStyle.value = smallFont - if (composeState.value.linkPreviewAllowed) { + if (composeState.value.linkPreviewAllowed && chatModel.controller.appPrefs.privacyLinkPreviews.get()) { if (s.text.isNotEmpty()) { showLinkPreview(parsedMessage) } else { resetLinkPreview() hasSimplexLink.value = false + composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) } - } else if (s.text.isNotEmpty() && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)) { - hasSimplexLink.value = getSimplexLink(parsedMessage).second } else { - hasSimplexLink.value = false + resetLinkPreview() + hasSimplexLink.value = s.text.isNotEmpty() && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks) && getMessageLinks(parsedMessage).second + if (composeState.value.linkPreviewAllowed) composeState.value = composeState.value.copy(preview = ComposePreview.NoPreview) } } } @@ -891,7 +1032,7 @@ fun ComposeView( fun editPrevMessage() { if (composeState.value.contextItem != ComposeContextItem.NoContextItem || composeState.value.preview != ComposePreview.NoPreview) return - val lastEditable = chatModel.chatsContext.chatItems.value.findLast { it.meta.editable } + val lastEditable = chatsCtx.chatItems.value.findLast { it.meta.editable } if (lastEditable != null) { composeState.value = ComposeState(editingItem = lastEditable, useLinkPreviews = useLinkPreviews) } @@ -974,6 +1115,296 @@ fun ComposeView( } } + val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled) + val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason) + val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv) + + @Composable + fun CommandsButton() { + val commandsEnabled = chat.chatInfo.sendMsgEnabled && chat.chatInfo.menuCommands.isNotEmpty() + IconButton( + onClick = { showCommandsMenu.value = !showCommandsMenu.value }, + enabled = commandsEnabled + ) { + Box( + modifier = Modifier.size(28.dp).clip(CircleShape), + contentAlignment = Alignment.Center + ) { + Text("//", style = MaterialTheme.typography.h3.copy(fontStyle = FontStyle.Italic, color = if (commandsEnabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary)) + } + } + } + + @Composable + fun AttachmentButton() { + val isGroupAndProhibitedFiles = + chatsCtx.secondaryContextFilter == null + && chat.chatInfo is ChatInfo.Group + && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership) + val attachmentClicked = if (isGroupAndProhibitedFiles) { + { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.files_and_media_prohibited), + text = generalGetString(MR.strings.only_owners_can_enable_files_and_media) + ) + } + } else { + showChooseAttachment + } + val attachmentEnabled = + !composeState.value.attachmentDisabled + && sendMsgEnabled.value + && !isGroupAndProhibitedFiles + && !nextSendGrpInv.value + IconButton( + attachmentClicked, + enabled = attachmentEnabled + ) { + Icon( + painterResource(MR.images.ic_attach_file_filled_500), + contentDescription = stringResource(MR.strings.attach), + tint = if (attachmentEnabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + modifier = Modifier + .size(28.dp) + .clip(CircleShape) + ) + } + } + + @Composable + fun AttachmentAndCommandsButtons() { + val cInfo = chat.chatInfo + Row( + Modifier.padding(start = 3.dp, end = 1.dp, bottom = if (appPlatform.isAndroid) 2.sp.toDp() else 5.sp.toDp() * fontSizeSqrtMultiplier), + horizontalArrangement = Arrangement.spacedBy((-8).dp) + ) { + val msg = composeState.value.message.text.trim() + val showAttachment = cInfo !is ChatInfo.Direct || cInfo.contact.profile.peerType != ChatPeerType.Bot || cInfo.featureEnabled(ChatFeature.Files) + if (cInfo.useCommands && (!showAttachment || msg.isEmpty() || msg.startsWith("/"))) { + CommandsButton() + } + if (showAttachment) { + AttachmentButton() + } + } + } + + val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) } + LaunchedEffect(allowedVoiceByPrefs) { + if (!allowedVoiceByPrefs && composeState.value.preview is ComposePreview.VoicePreview) { + // Voice was disabled right when this user records it, just cancel it + cancelVoice() + } + } + val needToAllowVoiceToContact = remember(chat.chatInfo) { + chat.chatInfo is ChatInfo.Direct && with(chat.chatInfo.contact.mergedPreferences.voice) { + ((userPreference as? ContactUserPref.User)?.preference?.allow == FeatureAllowed.NO || (userPreference as? ContactUserPref.Contact)?.preference?.allow == FeatureAllowed.NO) && + contactPreference.allow == FeatureAllowed.YES + } + } + LaunchedEffect(Unit) { + snapshotFlow { recState.value } + .distinctUntilChanged() + .collect { + when (it) { + is RecordingState.Started -> onAudioAdded(it.filePath, it.progressMs, false) + is RecordingState.Finished -> if (it.durationMs > 300) { + onAudioAdded(it.filePath, it.durationMs, true) + } else { + cancelVoice() + } + is RecordingState.NotStarted -> {} + } + } + } + + LaunchedEffect(rememberUpdatedState(chat.chatInfo.sendMsgEnabled).value) { + if (!chat.chatInfo.sendMsgEnabled) { + clearCurrentDraft() + clearState() + } + } + + KeyChangeEffect(chatModel.chatId.value) { prevChatId -> + val cs = composeState.value + if (cs.liveMessage != null && (cs.message.text.isNotEmpty() || cs.liveMessage.sent)) { + sendMessage(null) + resetLinkPreview() + clearPrevDraft(prevChatId) + deleteUnusedFiles() + } else if (cs.inProgress) { + clearPrevDraft(prevChatId) + composeState.value = cs.copy(inProgress = false, progressByTimeout = false) + } else if (!cs.empty) { + if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) { + composeState.value = cs.copy(preview = cs.preview.copy(finished = true)) + } + if (saveLastDraft) { + chatModel.draft.value = composeState.value + chatModel.draftChatId.value = prevChatId + } + composeState.value = ComposeState(useLinkPreviews = useLinkPreviews) + } else if (chatModel.draftChatId.value == chatModel.chatId.value && chatModel.draft.value != null) { + composeState.value = chatModel.draft.value ?: ComposeState(useLinkPreviews = useLinkPreviews) + } else { + clearPrevDraft(prevChatId) + deleteUnusedFiles() + } + chatModel.removeLiveDummy() + CIFile.cachedRemoteFileRequests.clear() + } + if (appPlatform.isDesktop) { + // Don't enable this on Android, it breaks it, This method only works on desktop. For Android there is a `KeyChangeEffect(chatModel.chatId.value)` + DisposableEffect(Unit) { + onDispose { + if (chatModel.sharedContent.value is SharedContent.Forward && saveLastDraft && !composeState.value.empty) { + chatModel.draft.value = composeState.value + chatModel.draftChatId.value = chat.id + } + } + } + } + + @Composable + fun SendMsgView_( + disableSendButton: Boolean, + placeholder: String? = null, + sendToConnect: (() -> Unit)? = null + ) { + val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } + val sendButtonColor = + if (chat.chatInfo.incognito) + if (isInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F) + else MaterialTheme.colors.primary + SendMsgView( + composeState, + showVoiceRecordIcon = true, + recState, + chat.chatInfo is ChatInfo.Direct, + liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown, + sendMsgEnabled = sendMsgEnabled.value, + userCantSendReason = userCantSendReason.value, + sendButtonEnabled = sendMsgEnabled.value && !disableSendButton, + sendToConnect = sendToConnect, + hideSendButton = chat.chatInfo.nextConnect && !nextSendGrpInv.value && composeState.value.whitespaceOnly, + nextConnect = chat.chatInfo.nextConnect, + needToAllowVoiceToContact = needToAllowVoiceToContact, + allowedVoiceByPrefs = allowedVoiceByPrefs, + allowVoiceToContact = ::allowVoiceToContact, + sendButtonColor = sendButtonColor, + timedMessageAllowed = timedMessageAllowed, + customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime, + placeholder = placeholder ?: composeState.value.placeholder, + sendMessage = { ttl -> + sendMessage(ttl) + resetLinkPreview() + }, + sendLiveMessage = if (chat.chatInfo.chatType != ChatType.Local) ::sendLiveMessage else null, + updateLiveMessage = ::updateLiveMessage, + cancelLiveMessage = { + composeState.value = composeState.value.copy(liveMessage = null) + chatModel.removeLiveDummy() + }, + editPrevMessage = ::editPrevMessage, + onFilesPasted = { composeState.onFilesAttached(it) }, + onMessageChange = ::onMessageChange, + textStyle = textStyle, + focusRequester = focusRequester, + ) + } + + @Composable + fun SendContactRequestView( + disableSendButton: Boolean, + icon: ImageResource, + sendRequest: () -> Unit + ) { + Row( + Modifier.padding(horizontal = DEFAULT_PADDING_HALF), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + Modifier.weight(1f) + ) { + SendMsgView_( + disableSendButton = disableSendButton, + placeholder = generalGetString(MR.strings.compose_view_add_message), + sendToConnect = sendRequest + ) + } + if (composeState.value.whitespaceOnly) { + SimpleButtonIconEnded( + text = stringResource(MR.strings.compose_view_connect), + icon = painterResource(icon), + style = MaterialTheme.typography.body2, + color = if (composeState.value.inProgress) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + disabled = composeState.value.inProgress, + click = { withApi { sendRequest() } } + ) + } + } + } + + @Composable + fun ConnectButtonView( + text: String, + icon: ImageResource, + connect: () -> Unit + ) { + var modifier = Modifier.height(60.dp).fillMaxWidth() + modifier = if (composeState.value.inProgress) modifier else modifier.clickable(onClick = { connect() }) + Box( + modifier, + contentAlignment = Alignment.Center + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(icon), + contentDescription = null, + tint = if (composeState.value.inProgress) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + Text( + text, + style = MaterialTheme.typography.body2, + color = if (composeState.value.inProgress) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + } + if (composeState.value.progressByTimeout) { + Box( + Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING_HALF), + contentAlignment = Alignment.CenterEnd + ) { + ComposeProgressIndicator() + } + } + } + } + + @Composable + fun ContextSendMessageToConnect(s: String) { + Row( + Modifier + .height(60.dp) + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(MR.images.ic_chat), + contentDescription = null, + modifier = Modifier + .padding(end = 8.dp) + .height(20.dp) + .width(20.dp), + tint = MaterialTheme.colors.secondary + ) + Text(s) + } + } + // In case a user sent something, state is in progress, the user rotates a screen to different orientation. // Without clearing the state the user will be unable to send anything until re-enters ChatView LaunchedEffect(Unit) { @@ -999,21 +1430,51 @@ fun ComposeView( chatModel.sharedContent.value = null } - val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled) - val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason) - val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv) + LaunchedEffect(composeState.value.inProgress) { + val newProgressByTimeout = if (composeState.value.inProgress) { + delay(1000) + composeState.value.inProgress + } else { + false + } + composeState.value = composeState.value.copy(progressByTimeout = newProgressByTimeout) + } Column { - if (nextSendGrpInv.value) { - ComposeContextInvitingContactMemberView() + val currentUser = chatModel.currentUser.value + if (chat.chatInfo.nextConnectPrepared && currentUser != null) { + ComposeContextProfilePickerView( + rhId = rhId, + chat = chat, + currentUser = currentUser + ) } + + if ( + chat.chatInfo is ChatInfo.Group + && chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext + && chatsCtx.secondaryContextFilter.groupScopeInfo is GroupChatScopeInfo.MemberSupport + && chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_ != null + && chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_.memberPending + && composeState.value.contextItem == ComposeContextItem.NoContextItem + && composeState.value.preview == ComposePreview.NoPreview + ) { + ComposeContextPendingMemberActionsView( + rhId = rhId, + groupInfo = chat.chatInfo.groupInfo, + member = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_ + ) + } + val ctx = composeState.value.contextItem if (ctx is ComposeContextItem.ReportedItem) { ReportReasonView(ctx.reason) } - val simplexLinkProhibited = hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks) - val fileProhibited = composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files) + + val simplexLinkProhibited = chatsCtx.secondaryContextFilter == null && hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks) + val fileProhibited = chatsCtx.secondaryContextFilter == null && composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files) val voiceProhibited = composeState.value.preview is ComposePreview.VoicePreview && !chat.chatInfo.featureEnabled(ChatFeature.Voice) + val disableSendButton = simplexLinkProhibited || fileProhibited || voiceProhibited if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) { if (simplexLinkProhibited) { MsgNotAllowedView(generalGetString(MR.strings.simplex_links_not_allowed), icon = painterResource(MR.images.ic_link)) @@ -1038,151 +1499,86 @@ fun ComposeView( } } } + Surface(color = MaterialTheme.colors.background, contentColor = MaterialTheme.colors.onBackground) { Divider() - Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) { - val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership) - val attachmentClicked = if (isGroupAndProhibitedFiles) { - { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.files_and_media_prohibited), - text = generalGetString(MR.strings.only_owners_can_enable_files_and_media) - ) - } + if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared) { + if (chat.chatInfo.groupInfo.businessChat == null) { + ConnectButtonView( + text = stringResource(MR.strings.compose_view_join_group), + icon = MR.images.ic_group_filled, + connect = { withApi { connectPreparedGroup() } } + ) } else { - showChooseAttachment - } - val attachmentEnabled = - !composeState.value.attachmentDisabled - && sendMsgEnabled.value - && !isGroupAndProhibitedFiles - && !nextSendGrpInv.value - IconButton( - attachmentClicked, - Modifier.padding(start = 3.dp, end = 1.dp, bottom = if (appPlatform.isAndroid) 2.sp.toDp() else 5.sp.toDp() * fontSizeSqrtMultiplier), - enabled = attachmentEnabled - ) { - Icon( - painterResource(MR.images.ic_attach_file_filled_500), - contentDescription = stringResource(MR.strings.attach), - tint = if (attachmentEnabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier - .size(28.dp) - .clip(CircleShape) + SendContactRequestView( + disableSendButton = disableSendButton, + icon = MR.images.ic_work_filled, + sendRequest = { withApi { connectPreparedGroup() } } ) } - val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) } - LaunchedEffect(allowedVoiceByPrefs) { - if (!allowedVoiceByPrefs && composeState.value.preview is ComposePreview.VoicePreview) { - // Voice was disabled right when this user records it, just cancel it - cancelVoice() + } else if (nextSendGrpInv.value) { + Column { + ContextSendMessageToConnect(generalGetString(MR.strings.compose_send_direct_message_to_connect)) + Divider() + Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) { + AttachmentAndCommandsButtons() + SendMsgView_( + disableSendButton = disableSendButton, + sendToConnect = { withApi { sendMemberContactInvitation() } } + ) } } - val needToAllowVoiceToContact = remember(chat.chatInfo) { - chat.chatInfo is ChatInfo.Direct && with(chat.chatInfo.contact.mergedPreferences.voice) { - ((userPreference as? ContactUserPref.User)?.preference?.allow == FeatureAllowed.NO || (userPreference as? ContactUserPref.Contact)?.preference?.allow == FeatureAllowed.NO) && - contactPreference.allow == FeatureAllowed.YES - } - } - LaunchedEffect(Unit) { - snapshotFlow { recState.value } - .distinctUntilChanged() - .collect { - when (it) { - is RecordingState.Started -> onAudioAdded(it.filePath, it.progressMs, false) - is RecordingState.Finished -> if (it.durationMs > 300) { - onAudioAdded(it.filePath, it.durationMs, true) - } else { - cancelVoice() - } - is RecordingState.NotStarted -> {} - } + } else if ( + chat.chatInfo is ChatInfo.Direct + && chat.chatInfo.contact.nextConnectPrepared + && chat.chatInfo.contact.preparedContact != null + ) { + when (chat.chatInfo.contact.preparedContact.uiConnLinkType) { + ConnectionMode.Inv -> + ConnectButtonView( + text = stringResource(MR.strings.compose_view_connect), + icon = MR.images.ic_person_add_filled, + connect = { withApi { sendConnectPreparedContact() } } + ) + ConnectionMode.Con -> + if (chat.chatInfo.contact.isBot) { + ConnectButtonView( + text = stringResource(MR.strings.compose_view_connect), + icon = MR.images.ic_bolt_filled, + connect = { withApi { sendConnectPreparedContact() } } + ) + } else { + SendContactRequestView( + disableSendButton = disableSendButton, + icon = MR.images.ic_person_add_filled, + sendRequest = { showSendConnectPreparedContactAlert() } + ) } } - - LaunchedEffect(rememberUpdatedState(chat.chatInfo.sendMsgEnabled).value) { - if (!chat.chatInfo.sendMsgEnabled) { - clearCurrentDraft() - clearState() - } - } - - KeyChangeEffect(chatModel.chatId.value) { prevChatId -> - val cs = composeState.value - if (cs.liveMessage != null && (cs.message.text.isNotEmpty() || cs.liveMessage.sent)) { - sendMessage(null) - resetLinkPreview() - clearPrevDraft(prevChatId) - deleteUnusedFiles() - } else if (cs.inProgress) { - clearPrevDraft(prevChatId) - } else if (!cs.empty) { - if (cs.preview is ComposePreview.VoicePreview && !cs.preview.finished) { - composeState.value = cs.copy(preview = cs.preview.copy(finished = true)) - } - if (saveLastDraft) { - chatModel.draft.value = composeState.value - chatModel.draftChatId.value = prevChatId - } - composeState.value = ComposeState(useLinkPreviews = useLinkPreviews) - } else if (chatModel.draftChatId.value == chatModel.chatId.value && chatModel.draft.value != null) { - composeState.value = chatModel.draft.value ?: ComposeState(useLinkPreviews = useLinkPreviews) - } else { - clearPrevDraft(prevChatId) - deleteUnusedFiles() - } - chatModel.removeLiveDummy() - CIFile.cachedRemoteFileRequests.clear() - } - if (appPlatform.isDesktop) { - // Don't enable this on Android, it breaks it, This method only works on desktop. For Android there is a `KeyChangeEffect(chatModel.chatId.value)` - DisposableEffect(Unit) { - onDispose { - if (chatModel.sharedContent.value is SharedContent.Forward && saveLastDraft && !composeState.value.empty) { - chatModel.draft.value = composeState.value - chatModel.draftChatId.value = chat.id - } - } - } - } - val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } - val sendButtonColor = - if (chat.chatInfo.incognito) - if (isInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F) - else MaterialTheme.colors.primary - SendMsgView( - composeState, - showVoiceRecordIcon = true, - recState, - chat.chatInfo is ChatInfo.Direct, - liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown, - sendMsgEnabled = sendMsgEnabled.value, - userCantSendReason = userCantSendReason.value, - sendButtonEnabled = sendMsgEnabled.value && !(simplexLinkProhibited || fileProhibited || voiceProhibited), - nextSendGrpInv = nextSendGrpInv.value, - needToAllowVoiceToContact, - allowedVoiceByPrefs, - allowVoiceToContact = ::allowVoiceToContact, - sendButtonColor = sendButtonColor, - timedMessageAllowed = timedMessageAllowed, - customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime, - placeholder = composeState.value.placeholder, - sendMessage = { ttl -> - sendMessage(ttl) - resetLinkPreview() - }, - sendLiveMessage = if (chat.chatInfo.chatType != ChatType.Local) ::sendLiveMessage else null, - updateLiveMessage = ::updateLiveMessage, - cancelLiveMessage = { - composeState.value = composeState.value.copy(liveMessage = null) - chatModel.removeLiveDummy() - }, - editPrevMessage = ::editPrevMessage, - onFilesPasted = { composeState.onFilesAttached(it) }, - onMessageChange = ::onMessageChange, - textStyle = textStyle, - focusRequester = focusRequester, + } else if ( + chat.chatInfo is ChatInfo.Direct + && chat.chatInfo.contact.nextAcceptContactRequest + && chat.chatInfo.contact.contactRequestId != null + ) { + ComposeContextContactRequestActionsView( + rhId = rhId, + contactRequestId = chat.chatInfo.contact.contactRequestId ) + } else if ( + chat.chatInfo is ChatInfo.Direct + && chat.chatInfo.contact.nextAcceptContactRequest + && chat.chatInfo.contact.groupDirectInv != null + ) { + ComposeContextMemberContactActionsView( + rhId = rhId, + contact = chat.chatInfo.contact, + groupDirectInv = chat.chatInfo.contact.groupDirectInv + ) + } else { + Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) { + AttachmentAndCommandsButtons() + SendMsgView_(disableSendButton = disableSendButton) + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index b9538bc691..03d0b99854 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -77,7 +77,7 @@ fun SelectedItemsButtonsToolbar( val forwardCountProhibited = remember { mutableStateOf(false) } Box { // It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty - ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() }) + ComposeView(rhId = null, chatModel = chatModel, chatModel.chatsContext, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(false) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() }) Row( Modifier .matchParentSize() @@ -101,21 +101,21 @@ fun SelectedItemsButtonsToolbar( ) } - IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value) { + IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value && chatsCtx.secondaryContextFilter == null) { Icon( painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), - tint = if (!moderateEnabled.value || deleteCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error + tint = if (!moderateEnabled.value || deleteCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.error ) } - IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value) { + IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value && chatsCtx.secondaryContextFilter == null) { Icon( painterResource(MR.images.ic_forward), null, Modifier.size(22.dp), - tint = if (!forwardEnabled.value || forwardCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + tint = if (!forwardEnabled.value || forwardCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index 5710f09ed5..467f1e52af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -41,7 +41,9 @@ fun SendMsgView( sendMsgEnabled: Boolean, userCantSendReason: Pair?, sendButtonEnabled: Boolean, - nextSendGrpInv: Boolean, + sendToConnect: (() -> Unit)? = null, + hideSendButton: Boolean = false, + nextConnect: Boolean, needToAllowVoiceToContact: Boolean, allowedVoiceByPrefs: Boolean, sendButtonColor: Color = MaterialTheme.colors.primary, @@ -63,16 +65,7 @@ fun SendMsgView( val padding = if (appPlatform.isAndroid) PaddingValues(vertical = 8.dp) else PaddingValues(top = 3.dp, bottom = 4.dp) Box(Modifier.padding(padding)) { val cs = composeState.value - var progressByTimeout by rememberSaveable { mutableStateOf(false) } - LaunchedEffect(composeState.value.inProgress) { - progressByTimeout = if (composeState.value.inProgress) { - delay(500) - composeState.value.inProgress - } else { - false - } - } - val showVoiceButton = !nextSendGrpInv && cs.message.text.isEmpty() && showVoiceRecordIcon && !composeState.value.editing && + val showVoiceButton = !nextConnect && cs.message.text.isEmpty() && showVoiceRecordIcon && !composeState.value.editing && !composeState.value.forwarding && cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started) && (cs.contextItem !is ComposeContextItem.ReportedItem) val showDeleteTextButton = rememberSaveable { mutableStateOf(false) } val sendMsgButtonDisabled = !sendMsgEnabled || !cs.sendEnabled() || @@ -95,7 +88,11 @@ fun SendMsgView( focusRequester ) { if (!cs.inProgress) { - sendMessage(null) + if (sendToConnect != null) { + sendToConnect() + } else { + sendMessage(null) + } } } if (clicksOnTextFieldDisabled) { @@ -131,9 +128,9 @@ fun SendMsgView( } } when { - progressByTimeout -> ProgressIndicator() + cs.progressByTimeout -> ComposeProgressIndicator() cs.contextItem is ComposeContextItem.ReportedItem -> { - SendMsgButton(painterResource(MR.images.ic_check_filled), sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendMessage) + SendMsgButton(painterResource(MR.images.ic_check_filled), sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendToConnect, sendMessage) } showVoiceButton && sendMsgEnabled -> { Row(verticalAlignment = Alignment.CenterVertically) { @@ -181,7 +178,7 @@ fun SendMsgView( fun MenuItems(): List<@Composable () -> Unit> { val menuItems = mutableListOf<@Composable () -> Unit>() - if (cs.liveMessage == null && !cs.editing && !nextSendGrpInv || sendMsgEnabled) { + if (cs.liveMessage == null && !cs.editing && !nextConnect || sendMsgEnabled) { if ( cs.preview !is ComposePreview.VoicePreview && cs.contextItem is ComposeContextItem.NoContextItem && @@ -215,19 +212,21 @@ fun SendMsgView( return menuItems } - val menuItems = MenuItems() - if (menuItems.isNotEmpty()) { - SendMsgButton(icon, sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendMessage) { showDropdown.value = true } - DefaultDropdownMenu(showDropdown) { - menuItems.forEach { composable -> composable() } + if (!hideSendButton) { + val menuItems = MenuItems() + if (menuItems.isNotEmpty()) { + SendMsgButton(icon, sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendToConnect, sendMessage) { showDropdown.value = true } + DefaultDropdownMenu(showDropdown) { + menuItems.forEach { composable -> composable() } + } + CustomDisappearingMessageDialog( + showCustomDisappearingMessageDialog, + sendMessage = sendMessage, + customDisappearingMessageTimePref = customDisappearingMessageTimePref + ) + } else { + SendMsgButton(icon, sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendToConnect, sendMessage) } - CustomDisappearingMessageDialog( - showCustomDisappearingMessageDialog, - sendMessage = sendMessage, - customDisappearingMessageTimePref = customDisappearingMessageTimePref - ) - } else { - SendMsgButton(icon, sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendMessage) } } } @@ -403,8 +402,8 @@ private fun RecordVoiceButton(interactionSource: MutableInteractionSource) { } @Composable -private fun ProgressIndicator() { - CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp) +fun ComposeProgressIndicator() { + CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp) } @Composable @@ -430,6 +429,7 @@ private fun SendMsgButton( alpha: Animatable, sendButtonColor: Color, enabled: Boolean, + sendToConnect: (() -> Unit)?, sendMessage: (Int?) -> Unit, onLongClick: (() -> Unit)? = null ) { @@ -438,7 +438,13 @@ private fun SendMsgButton( Box( modifier = Modifier.requiredSize(36.dp) .combinedClickable( - onClick = { sendMessage(null) }, + onClick = { + if (sendToConnect != null) { + sendToConnect() + } else { + sendMessage(null) + } + }, onLongClick = onLongClick, enabled = enabled, role = Role.Button, @@ -581,7 +587,7 @@ fun PreviewSendMsgView() { sendMsgEnabled = true, userCantSendReason = null, sendButtonEnabled = true, - nextSendGrpInv = false, + nextConnect = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, allowVoiceToContact = {}, @@ -616,7 +622,7 @@ fun PreviewSendMsgViewEditing() { sendMsgEnabled = true, userCantSendReason = null, sendButtonEnabled = true, - nextSendGrpInv = false, + nextConnect = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, allowVoiceToContact = {}, @@ -651,7 +657,7 @@ fun PreviewSendMsgViewInProgress() { sendMsgEnabled = true, userCantSendReason = null, sendButtonEnabled = true, - nextSendGrpInv = false, + nextConnect = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, allowVoiceToContact = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index e670fae5ef..91f7af2b95 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -68,7 +68,7 @@ private fun VerifyCodeLayout( } } - QRCode(connectionCode, padding = PaddingValues(vertical = DEFAULT_PADDING_HALF)) + QRCode(connectionCode, small = true, padding = PaddingValues(vertical = DEFAULT_PADDING_HALF)) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Spacer(Modifier.weight(2f)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 10694d13bf..9298b600e9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -55,6 +55,16 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea GroupPreferencesView(chatModel, rhId, groupInfo.id, close) } }, + openMemberAdmission = { + ModalManager.end.showCustomModal { close -> + MemberAdmissionView( + chat.simplex.common.platform.chatModel, + rhId, + groupInfo.id, + close + ) + } + }, inviteMembers = { allowModifyMembers = false withLongRunningApi(slow = 120_000) { @@ -93,8 +103,9 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List { .asSequence() .map { it.chatInfo } .filterIsInstance() + .filter { it.sendMsgEnabled } .map { it.contact } - .filter { c -> c.sendMsgEnabled && !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s) + .filter { c -> !c.sendMsgToConnect && c.contactId !in memberContactIds && c.anyNameContains(s) } .sortedBy { it.displayName.lowercase() } .toList() @@ -110,6 +121,7 @@ fun AddGroupMembersLayout( allowModifyMembers: Boolean, searchText: MutableState, openPreferences: () -> Unit, + openMemberAdmission: () -> Unit, inviteMembers: () -> Unit, clearSelection: () -> Unit, addContact: (Long) -> Unit, @@ -144,7 +156,7 @@ fun AddGroupMembersLayout( horizontalArrangement = Arrangement.Center ) { ChatInfoToolbarTitle( - ChatInfo.Group(groupInfo), + ChatInfo.Group(groupInfo, groupChatScope = null), imageSize = 60.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight ) @@ -165,6 +177,9 @@ fun AddGroupMembersLayout( } else { SectionView { if (creatingGroup) { + SectionItemView(openMemberAdmission) { + Text(stringResource(MR.strings.set_member_admission)) + } SectionItemView(openPreferences) { Text(stringResource(MR.strings.set_group_preferences)) } @@ -376,6 +391,7 @@ fun PreviewAddGroupMembersLayout() { allowModifyMembers = true, searchText = remember { mutableStateOf(TextFieldValue("")) }, openPreferences = {}, + openMemberAdmission = {}, inviteMembers = {}, clearSelection = {}, addContact = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 22956738e7..1ea3daeab1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -54,12 +54,11 @@ fun ModalData.GroupChatInfoView( chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatId: String, - groupLink: CreatedConnLink?, - groupLinkMemberRole: GroupMemberRole?, + groupLink: GroupLink?, selectedItems: MutableState?>, appBar: MutableState<@Composable (BoxScope.() -> Unit)?>, scrollToItemId: MutableState, - onGroupLinkUpdated: (Pair?) -> Unit, + onGroupLinkUpdated: (GroupLink?) -> Unit, close: () -> Unit, onSearchClicked: () -> Unit ) { @@ -74,6 +73,9 @@ fun ModalData.GroupChatInfoView( val chatItemTTL = remember(groupInfo.id) { mutableStateOf(if (groupInfo.chatItemTTL != null) ChatItemTTL.fromSeconds(groupInfo.chatItemTTL) else null) } val deletingItems = rememberSaveable(groupInfo.id) { mutableStateOf(false) } val scope = rememberCoroutineScope() + val activeSortedMembers = remember { chatModel.groupMembers }.value + .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved } + .sortedByDescending { it.memberRole } GroupChatInfoLayout( chat, @@ -95,9 +97,7 @@ fun ModalData.GroupChatInfoView( setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems) }, - activeSortedMembers = remember { chatModel.groupMembers }.value - .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved } - .sortedByDescending { it.memberRole }, + activeSortedMembers = activeSortedMembers, developerTools, onLocalAliasChanged = { setGroupAlias(chat, it, chatModel) }, groupLink, @@ -126,7 +126,7 @@ fun ModalData.GroupChatInfoView( } ModalManager.end.showModalCloseable(true) { closeCurrent -> remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> - GroupMemberInfoView(rhId, groupInfo, mem, stats, code, chatModel, closeCurrent) { + GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, openedFromSupportChat = false, closeCurrent) { closeCurrent() close() } @@ -140,6 +140,17 @@ fun ModalData.GroupChatInfoView( addOrEditWelcomeMessage = { ModalManager.end.showCustomModal { close -> GroupWelcomeView(chatModel, rhId, groupInfo, close) } }, + openMemberSupport = { + ModalManager.end.showCustomModal { close -> + MemberSupportView( + rhId, + chat, + groupInfo, + scrollToItemId, + close + ) + } + }, openPreferences = { ModalManager.end.showCustomModal { close -> GroupPreferencesView( @@ -154,7 +165,7 @@ fun ModalData.GroupChatInfoView( clearChat = { clearChatDialog(chat, close) }, leaveGroup = { leaveGroupDialog(rhId, groupInfo, chatModel, close) }, manageGroupLink = { - ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) } + ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, onGroupLinkUpdated) } }, onSearchClicked = onSearchClicked, deletingItems = deletingItems @@ -317,6 +328,40 @@ fun AddGroupMembersButton( ) } +@Composable +fun UserSupportChatButton( + chat: Chat, + groupInfo: GroupInfo, + scrollToItemId: MutableState +) { + val scope = rememberCoroutineScope() + + SettingsActionItemWithContent( + painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag), + stringResource(MR.strings.button_support_chat), + click = { + val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null) + val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo) + scope.launch { + showMemberSupportChatView( + chatModel.chatId, + scrollToItemId = scrollToItemId, + supportChatInfo, + scopeInfo + ) + } + }, + iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary), + ) { + if (chat.supportUnreadCount > 0) { + UnreadBadge( + text = unreadCountStr(chat.supportUnreadCount), + backgroundColor = MaterialTheme.colors.primary + ) + } + } +} + @Composable fun ModalData.GroupChatInfoLayout( chat: Chat, @@ -329,7 +374,7 @@ fun ModalData.GroupChatInfoLayout( activeSortedMembers: List, developerTools: Boolean, onLocalAliasChanged: (String) -> Unit, - groupLink: CreatedConnLink?, + groupLink: GroupLink?, selectedItems: MutableState?>, appBar: MutableState<@Composable (BoxScope.() -> Unit)?>, scrollToItemId: MutableState, @@ -337,6 +382,7 @@ fun ModalData.GroupChatInfoLayout( showMemberInfo: (GroupMember) -> Unit, editGroupProfile: () -> Unit, addOrEditWelcomeMessage: () -> Unit, + openMemberSupport: () -> Unit, openPreferences: () -> Unit, deleteGroup: () -> Unit, clearChat: () -> Unit, @@ -422,6 +468,40 @@ fun ModalData.GroupChatInfoLayout( SectionSpacer() + var anyTopSectionRowShow = false + SectionView { + if (groupInfo.canAddMembers && groupInfo.businessChat == null) { + anyTopSectionRowShow = true + if (groupLink == null) { + CreateGroupLinkButton(manageGroupLink) + } else { + GroupLinkButton(manageGroupLink) + } + } + if (groupInfo.businessChat == null && groupInfo.membership.memberRole >= GroupMemberRole.Moderator) { + anyTopSectionRowShow = true + MemberSupportButton(chat, openMemberSupport) + } + if (groupInfo.canModerate) { + anyTopSectionRowShow = true + GroupReportsButton(chat) { + scope.launch { + showGroupReportsView(chatModel.chatId, scrollToItemId, chat.chatInfo) + } + } + } + if ( + groupInfo.membership.memberActive && + (groupInfo.membership.memberRole < GroupMemberRole.Moderator || groupInfo.membership.supportChat != null) + ) { + anyTopSectionRowShow = true + UserSupportChatButton(chat, groupInfo, scrollToItemId) + } + } + if (anyTopSectionRowShow) { + SectionDividerSpaced(maxBottomPadding = false) + } + SectionView { if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) { EditGroupProfileButton(editGroupProfile) @@ -431,19 +511,17 @@ fun ModalData.GroupChatInfoLayout( } val prefsTitleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences GroupPreferencesButton(prefsTitleId, openPreferences) - if (groupInfo.canModerate) { - GroupReportsButton { - scope.launch { - showGroupReportsView(chatModel.chatId, scrollToItemId, chat.chatInfo) - } - } - } + } + val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs + SectionTextFooter(stringResource(footerId)) + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + + SectionView { if (activeSortedMembers.filter { it.memberCurrent }.size <= SMALL_GROUPS_RCPS_MEM_LIMIT) { SendReceiptsOption(currentUser, sendReceipts, setSendReceipts) } else { SendReceiptsOptionDisabled() } - WallpaperButton { ModalManager.end.showModal { val chat = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chat.id } } } @@ -453,81 +531,77 @@ fun ModalData.GroupChatInfoLayout( } } } + ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems) + SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer)) } - val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs - SectionTextFooter(stringResource(footerId)) - SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) - - ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems) SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = true) - SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) { - if (groupInfo.canAddMembers) { - if (groupInfo.businessChat == null) { - if (groupLink == null) { - CreateGroupLinkButton(manageGroupLink) - } else { - GroupLinkButton(manageGroupLink) + if (!groupInfo.nextConnectPrepared) { + SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) { + if (groupInfo.canAddMembers) { + val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers + val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + val addMembersTitleId = when (groupInfo.businessChat?.chatType) { + BusinessChatType.Customer -> MR.strings.button_add_team_members + BusinessChatType.Business -> MR.strings.button_add_friends + null -> MR.strings.button_add_members + } + AddMembersButton(addMembersTitleId, tint, onAddMembersClick) + } + if (activeSortedMembers.size > 8) { + SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) { + MemberListSearchRowView(searchText) } } - val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers - val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary - val addMembersTitleId = when (groupInfo.businessChat?.chatType) { - BusinessChatType.Customer -> MR.strings.button_add_team_members - BusinessChatType.Business -> MR.strings.button_add_friends - null -> MR.strings.button_add_members + SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + MemberRow(groupInfo.membership, user = true) } - AddMembersButton(addMembersTitleId, tint, onAddMembersClick) - } - if (activeSortedMembers.size > 8) { - SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) { - SearchRowView(searchText) - } - } - SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) { - MemberRow(groupInfo.membership, user = true) } } } - items(filteredMembers.value, key = { it.groupMemberId }) { member -> - Divider() - val showMenu = remember { mutableStateOf(false) } - val canBeSelected = groupInfo.membership.memberRole >= member.memberRole && member.memberRole < GroupMemberRole.Moderator - SectionItemViewLongClickable( - click = { - if (selectedItems.value != null) { - if (canBeSelected) { - toggleItemSelection(member.groupMemberId, selectedItems) + if (!groupInfo.nextConnectPrepared) { + items(filteredMembers.value, key = { it.groupMemberId }) { member -> + Divider() + val showMenu = remember { mutableStateOf(false) } + val canBeSelected = groupInfo.membership.memberRole >= member.memberRole && member.memberRole < GroupMemberRole.Moderator + SectionItemViewLongClickable( + click = { + if (selectedItems.value != null) { + if (canBeSelected) { + toggleItemSelection(member.groupMemberId, selectedItems) + } + } else { + showMemberInfo(member) + } + }, + longClick = { showMenu.value = true }, + minHeight = 54.dp, + padding = PaddingValues(horizontal = DEFAULT_PADDING) + ) { + Box(contentAlignment = Alignment.CenterStart) { + androidx.compose.animation.AnimatedVisibility(selectedItems.value != null, enter = fadeIn(), exit = fadeOut()) { + SelectedListItem(Modifier.alpha(if (canBeSelected) 1f else 0f).padding(start = 2.dp), member.groupMemberId, selectedItems) + } + val selectionOffset by animateDpAsState(if (selectedItems.value != null) 20.dp + 22.dp * fontSizeMultiplier else 0.dp) + DropDownMenuForMember(chat.remoteHostId, member, groupInfo, selectedItems, showMenu) + Box(Modifier.padding(start = selectionOffset)) { + MemberRow(member) } - } else { - showMemberInfo(member) - } - }, - longClick = { showMenu.value = true }, - minHeight = 54.dp, - padding = PaddingValues(horizontal = DEFAULT_PADDING) - ) { - Box(contentAlignment = Alignment.CenterStart) { - androidx.compose.animation.AnimatedVisibility(selectedItems.value != null, enter = fadeIn(), exit = fadeOut()) { - SelectedListItem(Modifier.alpha(if (canBeSelected) 1f else 0f).padding(start = 2.dp), member.groupMemberId, selectedItems) - } - val selectionOffset by animateDpAsState(if (selectedItems.value != null) 20.dp + 22.dp * fontSizeMultiplier else 0.dp) - DropDownMenuForMember(chat.remoteHostId, member, groupInfo, selectedItems, showMenu) - Box(Modifier.padding(start = selectionOffset)) { - MemberRow(member) } } } } item { - SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + if (!groupInfo.nextConnectPrepared) { + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + } SectionView { ClearChatButton(clearChat) if (groupInfo.canDelete) { val titleId = if (groupInfo.businessChat == null) MR.strings.button_delete_group else MR.strings.button_delete_chat DeleteGroupButton(titleId, deleteGroup) } - if (groupInfo.membership.memberCurrent) { + if (groupInfo.membership.memberCurrentOrPending) { val titleId = if (groupInfo.businessChat == null) MR.strings.button_leave_group else MR.strings.button_leave_chat LeaveGroupButton(titleId, leaveGroup) } @@ -631,17 +705,14 @@ private fun SelectedItemsCounterToolbarSetter( } @Composable -fun ChatTTLSection(chatItemTTL: State, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State) { +fun ChatTTLOption(chatItemTTL: State, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State) { Box { - SectionView { - TtlOptions( - chatItemTTL, - enabled = remember { derivedStateOf { !deletingItems.value } }, - onSelected = setChatItemTTL, - default = chatModel.chatItemTTL - ) - SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer)) - } + TtlOptions( + chatItemTTL, + enabled = remember { derivedStateOf { !deletingItems.value } }, + onSelected = setChatItemTTL, + default = chatModel.chatItemTTL + ) if (deletingItems.value) { Box(Modifier.matchParentSize()) { ProgressIndicator() @@ -653,31 +724,42 @@ fun ChatTTLSection(chatItemTTL: State, setChatItemTTL: (ChatItemTT @Composable private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) { Column( - Modifier.padding(horizontal = 8.dp), + Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally ) { ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) val clipboard = LocalClipboardManager.current - val copyNameToClipboard = { - clipboard.setText(AnnotatedString(groupInfo.groupProfile.displayName)) + val copyNameToClipboard = fun(name: String) { + clipboard.setText(AnnotatedString(name)) showToast(generalGetString(MR.strings.copied)) } + val displayName = groupInfo.groupProfile.displayName.trim() + val copyDisplayName = { copyNameToClipboard(displayName) } Text( - groupInfo.groupProfile.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), + displayName, + style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), color = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, - maxLines = 4, + maxLines = 3, overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName) ) - if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != groupInfo.groupProfile.displayName) { - Text( - cInfo.fullName, style = MaterialTheme.typography.h2, - color = MaterialTheme.colors.onBackground, - textAlign = TextAlign.Center, - maxLines = 8, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + ChatInfoDescription(cInfo, displayName, copyNameToClipboard) + } +} + +@Composable +private fun MemberSupportButton(chat: Chat, onClick: () -> Unit) { + SettingsActionItemWithContent( + painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag), + stringResource(MR.strings.member_support), + click = onClick, + iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) + ) { + if (chat.supportUnreadCount > 0) { + UnreadBadge( + text = unreadCountStr(chat.supportUnreadCount), + backgroundColor = MaterialTheme.colors.primary ) } } @@ -693,12 +775,20 @@ private fun GroupPreferencesButton(titleId: StringResource, onClick: () -> Unit) } @Composable -private fun GroupReportsButton(onClick: () -> Unit) { - SettingsActionItem( - painterResource(MR.images.ic_flag), +private fun GroupReportsButton(chat: Chat, onClick: () -> Unit) { + SettingsActionItemWithContent( + painterResource(if (chat.chatStats.reportsCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag), stringResource(MR.strings.group_reports_member_reports), - click = onClick - ) + click = onClick, + iconColor = (if (chat.chatStats.reportsCount > 0) Color.Red else MaterialTheme.colors.secondary) + ) { + if (chat.chatStats.reportsCount > 0) { + UnreadBadge( + text = unreadCountStr(chat.chatStats.reportsCount), + backgroundColor = Color.Red + ) + } + } } @Composable @@ -820,7 +910,7 @@ fun MemberRow(member: GroupMember, user: Boolean = false, infoPage: Boolean = tr } @Composable -private fun MemberVerifiedShield() { +fun MemberVerifiedShield() { Icon(painterResource(MR.images.ic_verified_user), null, Modifier.padding(end = 3.dp).size(16.dp), tint = MaterialTheme.colors.secondary) } @@ -941,7 +1031,7 @@ private fun DeleteGroupButton(titleId: StringResource, onClick: () -> Unit) { } @Composable -private fun SearchRowView( +fun MemberListSearchRowView( searchText: MutableState = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } ) { Box(Modifier.width(36.dp), contentAlignment = Alignment.Center) { @@ -964,16 +1054,18 @@ private fun setGroupAlias(chat: Chat, localAlias: String, chatModel: ChatModel) fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List, onSuccess: () -> Unit = {}) { withBGApi { - val updatedMembers = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds) - if (updatedMembers != null) { + val r = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds) + if (r != null) { + val (updatedGroupInfo, updatedMembers) = r withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, updatedGroupInfo) updatedMembers.forEach { updatedMember -> - chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, updatedMember) + chatModel.chatsContext.upsertGroupMember(rhId, updatedGroupInfo, updatedMember) } } withContext(Dispatchers.Main) { updatedMembers.forEach { updatedMember -> - chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, updatedMember) + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, updatedGroupInfo, updatedMember) } } onSuccess() @@ -1016,7 +1108,18 @@ fun PreviewGroupChatInfoLayout() { selectedItems = remember { mutableStateOf(null) }, appBar = remember { mutableStateOf(null) }, scrollToItemId = remember { mutableStateOf(null) }, - addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {}, onSearchClicked = {}, deletingItems = remember { mutableStateOf(true) } + addMembers = {}, + showMemberInfo = {}, + editGroupProfile = {}, + addOrEditWelcomeMessage = {}, + openMemberSupport = {}, + openPreferences = {}, + deleteGroup = {}, + clearChat = {}, + leaveGroup = {}, + manageGroupLink = {}, + onSearchClicked = {}, + deletingItems = remember { mutableStateOf(true) } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 6e1b9a731d..5a94e7d505 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -1,8 +1,8 @@ package chat.simplex.common.views.chat.group import SectionBottomSpacer +import SectionItemView import SectionViewWithButton -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.style.TextAlign import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp @@ -28,46 +29,109 @@ fun GroupLinkView( chatModel: ChatModel, rhId: Long?, groupInfo: GroupInfo, - connLinkContact: CreatedConnLink?, - memberRole: GroupMemberRole?, - onGroupLinkUpdated: ((Pair?) -> Unit)?, + groupLink: GroupLink?, + onGroupLinkUpdated: ((GroupLink?) -> Unit)?, creatingGroup: Boolean = false, close: (() -> Unit)? = null ) { - var groupLink by rememberSaveable(stateSaver = CreatedConnLink.nullableStateSaver) { mutableStateOf(connLinkContact) } - val groupLinkMemberRole = rememberSaveable { mutableStateOf(memberRole) } + var groupLinkVar by rememberSaveable(stateSaver = GroupLink.nullableStateSaver) { mutableStateOf(groupLink) } + val groupLinkMemberRole = rememberSaveable { mutableStateOf(groupLink?.acceptMemberRole) } var creatingLink by rememberSaveable { mutableStateOf(false) } + val clipboard = LocalClipboardManager.current fun createLink() { creatingLink = true withBGApi { val link = chatModel.controller.apiCreateGroupLink(rhId, groupInfo.groupId) if (link != null) { - groupLink = link.first - groupLinkMemberRole.value = link.second + groupLinkVar = link + groupLinkMemberRole.value = link.acceptMemberRole onGroupLinkUpdated?.invoke(link) } creatingLink = false } } + fun addShortLink(shareOnCompletion: Boolean = false) { + creatingLink = true + withBGApi { + val link = chatModel.controller.apiAddGroupShortLink(rhId, groupInfo.groupId) + if (link != null) { + groupLinkVar = link + groupLinkMemberRole.value = link.acceptMemberRole + onGroupLinkUpdated?.invoke(link) + if (shareOnCompletion) { + clipboard.shareText(link.connLinkContact.simplexChatUri(short = true)) + } + } + creatingLink = false + } + } + fun showAddShortLinkAlert(shareAddress: (() -> Unit)? = null) { + AlertManager.shared.showAlertDialogButtonsColumn( + title = generalGetString(MR.strings.share_group_profile_via_link), + text = generalGetString(MR.strings.share_group_profile_via_link_alert_text), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + addShortLink(shareOnCompletion = shareAddress != null) + }) { + Text( + generalGetString(MR.strings.share_profile_via_link_alert_confirm), + Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary + ) + } + + if (shareAddress != null) { + // Delete without notification + SectionItemView({ + AlertManager.shared.hideAlert() + shareAddress() + }) { + Text( + generalGetString(MR.strings.share_old_link_alert_button), + Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary + ) + } + } + // Cancel + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text( + stringResource(MR.strings.cancel_verb), + Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary + ) + } + } + } + ) + } LaunchedEffect(Unit) { if (groupLink == null && !creatingLink) { createLink() } } GroupLinkLayout( - groupLink = groupLink, + groupLink = groupLinkVar, groupInfo, groupLinkMemberRole, creatingLink, createLink = ::createLink, + showAddShortLinkAlert = ::showAddShortLinkAlert, updateLink = { val role = groupLinkMemberRole.value if (role != null) { withBGApi { val link = chatModel.controller.apiGroupLinkMemberRole(rhId, groupInfo.groupId, role) if (link != null) { - groupLink = link.first - groupLinkMemberRole.value = link.second + groupLinkVar = link + groupLinkMemberRole.value = link.acceptMemberRole onGroupLinkUpdated?.invoke(link) } } @@ -82,7 +146,7 @@ fun GroupLinkView( withBGApi { val r = chatModel.controller.apiDeleteGroupLink(rhId, groupInfo.groupId) if (r) { - groupLink = null + groupLinkVar = null onGroupLinkUpdated?.invoke(null) } } @@ -100,11 +164,12 @@ fun GroupLinkView( @Composable fun GroupLinkLayout( - groupLink: CreatedConnLink?, + groupLink: GroupLink?, groupInfo: GroupInfo, groupLinkMemberRole: MutableState, creatingLink: Boolean, createLink: () -> Unit, + showAddShortLinkAlert: ((() -> Unit)?) -> Unit, updateLink: () -> Unit, deleteLink: () -> Unit, creatingGroup: Boolean = false, @@ -153,11 +218,11 @@ fun GroupLinkLayout( } val showShortLink = remember { mutableStateOf(true) } Spacer(Modifier.height(DEFAULT_PADDING_HALF)) - if (groupLink.connShortLink == null) { - SimpleXCreatedLinkQRCode(groupLink, short = false) + if (groupLink.connLinkContact.connShortLink == null) { + SimpleXCreatedLinkQRCode(groupLink.connLinkContact, short = false) } else { SectionViewWithButton(titleButton = { ToggleShortLinkButton(showShortLink) }) { - SimpleXCreatedLinkQRCode(groupLink, short = showShortLink.value) + SimpleXCreatedLinkQRCode(groupLink.connLinkContact, short = showShortLink.value) } } Row( @@ -169,7 +234,15 @@ fun GroupLinkLayout( SimpleButton( stringResource(MR.strings.share_link), icon = painterResource(MR.images.ic_share), - click = { clipboard.shareText(groupLink.simplexChatUri(short = showShortLink.value)) } + click = { + if (groupLink.shouldBeUpgraded) { + showAddShortLinkAlert { + clipboard.shareText(groupLink.connLinkContact.simplexChatUri(short = showShortLink.value)) + } + } else { + clipboard.shareText(groupLink.connLinkContact.simplexChatUri(short = showShortLink.value)) + } + } ) if (creatingGroup && close != null) { ContinueButton(close) @@ -182,12 +255,28 @@ fun GroupLinkLayout( ) } } + if (groupLink.shouldBeUpgraded) { + AddShortLinkButton(text = stringResource(MR.strings.upgrade_group_link)) { + showAddShortLinkAlert(null) + } + } } } SectionBottomSpacer() } } +@Composable +private fun AddShortLinkButton(text: String, onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_add), + text, + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + @Composable private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState, enabled: Boolean = true) { Row( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 285c96165c..b29374390e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -34,7 +34,7 @@ import chat.simplex.common.views.newchat.* import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* -import chat.simplex.common.views.chatlist.openLoadedChat +import chat.simplex.common.views.chatlist.openDirectChat import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.datetime.Clock @@ -45,17 +45,23 @@ fun GroupMemberInfoView( rhId: Long?, groupInfo: GroupInfo, member: GroupMember, + scrollToItemId: MutableState, connectionStats: ConnectionStats?, connectionCode: String?, chatModel: ChatModel, + openedFromSupportChat: Boolean, close: () -> Unit, closeAll: () -> Unit, // Close all open windows up to ChatView ) { + KeyChangeEffect(chat.simplex.common.platform.chatModel.chatId.value) { + ModalManager.end.closeModals() + } BackHandler(onBack = close) val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatModel.chatId.value && ch.remoteHostId == rhId } val connStats = remember { mutableStateOf(connectionStats) } val developerTools = chatModel.controller.appPrefs.developerTools.get() var progressIndicator by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() fun syncMemberConnection() { withBGApi { @@ -79,17 +85,16 @@ fun GroupMemberInfoView( rhId = rhId, groupInfo, member, + scrollToItemId, connStats, newRole, developerTools, connectionCode, getContactChat = { chatModel.getContactChat(it) }, - openDirectChat = { - withBGApi { - apiLoadMessages(chatModel.chatsContext, rhId, ChatType.Direct, it, ChatPagination.Initial(ChatPagination.INITIAL_COUNT)) - if (chatModel.getContactChat(it) != null) { - closeAll() - } + openDirectChat = { contactId -> + scope.launch { + openDirectChat(rhId, contactId) + closeAll() } }, createMemberContact = { @@ -102,7 +107,7 @@ fun GroupMemberInfoView( withContext(Dispatchers.Main) { chatModel.chatsContext.addChat(memberChat) } - openLoadedChat(memberChat) + openDirectChat(rhId, memberContact.contactId) closeAll() chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected()) } @@ -224,7 +229,8 @@ fun GroupMemberInfoView( ) } } - } + }, + openedFromSupportChat = openedFromSupportChat ) if (progressIndicator) { @@ -243,32 +249,34 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c text = generalGetString(messageId), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { - withBGApi { - val removedMembers = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId)) - if (removedMembers != null) { - withContext(Dispatchers.Main) { - removedMembers.forEach { removedMember -> - chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, removedMember) - } - } - withContext(Dispatchers.Main) { - removedMembers.forEach { removedMember -> - chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, removedMember) - } - } - } - close?.invoke() - } + removeMember(rhId, member, chatModel, close) }, destructive = true, ) } +fun removeMember(rhId: Long?, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { + withBGApi { + val r = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId)) + if (r != null) { + val (updatedGroupInfo, removedMembers) = r + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, updatedGroupInfo) + removedMembers.forEach { removedMember -> + chatModel.chatsContext.upsertGroupMember(rhId, updatedGroupInfo, removedMember) + } + } + } + close?.invoke() + } +} + @Composable fun GroupMemberInfoLayout( rhId: Long?, groupInfo: GroupInfo, member: GroupMember, + scrollToItemId: MutableState, connStats: MutableState, newRole: MutableState, developerTools: Boolean, @@ -288,6 +296,7 @@ fun GroupMemberInfoLayout( syncMemberConnection: () -> Unit, syncMemberConnectionForce: () -> Unit, verifyClicked: () -> Unit, + openedFromSupportChat: Boolean ) { val cStats = connStats.value fun knownDirectChat(contactId: Long): Pair? { @@ -299,6 +308,29 @@ fun GroupMemberInfoLayout( } } + @Composable + fun SupportChatButton() { + val scope = rememberCoroutineScope() + + SettingsActionItem( + painterResource(MR.images.ic_flag), + stringResource(MR.strings.button_support_chat_member), + click = { + val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = member) + val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo) + scope.launch { + showMemberSupportChatView( + chatModel.chatId, + scrollToItemId = scrollToItemId, + supportChatInfo, + scopeInfo + ) + } + }, + iconColor = MaterialTheme.colors.secondary, + ) + } + @Composable fun ModeratorDestructiveSection() { val canBlockForAll = member.canBlockForAll(groupInfo) @@ -413,6 +445,13 @@ fun GroupMemberInfoLayout( if (member.memberActive) { SectionView { + if ( + !openedFromSupportChat && + groupInfo.membership.memberRole >= GroupMemberRole.Moderator && + (member.memberRole < GroupMemberRole.Moderator || member.supportChat != null) + ) { + SupportChatButton() + } if (connectionCode != null) { VerifyCodeButton(member.verified, verifyClicked) } @@ -535,11 +574,12 @@ fun GroupMemberInfoHeader(member: GroupMember) { horizontalAlignment = Alignment.CenterHorizontally ) { MemberProfileImage(size = 192.dp, member, color = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) + val displayName = member.displayName.trim() // alias if set val text = buildAnnotatedString { if (member.verified) { appendInlineContent(id = "shieldIcon") } - append(member.displayName) + append(displayName) } val inlineContent: Map = mapOf( "shieldIcon" to InlineTextContent( @@ -549,10 +589,11 @@ fun GroupMemberInfoHeader(member: GroupMember) { } ) val clipboard = LocalClipboardManager.current - val copyNameToClipboard = { - clipboard.setText(AnnotatedString(member.displayName)) + val copyNameToClipboard = fun(name: String) { + clipboard.setText(AnnotatedString(name)) showToast(generalGetString(MR.strings.copied)) } + val copyDisplayName = { copyNameToClipboard(displayName) } Text( text, inlineContent = inlineContent, @@ -560,18 +601,10 @@ fun GroupMemberInfoHeader(member: GroupMember) { textAlign = TextAlign.Center, maxLines = 3, overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName) ) - if (member.fullName != "" && member.fullName != member.displayName) { - Text( - member.fullName, style = MaterialTheme.typography.h2, - color = MaterialTheme.colors.onBackground, - textAlign = TextAlign.Center, - maxLines = 4, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) - ) - } + // passing actual display name here, as alias is used above + ChatInfoDescription(member, member.memberProfile.displayName.trim(), copyNameToClipboard) } } @@ -755,7 +788,7 @@ fun updateMembersRoleDialog( fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) { try { withBGApi { - planAndConnect(rhId, connReqUri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) + planAndConnect(rhId, connReqUri, close = { ModalManager.closeAllModalsEverywhere() }) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( @@ -878,6 +911,7 @@ fun PreviewGroupMemberInfoLayout() { rhId = null, groupInfo = GroupInfo.sampleData, member = GroupMember.sampleData, + scrollToItemId = remember { mutableStateOf(null) }, connStats = remember { mutableStateOf(null) }, newRole = remember { mutableStateOf(GroupMemberRole.Member) }, developerTools = false, @@ -897,6 +931,7 @@ fun PreviewGroupMemberInfoLayout() { syncMemberConnection = {}, syncMemberConnectionForce = {}, verifyClicked = {}, + openedFromSupportChat = false, ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt index 2c4d4b16a8..81e7d80da3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt @@ -44,7 +44,7 @@ fun SelectedItemsMembersToolbar( ) { // It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty Box(Modifier.alpha(0f)) { - ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() }) + ComposeView(rhId = null, chatModel = chatModel, chatModel.chatsContext, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(false) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() }) } Row( Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt index 91f4f5173c..aa737a02d3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt @@ -35,6 +35,7 @@ private val MAX_PICKER_HEIGHT = (PICKER_ROW_SIZE * 4) + (MEMBER_ROW_AVATAR_SIZE @Composable fun GroupMentions( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, composeState: MutableState, composeViewFocusRequester: FocusRequester?, @@ -48,12 +49,31 @@ fun GroupMentions( val mentionName = remember { mutableStateOf("") } val mentionRange = remember { mutableStateOf(null) } val mentionMemberId = remember { mutableStateOf(null) } + + fun contextMemberFilter(member: GroupMember): Boolean = + when (chatsCtx.secondaryContextFilter) { + null -> true + is SecondaryContextFilter.GroupChatScopeContext -> + when (chatsCtx.secondaryContextFilter.groupScopeInfo) { + is GroupChatScopeInfo.MemberSupport -> { + val scopeMember = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_ + if (scopeMember != null) { + member.memberRole >= GroupMemberRole.Moderator || member.groupMemberId == scopeMember.groupMemberId + } else { + member.memberRole >= GroupMemberRole.Moderator + } + } + } + is SecondaryContextFilter.MsgContentTagContext -> false + } + val filteredMembers = remember { derivedStateOf { val members = chatModel.groupMembers.value .filter { val status = it.memberStatus status != GroupMemberStatus.MemLeft && status != GroupMemberStatus.MemRemoved && status != GroupMemberStatus.MemInvited + && contextMemberFilter(it) } .sortedByDescending { it.memberRole } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 12c5b65769..b8db5969a1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -17,7 +17,9 @@ import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon import chat.simplex.common.model.* import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatModel +import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource import kotlinx.coroutines.* private val featureRoles: List> = listOf( @@ -71,6 +73,16 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> preferences = currentPreferences }, savePrefs = ::savePrefs, + openMemberAdmission = { + ModalManager.end.showCustomModal { close -> + MemberAdmissionView( + chatModel, + rhId, + chatId, + close + ) + } + } ) } } @@ -83,10 +95,15 @@ private fun GroupPreferencesLayout( applyPrefs: (FullGroupPreferences) -> Unit, reset: () -> Unit, savePrefs: () -> Unit, + openMemberAdmission: () -> Unit, ) { ColumnWithScrollBar { val titleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences AppBarTitle(stringResource(titleId)) + if (groupInfo.businessChat == null) { + MemberAdmissionButton(openMemberAdmission) + SectionDividerSpaced(maxBottomPadding = false) + } val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) } val onTTLUpdated = { ttl: Int? -> applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl))) @@ -156,6 +173,15 @@ private fun GroupPreferencesLayout( } } +@Composable +private fun MemberAdmissionButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_toggle_on), + stringResource(MR.strings.member_admission), + click = onClick + ) +} + @Composable private fun FeatureSection( feature: GroupFeature, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index fb24c028b2..f15f70673a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -56,24 +56,27 @@ fun GroupProfileLayout( val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = rememberSaveable { mutableStateOf(groupProfile.displayName) } val fullName = rememberSaveable { mutableStateOf(groupProfile.fullName) } + val shortDescr = rememberSaveable { mutableStateOf(groupProfile.shortDescr ?: "") } val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(groupProfile.image) } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() val focusRequester = remember { FocusRequester() } val dataUnchanged = - displayName.value == groupProfile.displayName && - fullName.value == groupProfile.fullName && + displayName.value.trim() == groupProfile.displayName && + fullName.value.trim() == groupProfile.fullName && + shortDescr.value.trim() == (groupProfile.shortDescr ?: "") && groupProfile.image == profileImage.value val closeWithAlert = { - if (dataUnchanged || !canUpdateProfile(displayName.value, groupProfile)) { + if (dataUnchanged || !canUpdateProfile(displayName.value, shortDescr.value, groupProfile)) { close() } else { showUnsavedChangesAlert({ saveProfile( groupProfile.copy( displayName = displayName.value.trim(), - fullName = fullName.value, + fullName = fullName.value.trim(), + shortDescr = shortDescr.value.trim().ifEmpty { null }, image = profileImage.value ) ) @@ -130,7 +133,7 @@ fun GroupProfileLayout( } } ProfileNameField(displayName, "", { isValidNewProfileName(it, groupProfile) }, focusRequester) - if (groupProfile.fullName.isNotEmpty() && groupProfile.fullName != groupProfile.displayName) { + if (groupProfile.fullName.trim().isNotEmpty() && groupProfile.fullName.trim() != groupProfile.displayName.trim()) { Spacer(Modifier.height(DEFAULT_PADDING)) Text( stringResource(MR.strings.group_full_name_field), @@ -139,8 +142,28 @@ fun GroupProfileLayout( ) ProfileNameField(fullName) } + Spacer(Modifier.height(DEFAULT_PADDING)) - val enabled = !dataUnchanged && canUpdateProfile(displayName.value, groupProfile) + + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(MR.strings.group_short_descr_field), + fontSize = 16.sp, + ) + if (!bioFitsLimit(shortDescr.value)) { + Spacer(Modifier.size(DEFAULT_PADDING_HALF)) + IconButton( + onClick = { AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.group_descr_too_large)) }, + Modifier.size(20.dp) + ) { + Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error) + } + } + } + ProfileNameField(shortDescr, "", isValid = { bioFitsLimit(it) }) + + Spacer(Modifier.height(DEFAULT_PADDING)) + val enabled = !dataUnchanged && canUpdateProfile(displayName.value, shortDescr.value, groupProfile) if (enabled) { Text( stringResource(MR.strings.save_group_profile), @@ -148,7 +171,8 @@ fun GroupProfileLayout( saveProfile( groupProfile.copy( displayName = displayName.value.trim(), - fullName = fullName.value, + fullName = fullName.value.trim(), + shortDescr = shortDescr.value.trim().ifEmpty { null }, image = profileImage.value ) ) @@ -174,8 +198,8 @@ fun GroupProfileLayout( } } -private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean = - displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile) +private fun canUpdateProfile(displayName: String, shortDescr: String, groupProfile: GroupProfile): Boolean = + displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile) && bioFitsLimit(shortDescr) private fun isValidNewProfileName(displayName: String, groupProfile: GroupProfile): Boolean = displayName == groupProfile.displayName || isValidDisplayName(displayName.trim()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt index 1eeeb99c93..2cc2402c0a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt @@ -15,7 +15,15 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.* @Composable -private fun GroupReportsView(reportsChatsCtx: ChatModel.ChatsContext, staleChatId: State, scrollToItemId: MutableState) { +private fun GroupReportsView( + reportsChatsCtx: ChatModel.ChatsContext, + staleChatId: State, + scrollToItemId: MutableState, + close: () -> Unit +) { + KeyChangeEffect(chatModel.chatId.value) { + close() + } ChatView(reportsChatsCtx, staleChatId, scrollToItemId, onComposed = {}) } @@ -53,7 +61,7 @@ fun GroupReportsAppBar( } @Composable -private fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) { +fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) { LaunchedEffect(Unit) { snapshotFlow { chatModel.chatId.value } .distinctUntilChanged() @@ -69,13 +77,13 @@ private fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) { } suspend fun showGroupReportsView(staleChatId: State, scrollToItemId: MutableState, chatInfo: ChatInfo) { - val reportsChatsCtx = ChatModel.ChatsContext(contentTag = MsgContentTag.Report) + val reportsChatsCtx = ChatModel.ChatsContext(secondaryContextFilter = SecondaryContextFilter.MsgContentTagContext(MsgContentTag.Report)) openChat(secondaryChatsCtx = reportsChatsCtx, chatModel.remoteHostId(), chatInfo) ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close -> ModalView({}, showAppBar = false) { val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) { - GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId) + GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId, close) } else { LaunchedEffect(Unit) { close() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt new file mode 100644 index 0000000000..48171bfeb7 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt @@ -0,0 +1,152 @@ +package chat.simplex.common.views.chat.group + +import InfoRow +import SectionBottomSpacer +import SectionDividerSpaced +import SectionItemView +import SectionTextFooter +import SectionView +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import dev.icerock.moko.resources.compose.stringResource +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel +import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Composable +fun MemberAdmissionView(m: ChatModel, rhId: Long?, chatId: String, close: () -> Unit) { + val groupInfo = remember { derivedStateOf { + val ch = m.getChat(chatId) + val g = (ch?.chatInfo as? ChatInfo.Group)?.groupInfo + if (g == null || ch.remoteHostId != rhId) null else g + }} + val gInfo = groupInfo.value ?: return + var admission by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(gInfo.groupProfile.memberAdmission) } + var currentAdmission by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(admission) } + + fun saveAdmission(afterSave: () -> Unit = {}) { + withBGApi { + val gp = gInfo.groupProfile.copy(memberAdmission = admission) + val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp) + if (g != null) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, g) + currentAdmission = admission + } + } + afterSave() + } + } + ModalView( + close = { + if (admission == currentAdmission) close() + else showUnsavedChangesAlert({ saveAdmission(close) }, close) + }, + ) { + MemberAdmissionLayout( + admission, + currentAdmission, + gInfo, + applyAdmission = { admsn -> + admission = admsn + }, + reset = { + admission = currentAdmission + }, + saveAdmission = ::saveAdmission, + ) + } +} + +@Composable +private fun MemberAdmissionLayout( + admission: GroupMemberAdmission?, + currentAdmission: GroupMemberAdmission?, + groupInfo: GroupInfo, + applyAdmission: (GroupMemberAdmission) -> Unit, + reset: () -> Unit, + saveAdmission: () -> Unit, +) { + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.member_admission)) + val review = remember(admission) { mutableStateOf(admission?.review) } + AdmissionSection(MR.strings.admission_stage_review, MR.strings.admission_stage_review_descr, review, groupInfo) { criteria -> + if (admission != null) { + applyAdmission(admission.copy(review = criteria)) + } else { + applyAdmission(GroupMemberAdmission(review = criteria)) + } + } + if (groupInfo.isOwner) { + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + ResetSaveButtons( + reset = reset, + save = saveAdmission, + disabled = admission == currentAdmission + ) + } + SectionBottomSpacer() + } +} + +private val memberCriterias: List> = listOf( + null to generalGetString(MR.strings.member_criteria_off), + MemberCriteria.All to generalGetString(MR.strings.member_criteria_all) +) + +@Composable +private fun AdmissionSection( + admissionStageStrId: StringResource, + admissionStageDescrStrId: StringResource, + memberCriteria: State, + groupInfo: GroupInfo, + onSelected: (MemberCriteria?) -> Unit +) { + SectionView { + if (groupInfo.isOwner) { + ExposedDropDownSettingRow( + generalGetString(admissionStageStrId), + memberCriterias, + memberCriteria, + onSelected = { value -> + onSelected(value) + } + ) + } else { + InfoRow( + stringResource(admissionStageStrId), + memberCriteria.value?.text ?: generalGetString(MR.strings.member_criteria_off) + ) + } + } + SectionTextFooter(stringResource( admissionStageDescrStrId)) +} + +@Composable +private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) { + SectionView { + SectionItemView(reset, disabled = disabled) { + Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } + SectionItemView(save, disabled = disabled) { + Text(stringResource(MR.strings.save_and_notify_group_members), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + } + } +} + +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(MR.strings.save_admission_question), + confirmText = generalGetString(MR.strings.save_and_notify_group_members), + dismissText = generalGetString(MR.strings.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt new file mode 100644 index 0000000000..6680ef99bc --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt @@ -0,0 +1,169 @@ +package chat.simplex.common.views.chat.group + +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.chatlist.* +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +private fun MemberSupportChatView( + chatInfo: ChatInfo, + memberSupportChatsCtx: ChatModel.ChatsContext, + staleChatId: State, + scrollToItemId: MutableState +) { + KeyChangeEffect(chatModel.chatId.value) { + ModalManager.end.closeModals() + } + if (appPlatform.isAndroid) { + DisposableEffect(Unit) { + onDispose { + val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatInfo.id } + if ( + memberSupportChatsCtx.isUserSupportChat + && chat?.chatInfo?.groupInfo_?.membership?.memberPending == true + ) { + withBGApi { + chatModel.chatId.value = null + } + } + } + } + } + ChatView(memberSupportChatsCtx, staleChatId, scrollToItemId, onComposed = {}) +} + +@Composable +fun MemberSupportChatAppBar( + chatsCtx: ChatModel.ChatsContext, + rhId: Long?, + chat: Chat, + scopeMember_: GroupMember?, + scrollToItemId: MutableState, + close: () -> Unit, + onSearchValueChanged: (String) -> Unit +) { + val oneHandUI = remember { ChatController.appPrefs.oneHandUI.state } + val chatBottomBar = remember { ChatController.appPrefs.chatBottomBar.state } + val showSearch = rememberSaveable { mutableStateOf(false) } + val onBackClicked = { + if (!showSearch.value) { + close() + } else { + onSearchValueChanged("") + showSearch.value = false + } + } + BackHandler(onBack = onBackClicked) + if (chat.chatInfo is ChatInfo.Group && scopeMember_ != null) { + val groupInfo = chat.chatInfo.groupInfo + DefaultAppBar( + navigationButton = { NavigationButtonBack(onBackClicked) }, + title = { MemberSupportChatToolbarTitle(scopeMember_) }, + onTitleClick = { + withBGApi { + val r = chatModel.controller.apiGroupMemberInfo(rhId, groupInfo.groupId, scopeMember_.groupMemberId) + val stats = r?.second + val code = if (scopeMember_.memberActive) { + val memCode = chatModel.controller.apiGetGroupMemberCode(rhId, groupInfo.apiId, scopeMember_.groupMemberId) + memCode?.second + } else { + null + } + ModalManager.end.showModalCloseable(true) { closeCurrent -> + remember { derivedStateOf { chatModel.getGroupMember(scopeMember_.groupMemberId) } }.value?.let { mem -> + GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, openedFromSupportChat = true, close = closeCurrent) { + closeCurrent() + close() + } + } + } + } + }, + onTop = !oneHandUI.value || !chatBottomBar.value, + showSearch = showSearch.value, + onSearchValueChanged = onSearchValueChanged, + buttons = { + IconButton({ showSearch.value = true }) { + Icon(painterResource(MR.images.ic_search), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) + } + } + ) + } else { + DefaultAppBar( + navigationButton = { NavigationButtonBack(onBackClicked) }, + fixedTitleText = stringResource(MR.strings.support_chat), + onTitleClick = null, + onTop = !oneHandUI.value || !chatBottomBar.value, + showSearch = showSearch.value, + onSearchValueChanged = onSearchValueChanged, + buttons = { + IconButton({ showSearch.value = true }) { + Icon(painterResource(MR.images.ic_search), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) + } + } + ) + } + ItemsReload(chatsCtx) +} + +@Composable +fun MemberSupportChatToolbarTitle(member: GroupMember, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + MemberProfileImage(size = imageSize * fontSizeSqrtMultiplier, member, iconColor) + Column( + Modifier.padding(start = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + if (member.verified) { + MemberVerifiedShield() + } + Text( + member.displayName, fontWeight = FontWeight.SemiBold, + maxLines = 1, overflow = TextOverflow.Ellipsis + ) + } + if (member.fullName != "" && member.fullName != member.displayName && member.localAlias.isEmpty()) { + Text( + member.fullName, + maxLines = 1, overflow = TextOverflow.Ellipsis + ) + } + } + } +} + +suspend fun showMemberSupportChatView(staleChatId: State, scrollToItemId: MutableState, chatInfo: ChatInfo, scopeInfo: GroupChatScopeInfo) { + val memberSupportChatsCtx = ChatModel.ChatsContext(secondaryContextFilter = SecondaryContextFilter.GroupChatScopeContext(scopeInfo)) + openChat(secondaryChatsCtx = memberSupportChatsCtx, chatModel.remoteHostId(), chatInfo) + ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close -> + ModalView({}, showAppBar = false) { + if (chatInfo is ChatInfo.Group && chatInfo.groupChatScope != null) { + MemberSupportChatView(chatInfo, memberSupportChatsCtx, staleChatId, scrollToItemId) + } else { + LaunchedEffect(Unit) { + close() + } + } + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt new file mode 100644 index 0000000000..e696128288 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt @@ -0,0 +1,327 @@ +package chat.simplex.common.views.chat.group + +import SectionBottomSpacer +import SectionItemView +import SectionItemViewLongClickable +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.icerock.moko.resources.compose.stringResource +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.chat.item.ItemAction +import chat.simplex.common.views.chatlist.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import kotlinx.coroutines.* + +@Composable +fun ModalData.MemberSupportView( + rhId: Long?, + chat: Chat, + groupInfo: GroupInfo, + scrollToItemId: MutableState, + close: () -> Unit +) { + KeyChangeEffect(chatModel.chatId.value) { + ModalManager.end.closeModals() + } + LaunchedEffect(Unit) { + setGroupMembers(rhId, groupInfo, chatModel) + } + ModalView( + close = close, + endButtons = { RefreshMembersButton(rhId, groupInfo) } + ) { + MemberSupportViewLayout( + chat, + groupInfo, + scrollToItemId + ) + } +} + +@Composable +fun RefreshMembersButton( + rhId: Long?, + groupInfo: GroupInfo +) { + IconButton( + onClick = { + withBGApi { + setGroupMembers(rhId, groupInfo, chatModel) + } + } + ) { + Icon( + painterResource(MR.images.ic_refresh), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } +} + +@Composable +private fun ModalData.MemberSupportViewLayout( + chat: Chat, + groupInfo: GroupInfo, + scrollToItemId: MutableState +) { + val oneHandUI = remember { ChatController.appPrefs.oneHandUI.state } + val scope = rememberCoroutineScope() + + val membersWithChats = remember { chatModel.groupMembers }.value + .filter { it.supportChat != null && it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved } + .sortedWith( + compareByDescending { it.memberPending } + .thenByDescending { (it.supportChat?.mentions ?: 0) > 0 } + .thenByDescending { (it.supportChat?.memberAttention ?: 0) > 0 } + .thenByDescending { (it.supportChat?.unread ?: 0) > 0 } + .thenByDescending { it.supportChat?.chatTs } + ) + + val searchText = remember { stateGetOrPut("searchText") { TextFieldValue() } } + val filteredmembersWithChats = remember(membersWithChats) { + derivedStateOf { + val s = searchText.value.text.trim().lowercase() + if (s.isEmpty()) membersWithChats else membersWithChats.filter { m -> m.anyNameContains(s) } + } + } + + LazyColumnWithScrollBar( + contentPadding = + PaddingValues( + top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp else topPaddingToContent(false) + ) + ) { + item { + AppBarTitle(stringResource(MR.strings.member_support)) + } + + if (membersWithChats.isEmpty()) { + item { + Box(Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING), contentAlignment = Alignment.Center) { + Text(generalGetString(MR.strings.no_support_chats), color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center) + } + } + } else { + item { + SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) { + MemberListSearchRowView(searchText) + } + } + items(filteredmembersWithChats.value, key = { it.groupMemberId }) { member -> + Divider() + val showMenu = remember { mutableStateOf(false) } + SectionItemViewLongClickable( + click = { + val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = member) + val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo) + scope.launch { + showMemberSupportChatView( + chatModel.chatId, + scrollToItemId = scrollToItemId, + supportChatInfo, + scopeInfo + ) + } + }, + longClick = { showMenu.value = true }, + minHeight = 54.dp, + padding = PaddingValues(horizontal = DEFAULT_PADDING) + ) { + Box(contentAlignment = Alignment.CenterStart) { + DropDownMenuForSupportChat(chat.remoteHostId, member, groupInfo, showMenu) + SupportChatRow(member) + } + } + } + item { + Divider() + SectionBottomSpacer() + } + } + } +} + +@Composable +fun SupportChatRow(member: GroupMember) { + fun memberStatus(): String { + return if (member.activeConn?.connDisabled == true) { + generalGetString(MR.strings.member_info_member_disabled) + } else if (member.activeConn?.connInactive == true) { + generalGetString(MR.strings.member_info_member_inactive) + } else if (member.memberPending) { + member.memberStatus.text + } else { + member.memberRole.text + } + } + + @Composable + fun SupportChatUnreadIndicator(supportChat: GroupSupportChat) { + Box(Modifier.widthIn(min = 34.sp.toDp()), contentAlignment = Alignment.TopEnd) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.sp.toDp())) { + if (supportChat.unread > 0 || supportChat.mentions > 0 || supportChat.memberAttention > 0) { + val unreadBadgeColor = when { + supportChat.mentions > 0 || supportChat.memberAttention > 0 -> MaterialTheme.colors.primaryVariant + else -> MaterialTheme.colors.secondary + } + if (supportChat.mentions == 1 && supportChat.unread == 1) { + Box(modifier = Modifier.offset(y = 2.sp.toDp()).size(15.sp.toDp()).background(unreadBadgeColor, shape = CircleShape), contentAlignment = Alignment.Center) { + Icon( + painterResource(MR.images.ic_alternate_email), + contentDescription = generalGetString(MR.strings.notifications), + tint = Color.White, + modifier = Modifier.size(9.sp.toDp()) + ) + } + } else { + if (supportChat.mentions > 0 && supportChat.unread > 1) { + Icon( + painterResource(MR.images.ic_alternate_email), + contentDescription = generalGetString(MR.strings.notifications), + tint = unreadBadgeColor, + modifier = Modifier.size(12.sp.toDp()).offset(y = 2.sp.toDp()) + ) + } + + UnreadBadge( + text = unreadCountStr(supportChat.unread), + backgroundColor = unreadBadgeColor, + yOffset = 2.dp + ) + } + } + } + } + } + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + Modifier.weight(1f).padding(top = MEMBER_ROW_VERTICAL_PADDING, end = DEFAULT_PADDING, bottom = MEMBER_ROW_VERTICAL_PADDING), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + MemberProfileImage(size = MEMBER_ROW_AVATAR_SIZE, member) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + Column { + Row(verticalAlignment = Alignment.CenterVertically) { + if (member.verified) { + MemberVerifiedShield() + } + Text( + member.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis, + color = if (member.memberIncognito) Indigo else Color.Unspecified + ) + } + + Text( + memberStatus(), + color = MaterialTheme.colors.secondary, + fontSize = 12.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + Row { + if (member.memberPending) { + Icon( + painterResource(MR.images.ic_flag_filled), + contentDescription = null, + Modifier.padding(end = 3.dp).size(16.dp), + tint = MaterialTheme.colors.primaryVariant + ) + } + if (member.supportChat != null) { + SupportChatUnreadIndicator(member.supportChat) + } + } + } +} + +@Composable +private fun DropDownMenuForSupportChat(rhId: Long?, member: GroupMember, groupInfo: GroupInfo, showMenu: MutableState) { + DefaultDropdownMenu(showMenu) { + if (member.memberPending) { + ItemAction(stringResource(MR.strings.accept_pending_member_button), painterResource(MR.images.ic_check), color = MaterialTheme.colors.primary, onClick = { + acceptMemberDialog(rhId, groupInfo, member) + showMenu.value = false + }) + } else { + if (member.supportChatNotRead) { + ItemAction(stringResource(MR.strings.mark_read), painterResource(MR.images.ic_check), color = MaterialTheme.colors.primary, onClick = { + markSupportChatRead(rhId, groupInfo, member) + showMenu.value = false + }) + } + ItemAction(stringResource(MR.strings.delete_member_support_chat_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = { + deleteMemberSupportChatDialog(rhId, groupInfo, member) + showMenu.value = false + }) + } + } +} + +fun deleteMemberSupportChatDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.delete_member_support_chat_alert_title), + confirmText = generalGetString(MR.strings.delete_member_support_chat_button), + onConfirm = { + deleteMemberSupportChat(rhId, groupInfo, member) + }, + destructive = true, + ) +} + +private fun deleteMemberSupportChat(rhId: Long?, groupInfo: GroupInfo, member: GroupMember) { + withBGApi { + val r = chatModel.controller.apiDeleteMemberSupportChat(rhId, groupInfo.groupId, member.groupMemberId) + if (r != null) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second) + chatModel.chatsContext.updateGroup(rhId, r.first) + } + } + } +} + +private fun markSupportChatRead(rhId: Long?, groupInfo: GroupInfo, member: GroupMember) { + withBGApi { + if (member.supportChatNotRead) { + val r = chatModel.controller.apiSupportChatRead( + rh = rhId, + type = ChatType.Group, + id = groupInfo.apiId, + scope = GroupChatScope.MemberSupport(member.groupMemberId) + ) + if (r != null) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second) + chatModel.chatsContext.updateGroup(rhId, r.first) + } + } + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt index 2bcbbe29e0..39bb9545e1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt @@ -194,7 +194,7 @@ fun CIGroupInvitationViewLongNamePreview() { CIGroupInvitationView( ci = ChatItem.getGroupInvitationSample(), groupInvitation = CIGroupInvitation.getSample( - groupProfile = GroupProfile("group_with_a_really_really_really_long_name", "Group With A Really Really Really Long Name"), + groupProfile = GroupProfile("group_with_a_really_really_really_long_name", "Group With A Really Really Really Long Name", shortDescr = null), status = CIGroupInvitationStatus.Accepted ), memberRole = GroupMemberRole.Admin, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 2e789df7bc..6f873035f1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -33,6 +33,7 @@ import chat.simplex.common.views.chatlist.openChat import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.StringResource import kotlinx.datetime.Clock import kotlin.math.* @@ -64,7 +65,7 @@ data class ChatItemReactionMenuItem ( fun ChatItemView( chatsCtx: ChatModel.ChatsContext, rhId: Long?, - cInfo: ChatInfo, + chat: Chat, cItem: ChatItem, composeState: MutableState, imageProvider: (() -> ImageGalleryProvider)? = null, @@ -86,6 +87,7 @@ fun ChatItemView( joinGroup: (Long, () -> Unit) -> Unit, acceptCall: (Contact) -> Unit, scrollToItem: (Long) -> Unit, + scrollToItemId: MutableState, scrollToQuotedItemFromItem: (Long) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit, openDirectChat: (Long) -> Unit, @@ -107,6 +109,7 @@ fun ChatItemView( itemSeparation: ItemSeparation, preview: Boolean = false, ) { + val cInfo = chat.chatInfo val uriHandler = LocalUriHandler.current val sent = cItem.chatDir.sent val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart @@ -271,6 +274,7 @@ fun ChatItemView( } } + // improvement could be to track "forwarded from" scope and open it @Composable fun GoToItemButton(alignStart: Boolean, parentActivated: State) { val chatTypeApiIdMsgId = cItem.meta.itemForwarded?.chatTypeApiIdMsgId @@ -324,7 +328,7 @@ fun ChatItemView( ) { @Composable fun framedItemView() { - FramedItemView(cInfo, cItem, uriHandler, imageProvider, linkMode = linkMode, showViaProxy = showViaProxy, showMenu, showTimestamp = showTimestamp, tailVisible = itemSeparation.largeGap, receiveFile, onLinkLongClick, scrollToItem, scrollToQuotedItemFromItem) + FramedItemView(chatsCtx, chat, cItem, uriHandler, imageProvider, linkMode = linkMode, showViaProxy = showViaProxy, showMenu, showTimestamp = showTimestamp, tailVisible = itemSeparation.largeGap, receiveFile, onLinkLongClick, scrollToItem, scrollToItemId, scrollToQuotedItemFromItem) } fun deleteMessageQuestionText(): String { @@ -635,6 +639,15 @@ fun ChatItemView( CIEventView(eventItemViewText(reversedChatItems)) } + @Composable fun PendingReviewEventItemView() { + Text( + buildAnnotatedString { + withStyle(chatEventStyle.copy(fontWeight = FontWeight.Bold)) { append(cItem.content.text) } + }, + Modifier.padding(horizontal = 6.dp, vertical = 6.dp) + ) + } + @Composable fun DeletedItem() { MarkedDeletedItemView(chatsCtx, cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp) @@ -656,26 +669,30 @@ fun ChatItemView( } @Composable - fun E2EEInfoNoPQText() { + fun e2eeInfoText(sId: StringResource) { Text( buildAnnotatedString { - withStyle(chatEventStyle) { append(annotatedStringResource(MR.strings.e2ee_info_no_pq)) } + withStyle(chatEventStyle) { append(annotatedStringResource(sId)) } }, Modifier.padding(horizontal = 6.dp, vertical = 6.dp) ) } + @Composable + fun E2EEInfoNoPQText() { + e2eeInfoText(MR.strings.e2ee_info_no_pq) + } + @Composable fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) { - if (e2EEInfo.pqEnabled) { - Text( - buildAnnotatedString { - withStyle(chatEventStyle) { append(annotatedStringResource(MR.strings.e2ee_info_pq)) } - }, - Modifier.padding(horizontal = 6.dp, vertical = 6.dp) - ) + if (e2EEInfo.pqEnabled != null) { + if (e2EEInfo.pqEnabled) { + e2eeInfoText(MR.strings.e2ee_info_pq) + } else { + E2EEInfoNoPQText() + } } else { - E2EEInfoNoPQText() + e2eeInfoText(MR.strings.e2ee_info_e2ee) } } @@ -711,12 +728,16 @@ fun ChatItemView( is CIContent.RcvGroupEventContent -> { when (c.rcvGroupEvent) { is RcvGroupEvent.MemberCreatedContact -> CIMemberCreatedContactView(cItem, openDirectChat) + is RcvGroupEvent.NewMemberPendingReview -> PendingReviewEventItemView() else -> EventItemView() } MsgContentItemDropdownMenu() } is CIContent.SndGroupEventContent -> { - EventItemView() + when (c.sndGroupEvent) { + is SndGroupEvent.UserPendingReview -> PendingReviewEventItemView() + else -> EventItemView() + } MsgContentItemDropdownMenu() } is CIContent.RcvConnEventContent -> { @@ -767,6 +788,7 @@ fun ChatItemView( is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText() is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText() + is CIContent.ChatBanner -> Spacer(modifier = Modifier.size(0.dp)) is CIContent.InvalidJSON -> { CIInvalidJSONView(c.json) DeleteItemMenu() @@ -1260,7 +1282,11 @@ sealed class ShapeStyle { data class RoundRect(val radius: Dp) : ShapeStyle() } -fun shapeStyle(chatItem: ChatItem? = null, tailEnabled: Boolean, tailVisible: Boolean, revealed: Boolean): ShapeStyle { +val shapeStyle: (chatItem: ChatItem?, tailEnabled: Boolean, tailVisible: Boolean, revealed: Boolean) -> ShapeStyle = + if (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) > 27) ::shapeStyleWithTail + else { _, _, _, _ -> ShapeStyle.RoundRect(msgRectMaxRadius) } + +fun shapeStyleWithTail(chatItem: ChatItem? = null, tailEnabled: Boolean, tailVisible: Boolean, revealed: Boolean): ShapeStyle { if (chatItem == null) { return ShapeStyle.RoundRect(msgRectMaxRadius) } @@ -1422,9 +1448,9 @@ fun PreviewChatItemView( chatItem: ChatItem = ChatItem.getSampleData(1, CIDirection.DirectSnd(), Clock.System.now(), "hello") ) { ChatItemView( - chatsCtx = ChatModel.ChatsContext(contentTag = null), + chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null), rhId = null, - ChatInfo.Direct.sampleData, + Chat.sampleData, chatItem, useLinkPreviews = true, linkMode = SimplexLinkMode.DESCRIPTION, @@ -1444,6 +1470,7 @@ fun PreviewChatItemView( joinGroup = { _, _ -> }, acceptCall = { _ -> }, scrollToItem = {}, + scrollToItemId = remember { mutableStateOf(null) }, scrollToQuotedItemFromItem = {}, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, @@ -1472,9 +1499,9 @@ fun PreviewChatItemView( fun PreviewChatItemViewDeletedContent() { SimpleXTheme { ChatItemView( - chatsCtx = ChatModel.ChatsContext(contentTag = null), + chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null), rhId = null, - ChatInfo.Direct.sampleData, + Chat.sampleData, ChatItem.getDeletedContentSampleData(), useLinkPreviews = true, linkMode = SimplexLinkMode.DESCRIPTION, @@ -1494,6 +1521,7 @@ fun PreviewChatItemViewDeletedContent() { joinGroup = { _, _ -> }, acceptCall = { _ -> }, scrollToItem = {}, + scrollToItemId = remember { mutableStateOf(null) }, scrollToQuotedItemFromItem = {}, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index fd8a32af64..f36da6c908 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -21,13 +21,17 @@ import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.ComposeState import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlin.math.ceil @Composable fun FramedItemView( - chatInfo: ChatInfo, + chatsCtx: ChatModel.ChatsContext, + chat: Chat, ci: ChatItem, uriHandler: UriHandler? = null, imageProvider: (() -> ImageGalleryProvider)? = null, @@ -39,8 +43,10 @@ fun FramedItemView( receiveFile: (Long) -> Unit, onLinkLongClick: (link: String) -> Unit = {}, scrollToItem: (Long) -> Unit = {}, + scrollToItemId: MutableState, scrollToQuotedItemFromItem: (Long) -> Unit = {}, ) { + val chatInfo = chat.chatInfo val sent = ci.chatDir.sent val chatTTL = chatInfo.timedMessagesTTL @@ -180,7 +186,7 @@ fun FramedItemView( fun ciFileView(ci: ChatItem, text: String) { CIFileView(ci.file, ci.meta.itemEdited, showMenu, false, receiveFile) if (text != "" || ci.meta.isLive) { - CIMarkdownText(ci, chatInfo, chatTTL, linkMode = linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode = linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } @@ -201,7 +207,7 @@ fun FramedItemView( var metaColor = MaterialTheme.colors.secondary Box(contentAlignment = Alignment.BottomEnd) { val chatItemTail = remember { appPreferences.chatItemTail.state } - val style = shapeStyle(ci, chatItemTail.value, tailVisible, revealed = true) + val style = shapeStyle(ci, chatItemTail.value, tailVisible, true) val tailRendered = style is ShapeStyle.Bubble && style.tailVisible Column( Modifier @@ -253,7 +259,11 @@ fun FramedItemView( onLongClick = { showMenu.value = true }, onClick = { if (ci.quotedItem.itemId != null) { - scrollToItem(ci.quotedItem.itemId) + if (ci.isReport && chatsCtx.secondaryContextFilter != null) { + scrollToItemId.value = ci.quotedItem.itemId + } else { + scrollToItem(ci.quotedItem.itemId) + } } else { scrollToQuotedItemFromItem(ci.id) } @@ -289,7 +299,7 @@ fun FramedItemView( if (mc.text == "" && !ci.meta.isLive) { metaColor = Color.White } else { - CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } is MsgContent.MCVideo -> { @@ -297,26 +307,26 @@ fun FramedItemView( if (mc.text == "" && !ci.meta.isLive) { metaColor = Color.White } else { - CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } is MsgContent.MCVoice -> { CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, hasText = true, ci, timedMessagesTTL = chatTTL, showViaProxy = showViaProxy, showTimestamp = showTimestamp, longClick = { onLinkLongClick("") }, receiveFile = receiveFile) if (mc.text != "") { - CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } is MsgContent.MCFile -> ciFileView(ci, mc.text) is MsgContent.MCUnknown -> if (ci.file == null) { - CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } else { ciFileView(ci, mc.text) } is MsgContent.MCLink -> { ChatItemLinkView(mc.preview, showMenu, onLongClick = { showMenu.value = true }) Box(Modifier.widthIn(max = DEFAULT_MAX_IMAGE_WIDTH)) { - CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } is MsgContent.MCReport -> { @@ -325,9 +335,9 @@ fun FramedItemView( append(if (mc.text.isEmpty()) mc.reason.text else "${mc.reason.text}: ") } } - CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp, prefix = prefix) + CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp, prefix = prefix) } - else -> CIMarkdownText(ci, chatInfo, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + else -> CIMarkdownText(chatsCtx, ci, chat, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } } } @@ -347,8 +357,9 @@ fun FramedItemView( @Composable fun CIMarkdownText( + chatsCtx: ChatModel.ChatsContext, ci: ChatItem, - chatInfo: ChatInfo, + chat: Chat, chatTTL: Int?, linkMode: SimplexLinkMode, uriHandler: UriHandler?, @@ -358,9 +369,11 @@ fun CIMarkdownText( prefix: AnnotatedString? = null ) { Box(Modifier.padding(vertical = 7.dp, horizontal = 12.dp)) { + val chatInfo = chat.chatInfo val text = if (ci.meta.isLive) ci.content.msgContent?.text ?: ci.text else ci.text MarkdownText( text, if (text.isEmpty()) emptyList() else ci.formattedText, toggleSecrets = true, + sendCommandMsg = if (chatInfo.useCommands && chat.chatInfo.sndReady) { { msg -> sendCommandMsg(chatsCtx, chat, msg) } } else null, meta = ci.meta, chatTTL = chatTTL, linkMode = linkMode, mentions = ci.mentions, userMemberId = when { chatInfo is ChatInfo.Group -> chatInfo.groupInfo.membership.memberId @@ -371,6 +384,32 @@ fun CIMarkdownText( } } +fun sendCommandMsg(chatsCtx: ChatModel.ChatsContext, chat: Chat, msg: String) { + if (chat.chatInfo.sndReady) { + withLongRunningApi(slow = 60_000) { + val cInfo = chat.chatInfo + val chatItems = + chatModel.controller.apiSendMessages( + rh = chat.remoteHostId, + type = cInfo.chatType, + id = cInfo.apiId, + scope = cInfo.groupChatScope(), + composedMessages = listOf(ComposedMessage(fileSource = null, quotedItemId = null, msgContent = MsgContent.MCText(msg), mentions = emptyMap())) + ) + if (!chatItems.isNullOrEmpty()) { + chatItems.forEach { aChatItem -> + withContext(Dispatchers.Main) { + chatsCtx.addChatItem(chat.remoteHostId, aChatItem.chatInfo, aChatItem.chatItem) + } + } + } + } + } else { + AlertManager.shared.showAlertMsg(MR.strings.cant_send_message_alert_title, MR.strings.cant_send_commands_alert_text) + } +} + + const val CHAT_IMAGE_LAYOUT_ID = "chatImage" const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble" const val CHAT_COMPOSE_LAYOUT_ID = "chatCompose" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index ad11eb4897..60595fc255 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -1,5 +1,8 @@ package chat.simplex.common.views.chat.item +import SectionItemView +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.BasicText import androidx.compose.foundation.text.InlineTextContent import androidx.compose.material.MaterialTheme @@ -11,15 +14,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.* import androidx.compose.ui.platform.* import androidx.compose.ui.text.* -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.AnnotatedString.Range +import androidx.compose.ui.text.font.* +import androidx.compose.ui.text.style.* import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.views.helpers.* +import chat.simplex.res.* import kotlinx.coroutines.* val reserveTimestampStyle = SpanStyle(color = Color.Transparent) @@ -61,6 +65,7 @@ fun MarkdownText ( mentions: Map? = null, userMemberId: String? = null, toggleSecrets: Boolean, + sendCommandMsg: ((String) -> Unit)? = null, style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface, lineHeight = 22.sp), maxLines: Int = Int.MAX_VALUE, overflow: TextOverflow = TextOverflow.Clip, @@ -134,51 +139,111 @@ fun MarkdownText ( } Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, inlineContent = inlineContent?.second ?: mapOf()) } else { - var hasAnnotations = false + var hasLinks = false + var hasSecrets = false + var hasCommands = false val annotatedText = buildAnnotatedString { inlineContent?.first?.invoke(this) appendSender(this, sender, senderBold) if (prefix != null) append(prefix) for ((i, ft) in formattedText.withIndex()) { if (ft.format == null) append(ft.text) - else if (toggleSecrets && ft.format is Format.Secret) { - val ftStyle = ft.format.style - hasAnnotations = true - val key = i.toString() - withAnnotation(tag = "SECRET", annotation = key) { - if (showSecrets[key] == true) append(ft.text) else withStyle(ftStyle) { append(ft.text) } - } - } else if (ft.format is Format.Mention) { - val mention = mentions?.get(ft.format.memberName) - - if (mention != null) { - if (mention.memberRef != null) { - val displayName = mention.memberRef.displayName - val name = if (mention.memberRef.localAlias.isNullOrEmpty()) { - displayName - } else { - "${mention.memberRef.localAlias} ($displayName)" - } - val mentionStyle = if (mention.memberId == userMemberId) ft.format.style.copy(color = MaterialTheme.colors.primary) else ft.format.style - - withStyle(mentionStyle) { append(mentionText(name)) } - } else { - withStyle( ft.format.style) { append(mentionText(ft.format.memberName)) } - } - } else { - append(ft.text) - } - } else { - val link = ft.link(linkMode) - if (link != null) { - hasAnnotations = true + else when(ft.format) { + is Format.Bold -> withStyle(ft.format.style) { append(ft.text) } + is Format.Italic -> withStyle(ft.format.style) { append(ft.text) } + is Format.StrikeThrough -> withStyle(ft.format.style) { append(ft.text) } + is Format.Snippet -> withStyle(ft.format.style) { append(ft.text) } + is Format.Colored -> withStyle(ft.format.style) { append(ft.text) } + is Format.Secret -> { val ftStyle = ft.format.style - withAnnotation(tag = if (ft.format is Format.SimplexLink) "SIMPLEX_URL" else "URL", annotation = link) { - withStyle(ftStyle) { append(ft.viewText(linkMode)) } + if (toggleSecrets) { + hasSecrets = true + val key = i.toString() + withAnnotation(tag = "SECRET", annotation = key) { + if (showSecrets[key] == true) append(ft.text) else withStyle(ftStyle) { append(ft.text) } + } + } else { + withStyle(ftStyle) { append(ft.text) } } - } else { - withStyle(ft.format.style) { append(ft.text) } } + is Format.Mention -> { + val mention = mentions?.get(ft.format.memberName) + if (mention != null) { + val ftStyle = ft.format.style + if (mention.memberRef != null) { + val displayName = mention.memberRef.displayName + val name = if (mention.memberRef.localAlias.isNullOrEmpty()) { + displayName + } else { + "${mention.memberRef.localAlias} ($displayName)" + } + val mentionStyle = if (mention.memberId == userMemberId) ftStyle.copy(color = MaterialTheme.colors.primary) else ftStyle + withStyle(mentionStyle) { append(mentionText(name)) } + } else { + withStyle(ftStyle) { append(mentionText(ft.format.memberName)) } + } + } else { + append(ft.text) + } + } + is Format.Command -> + if (sendCommandMsg == null) { + append(ft.text) + } else { + hasCommands = true + val ftStyle = ft.format.style + val cmd = ft.format.commandStr + withAnnotation(tag = "COMMAND", annotation = cmd) { + withStyle(ftStyle) { append("/$cmd") } + } + } + is Format.Uri -> { + hasLinks = true + val ftStyle = Format.linkStyle + val s = ft.text + val link = if (s.startsWith("http://") || s.startsWith("https://")) s else "https://$s" + withAnnotation(tag = "WEB_URL", annotation = link) { + withStyle(ftStyle) { append(ft.text) } + } + } + is Format.HyperLink -> { + hasLinks = true + val ftStyle = Format.linkStyle + withAnnotation(tag = "WEB_URL", annotation = ft.format.linkUri) { + withStyle(ftStyle) { append(ft.format.showText ?: ft.text) } + } + } + is Format.SimplexLink -> { + hasLinks = true + val ftStyle = Format.linkStyle + val link = + if (linkMode == SimplexLinkMode.BROWSER && ft.format.showText == null && !ft.text.startsWith("[")) ft.text + else ft.format.simplexUri + val t = ft.format.showText ?: if (linkMode == SimplexLinkMode.DESCRIPTION) ft.format.linkType.description else null + withAnnotation(tag = "SIMPLEX_URL", annotation = link) { + if (t == null) { + withStyle(ftStyle) { append(ft.text) } + } else { + withStyle(ftStyle) { append("$t ") } + withStyle(ftStyle.copy(fontStyle = FontStyle.Italic)) { append(ft.format.viaHosts) } + } + } + } + is Format.Email -> { + hasLinks = true + val ftStyle = Format.linkStyle + withAnnotation(tag = "OTHER_URL", annotation = "mailto:${ft.text}") { + withStyle(ftStyle) { append(ft.text) } + } + } + is Format.Phone -> { + hasLinks = true + val ftStyle = Format.linkStyle + withAnnotation(tag = "OTHER_URL", annotation = "tel:${ft.text}") { + withStyle(ftStyle) { append(ft.text) } + } + } + is Format.Unknown -> append(ft.text) } } if (meta?.isLive == true) { @@ -189,51 +254,51 @@ fun MarkdownText ( withStyle(reserveTimestampStyle) { append("\n" + metaText) } else */if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) } } - if (hasAnnotations && uriHandler != null) { + if ((hasLinks && uriHandler != null) || hasSecrets || (hasCommands && sendCommandMsg != null)) { val icon = remember { mutableStateOf(PointerIcon.Default) } ClickableText(annotatedText, style = style, modifier = modifier.pointerHoverIcon(icon.value), maxLines = maxLines, overflow = overflow, onLongClick = { offset -> - annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) - .firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) } - annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) - .firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) } + if (hasLinks) { + val withAnnotation: (String, (Range) -> Unit) -> Unit = { tag, f -> + annotatedText.getStringAnnotations(tag, start = offset, end = offset).firstOrNull()?.let(f) + } + withAnnotation("WEB_URL") { a -> onLinkLongClick(a.item) } + withAnnotation("SIMPLEX_URL") { a -> onLinkLongClick(a.item) } + withAnnotation("OTHER_URL") { a -> onLinkLongClick(a.item) } + } }, onClick = { offset -> - annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) - .firstOrNull()?.let { annotation -> - try { - uriHandler.openUri(annotation.item) - } catch (e: Exception) { - // It can happen, for example, when you click on a text 0.00001 but don't have any app that can catch - // `tel:` scheme in url installed on a device (no phone app or contacts, maybe) - Log.e(TAG, "Open url: ${e.stackTraceToString()}") - } - } - annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) - .firstOrNull()?.let { annotation -> - uriHandler.openVerifiedSimplexUri(annotation.item) - } - annotatedText.getStringAnnotations(tag = "SECRET", start = offset, end = offset) - .firstOrNull()?.let { annotation -> - val key = annotation.item + val withAnnotation: (String, (Range) -> Unit) -> Unit = { tag, f -> + annotatedText.getStringAnnotations(tag, start = offset, end = offset).firstOrNull()?.let(f) + } + if (hasLinks && uriHandler != null) { + withAnnotation("WEB_URL") { a -> openBrowserAlert(a.item, uriHandler) } + withAnnotation("OTHER_URL") { a -> safeOpenUri(a.item, uriHandler) } + withAnnotation("SIMPLEX_URL") { a -> uriHandler.openVerifiedSimplexUri(a.item) } + } + if (hasSecrets) { + withAnnotation("SECRET") { a -> + val key = a.item showSecrets[key] = !(showSecrets[key] ?: false) } + } + if (hasCommands && sendCommandMsg != null) { + withAnnotation("COMMAND") { a -> sendCommandMsg("/${a.item}") } + } }, onHover = { offset -> - icon.value = annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) - .firstOrNull()?.let { + val hasAnnotation: (String) -> Boolean = { tag -> annotatedText.hasStringAnnotations(tag, start = offset, end = offset) } + icon.value = + if (hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SECRET") || hasAnnotation("COMMAND")) { PointerIcon.Hand - } ?: annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) - .firstOrNull()?.let { - PointerIcon.Hand - } ?: annotatedText.getStringAnnotations(tag = "SECRET", start = offset, end = offset) - .firstOrNull()?.let { - PointerIcon.Hand - } ?: PointerIcon.Default + } else { + PointerIcon.Default + } }, shouldConsumeEvent = { offset -> - annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).any() - annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset).any() + annotatedText.hasStringAnnotations(tag = "WEB_URL", start = offset, end = offset) + || annotatedText.hasStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) + || annotatedText.hasStringAnnotations(tag = "OTHER_URL", start = offset, end = offset) } ) } else { @@ -302,6 +367,74 @@ fun ClickableText( ) } +fun openBrowserAlert(uri: String, uriHandler: UriHandler) { + val (res, err) = sanitizeUri(uri) + if (res == null) { + showInvalidLinkAlert(uri, err) + } else { + val message = if (uri.count() > 160) uri.substring(0, 159) + "…" else uri + val sanitizedUri = res.second + if (sanitizedUri == null) { + AlertManager.shared.showAlertDialog( + generalGetString(MR.strings.privacy_chat_list_open_web_link_question), + message, + confirmText = generalGetString(MR.strings.open_verb), + onConfirm = { safeOpenUri(uri, uriHandler) } + ) + } else { + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.privacy_chat_list_open_web_link_question), + message, + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + safeOpenUri(uri, uriHandler) + }) { + Text(generalGetString(MR.strings.privacy_chat_list_open_full_web_link), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + SectionItemView({ + AlertManager.shared.hideAlert() + safeOpenUri(sanitizedUri, uriHandler) + }) { + Text(generalGetString(MR.strings.privacy_chat_list_open_clean_web_link), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(generalGetString(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + }) + } + } +} + +fun safeOpenUri(uri: String, uriHandler: UriHandler) { + try { + uriHandler.openUri(uri) + } catch (e: Exception) { + // It can happen, for example, when you click on a text 0.00001 but don't have any app that can catch + // `tel:` scheme in url installed on a device (no phone app or contacts, maybe) + Log.e(TAG, "Open url: ${e.stackTraceToString()}") + showInvalidLinkAlert(uri, error = e.message) + } +} + +fun showInvalidLinkAlert(uri: String, error: String? = null) { + val message = if (error.isNullOrEmpty()) { uri } else { error + "\n" + uri } + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_parsing_uri_title), message) +} + +fun sanitizeUri(s: String): Pair?, String?> { + val parsed = parseSanitizeUri(s, safe = false) + return if (parsed?.uriInfo != null) { + (true to parsed.uriInfo.sanitized) to null + } else { + null to parsed?.parseError + } +} + private fun isRtl(s: CharSequence): Boolean { for (element in s) { val d = Character.getDirectionality(element) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 958b794bd7..4a8e9e5193 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -132,7 +132,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { click = { contactRequestAlertDialog(chat.remoteHostId, chat.chatInfo, chatModel) { onRequestAccepted(it) } }, dropdownMenuItems = { tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) { - ContactRequestMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu) + ContactRequestMenuItems(chat.remoteHostId, contactRequestId = chat.chatInfo.apiId, chatModel, showMenu) } }, showMenu, @@ -188,7 +188,7 @@ fun ErrorChatListItem() { suspend fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) { when { - contact.activeConn == null && contact.profile.contactLink != null && contact.active -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) + contact.isContactCard -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) else -> openDirectChat(rhId, contact.contactId) } } @@ -236,7 +236,6 @@ suspend fun openChat( suspend fun openLoadedChat(chat: Chat) { withContext(Dispatchers.Main) { - chatModel.chatsContext.chatItemStatuses.clear() chatModel.chatsContext.chatItems.replaceAll(chat.chatItems) chatModel.chatId.value = chat.chatInfo.id chatModel.chatsContext.chatState.clear() @@ -272,18 +271,28 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo @Composable fun ContactMenuItems(chat: Chat, contact: Contact, chatModel: ChatModel, showMenu: MutableState, showMarkRead: Boolean) { - if (contact.activeConn != null) { - if (showMarkRead) { - MarkReadChatAction(chat, showMenu) + if (contact.nextAcceptContactRequest) { + if (contact.contactRequestId != null) { + ContactRequestMenuItems(chat.remoteHostId, contactRequestId = contact.contactRequestId, chatModel, showMenu) + } else if (contact.groupDirectInv != null && !contact.groupDirectInv.memberRemoved) { + MemberContactRequestMenuItems(chat.remoteHostId, contact, showMenu) } else { - MarkUnreadChatAction(chat, chatModel, showMenu) + DeleteContactAction(chat, chatModel, showMenu) } - ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu) - ToggleNotificationsChatAction(chat, chatModel, contact.chatSettings.enableNtfs.nextMode(false), showMenu) - TagListAction(chat, showMenu) - ClearChatAction(chat, showMenu) + } else { + if (contact.activeConn != null) { + if (showMarkRead) { + MarkReadChatAction(chat, showMenu) + } else { + MarkUnreadChatAction(chat, chatModel, showMenu) + } + ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu) + ToggleNotificationsChatAction(chat, chatModel, contact.chatSettings.enableNtfs.nextMode(false), showMenu) + TagListAction(chat, showMenu) + ClearChatAction(chat, showMenu) + } + DeleteContactAction(chat, chatModel, showMenu) } - DeleteContactAction(chat, chatModel, showMenu) } @Composable @@ -305,7 +314,7 @@ fun GroupMenuItems( } } GroupMemberStatus.MemAccepted -> { - if (groupInfo.membership.memberCurrent) { + if (groupInfo.membership.memberCurrentOrPending) { LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu) } if (groupInfo.canDelete) { @@ -327,7 +336,7 @@ fun GroupMenuItems( } } ClearChatAction(chat, showMenu) - if (groupInfo.membership.memberCurrent) { + if (groupInfo.membership.memberCurrentOrPending) { LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu) } if (groupInfo.canDelete) { @@ -510,22 +519,46 @@ fun LeaveGroupAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, sh } @Composable -fun ContactRequestMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactRequest, chatModel: ChatModel, showMenu: MutableState, onSuccess: ((chat: Chat) -> Unit)? = null) { +fun ContactRequestMenuItems(rhId: Long?, contactRequestId: Long, chatModel: ChatModel, showMenu: MutableState, onSuccess: ((chat: Chat) -> Unit)? = null) { ItemAction( stringResource(MR.strings.accept_contact_button), painterResource(MR.images.ic_check), color = MaterialTheme.colors.onBackground, onClick = { - acceptContactRequest(rhId, incognito = false, chatInfo.apiId, chatInfo, true, chatModel, onSuccess) + acceptContactRequest(rhId, incognito = false, contactRequestId, true, chatModel, onSuccess) showMenu.value = false } ) + if (!chatModel.addressShortLinkDataSet()) { + ItemAction( + stringResource(MR.strings.accept_contact_incognito_button), + painterResource(MR.images.ic_theater_comedy), + color = MaterialTheme.colors.onBackground, + onClick = { + acceptContactRequest(rhId, incognito = true, contactRequestId, true, chatModel, onSuccess) + showMenu.value = false + } + ) + } ItemAction( - stringResource(MR.strings.accept_contact_incognito_button), - painterResource(MR.images.ic_theater_comedy), + stringResource(MR.strings.reject_contact_button), + painterResource(MR.images.ic_close), + onClick = { + rejectContactRequest(rhId, contactRequestId, chatModel) + showMenu.value = false + }, + color = Color.Red + ) +} + +@Composable +fun MemberContactRequestMenuItems(rhId: Long?, contact: Contact, showMenu: MutableState, onSuccess: ((chat: Chat) -> Unit)? = null) { + ItemAction( + stringResource(MR.strings.accept_contact_button), + painterResource(MR.images.ic_check), color = MaterialTheme.colors.onBackground, onClick = { - acceptContactRequest(rhId, incognito = true, chatInfo.apiId, chatInfo, true, chatModel, onSuccess) + acceptMemberContact(rhId, contact.contactId, onSuccess) showMenu.value = false } ) @@ -533,7 +566,7 @@ fun ContactRequestMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactRequest, chat stringResource(MR.strings.reject_contact_button), painterResource(MR.images.ic_close), onClick = { - rejectContactRequest(rhId, chatInfo, chatModel) + showRejectMemberContactRequestAlert(rhId, contact) showMenu.value = false }, color = Color.Red @@ -651,7 +684,7 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) { if (success) { withContext(Dispatchers.Main) { chatModel.chatsContext.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true))) - chatModel.chatsContext.updateChatTagReadNoContentTag(chat, wasUnread) + chatModel.chatsContext.updateChatTagReadInPrimaryContext(chat, wasUnread) } } } @@ -665,19 +698,21 @@ fun contactRequestAlertDialog(rhId: Long?, contactRequest: ChatInfo.ContactReque Column { SectionItemView({ AlertManager.shared.hideAlert() - acceptContactRequest(rhId, incognito = false, contactRequest.apiId, contactRequest, true, chatModel, onSucess) + acceptContactRequest(rhId, incognito = false, contactRequest.apiId, true, chatModel, onSucess) }) { Text(generalGetString(MR.strings.accept_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } - SectionItemView({ - AlertManager.shared.hideAlert() - acceptContactRequest(rhId, incognito = true, contactRequest.apiId, contactRequest, true, chatModel, onSucess) - }) { - Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + if (!chatModel.addressShortLinkDataSet()) { + SectionItemView({ + AlertManager.shared.hideAlert() + acceptContactRequest(rhId, incognito = true, contactRequest.apiId, true, chatModel, onSucess) + }) { + Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } } SectionItemView({ AlertManager.shared.hideAlert() - rejectContactRequest(rhId, contactRequest, chatModel) + rejectContactRequest(rhId, contactRequest.apiId, chatModel) }) { Text(generalGetString(MR.strings.reject_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) } @@ -687,25 +722,48 @@ fun contactRequestAlertDialog(rhId: Long?, contactRequest: ChatInfo.ContactReque ) } -fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel, close: ((chat: Chat) -> Unit)? = null ) { +fun acceptContactRequest( + rhId: Long?, + incognito: Boolean, + contactRequestId: Long, + isCurrentUser: Boolean, + chatModel: ChatModel, + close: ((chat: Chat) -> Unit)? = null, + inProgress: MutableState? = null +) { withBGApi { - val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId) - if (contact != null && isCurrentUser && contactRequest != null) { + inProgress?.value = true + val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, contactRequestId) + if (contact != null && isCurrentUser) { val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf()) withContext(Dispatchers.Main) { - chatModel.chatsContext.replaceChat(rhId, contactRequest.id, chat) + if (contact.contactRequestId != null) { // means contact request was initially created with contact, so we don't need to replace it + chatModel.chatsContext.updateContact(rhId, contact) + } else { + chatModel.chatsContext.replaceChat(rhId, contactRequestChatId(contactRequestId), chat) + } + inProgress?.value = false } chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected()) close?.invoke(chat) + } else { + inProgress?.value = false } } } -fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { +fun rejectContactRequest(rhId: Long?, contactRequestId: Long, chatModel: ChatModel, dismissToChatList: Boolean = false) { withBGApi { - chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId) + val contact_ = chatModel.controller.apiRejectContactRequest(rhId, contactRequestId) withContext(Dispatchers.Main) { - chatModel.chatsContext.removeChat(rhId, contactRequest.id) + if (contact_ != null) { // means contact request was initially created with contact, so we need to remove contact chat + chatModel.chatsContext.removeChat(rhId, contact_.id) + } else { + chatModel.chatsContext.removeChat(rhId, contactRequestChatId(contactRequestId)) + } + if (dismissToChatList) { + chatModel.chatId.value = null + } } } } @@ -765,33 +823,49 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( close: (() -> Unit)?, openChat: Boolean ) { + @Composable + fun UseCurrentProfileButton() { + SectionItemView({ + AlertManager.privacySensitive.hideAlert() + withBGApi { + val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false) + if (ok && openChat) { + close?.invoke() + openDirectChat(rhId, contact.contactId) + } + } + }) { + Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + + @Composable + fun UseIncognitoProfileButton(text: String) { + SectionItemView({ + AlertManager.privacySensitive.hideAlert() + withBGApi { + val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true) + if (ok && openChat) { + close?.invoke() + openDirectChat(rhId, contact.contactId) + } + } + }) { + Text(text, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + AlertManager.privacySensitive.showAlertDialogButtonsColumn( title = String.format(generalGetString(MR.strings.connect_with_contact_name_question), contact.chatViewName), buttons = { Column { - SectionItemView({ - AlertManager.privacySensitive.hideAlert() - withBGApi { - close?.invoke() - val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false) - if (ok && openChat) { - openDirectChat(rhId, contact.contactId) - } - } - }) { - Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) - } - SectionItemView({ - AlertManager.privacySensitive.hideAlert() - withBGApi { - close?.invoke() - val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true) - if (ok && openChat) { - openDirectChat(rhId, contact.contactId) - } - } - }) { - Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + if (!contact.profileChangeProhibited) { + UseCurrentProfileButton() + UseIncognitoProfileButton(generalGetString(MR.strings.connect_use_new_incognito_profile)) + } else if (!contact.contactConnIncognito) { + UseCurrentProfileButton() + } else { + UseIncognitoProfileButton(generalGetString(MR.strings.connect_use_incognito_profile)) } SectionItemView({ AlertManager.privacySensitive.hideAlert() @@ -886,7 +960,7 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch ChatInfo.Direct(contact.copy(chatSettings = chatSettings)) } is ChatInfo.Group -> with(chatInfo) { - ChatInfo.Group(groupInfo.copy(chatSettings = chatSettings)) + ChatInfo.Group(groupInfo.copy(chatSettings = chatSettings), groupChatScope = null) } else -> null } @@ -914,7 +988,7 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch val updatedChat = chatModel.getChat(chatInfo.id) if (updatedChat != null) { withContext(Dispatchers.Main) { - chatModel.chatsContext.updateChatTagReadNoContentTag(updatedChat, wasUnread) + chatModel.chatsContext.updateChatTagReadInPrimaryContext(updatedChat, wasUnread) } } val current = currentState?.value diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 87c02f038c..2109e21bfe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -55,6 +55,7 @@ sealed class ActiveFilter { } private fun showNewChatSheet(oneHandUI: State) { + connectProgressManager.cancelConnectProgress() ModalManager.start.closeModals() ModalManager.end.closeModals() chatModel.newChatSheetVisible.value = true @@ -348,22 +349,64 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow val updatingProgress = remember { chatModel.updatingProgress }.value val oneHandUI = remember { appPrefs.oneHandUI.state } - if (oneHandUI.value) { - val sp16 = with(LocalDensity.current) { 16.sp.toDp() } - - if (appPlatform.isDesktop && oneHandUI.value) { - val call = remember { chatModel.activeCall } - if (call.value != null) { - barButtons.add { - val c = call.value - if (c != null) { - ActiveCallInteractiveArea(c) - Spacer(Modifier.width(5.dp)) - } + if (updatingProgress != null) { + barButtons.add { + val interactionSource = remember { MutableInteractionSource() } + val hovered = interactionSource.collectIsHoveredAsState().value + IconButton(onClick = { + chatModel.updatingRequest?.close() + }, Modifier.hoverable(interactionSource)) { + if (hovered) { + Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange) + } else if (updatingProgress == -1f) { + CIFileViewScope.progressIndicator() + } else { + CIFileViewScope.progressCircle((updatingProgress * 100).toLong(), 100) } } } - if (!stopped) { + } + + if (stopped) { + barButtons.add { + IconButton(onClick = { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.chat_is_stopped_indication), + generalGetString(MR.strings.you_can_start_chat_via_setting_or_by_restarting_the_app) + ) + }) { + Icon( + painterResource(MR.images.ic_report_filled), + generalGetString(MR.strings.chat_is_stopped_indication), + tint = Color.Red, + ) + } + } + } else { + if (connectProgressManager.showConnectProgress != null) { + barButtons.add { + Box(Modifier.padding(horizontal = DEFAULT_PADDING_HALF)) { + CIFileViewScope.progressIndicator() + } + } + } + + if (oneHandUI.value) { + val sp16 = with(LocalDensity.current) { 16.sp.toDp() } + + if (appPlatform.isDesktop && oneHandUI.value) { + val call = remember { chatModel.activeCall } + if (call.value != null) { + barButtons.add { + val c = call.value + if (c != null) { + ActiveCallInteractiveArea(c) + Spacer(Modifier.width(5.dp)) + } + } + } + } + barButtons.add { IconButton( onClick = { @@ -388,38 +431,6 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow } } - if (updatingProgress != null) { - barButtons.add { - val interactionSource = remember { MutableInteractionSource() } - val hovered = interactionSource.collectIsHoveredAsState().value - IconButton(onClick = { - chatModel.updatingRequest?.close() - }, Modifier.hoverable(interactionSource)) { - if (hovered) { - Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange) - } else if (updatingProgress == -1f) { - CIFileViewScope.progressIndicator() - } else { - CIFileViewScope.progressCircle((updatingProgress * 100).toLong(), 100) - } - } - } - } else if (stopped) { - barButtons.add { - IconButton(onClick = { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.chat_is_stopped_indication), - generalGetString(MR.strings.you_can_start_chat_via_setting_or_by_restarting_the_app) - ) - }) { - Icon( - painterResource(MR.images.ic_report_filled), - generalGetString(MR.strings.chat_is_stopped_indication), - tint = Color.Red, - ) - } - } - } val clipboard = LocalClipboardManager.current val scope = rememberCoroutineScope() val canScrollToZero = remember { derivedStateOf { listState.firstVisibleItemIndex != 0 || listState.firstVisibleItemScrollOffset != 0 } } @@ -590,7 +601,8 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: String, chatModel: ChatModel) { chatModel.appOpenUrl.value = rhId to uri } else { withBGApi { - planAndConnect(rhId, uri, incognito = null, close = null) + chatModel.appOpenUrlConnecting.value = true + planAndConnect(rhId, uri, close = null, cleanup = { chatModel.appOpenUrlConnecting.value = false }) } } } @@ -648,7 +660,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState // if SimpleX link is pasted, show connection dialogue hideKeyboard(view) if (link.format is Format.SimplexLink) { - val linkText = link.simplexLinkText(link.format.linkType, link.format.smpHosts) + val linkText = link.format.simplexLinkText searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero) } searchShowingSimplexLink.value = true @@ -658,8 +670,13 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState if (it.isNotEmpty()) { // if some other text is pasted, enter search mode focusRequester.requestFocus() - } else if (listState.layoutInfo.totalItemsCount > 0) { - listState.scrollToItem(0) + } else { + if (!chatModel.appOpenUrlConnecting.value) { + connectProgressManager.cancelConnectProgress() + } + if (listState.layoutInfo.totalItemsCount > 0) { + listState.scrollToItem(0) + } } searchShowingSimplexLink.value = false searchChatFilteredBySimplexLink.value = null @@ -677,7 +694,6 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState< planAndConnect( chatModel.remoteHostId(), link, - incognito = null, filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, filterKnownGroup = { searchChatFilteredBySimplexLink.value = it.id }, close = null, @@ -1212,7 +1228,7 @@ fun presetTagMatchesChat(tag: PresetTagKind, chatInfo: ChatInfo, chatStats: Chat PresetTagKind.GROUP_REPORTS -> chatStats.reportsCount > 0 PresetTagKind.FAVORITES -> chatInfo.chatSettings?.favorite == true PresetTagKind.CONTACTS -> when (chatInfo) { - is ChatInfo.Direct -> !(chatInfo.contact.activeConn == null && chatInfo.contact.profile.contactLink != null && chatInfo.contact.active) && !chatInfo.contact.chatDeleted + is ChatInfo.Direct -> !chatInfo.contact.isContactCard && !chatInfo.contact.chatDeleted is ChatInfo.ContactRequest -> true is ChatInfo.ContactConnection -> true is ChatInfo.Group -> chatInfo.groupInfo.businessChat?.chatType == BusinessChatType.Customer diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index 93d512507a..d5d6facafe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -137,114 +137,155 @@ fun ChatPreviewView( fun chatPreviewTitle() { val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) } when (cInfo) { - is ChatInfo.Direct -> + is ChatInfo.Direct -> { Row(verticalAlignment = Alignment.CenterVertically) { if (cInfo.contact.verified) { VerifiedIcon() } - chatPreviewTitleText( - if (deleting) - MaterialTheme.colors.secondary - else - Color.Unspecified - ) + val color = if (deleting) + MaterialTheme.colors.secondary + else if ((cInfo.contact.nextAcceptContactRequest && cInfo.contact.groupDirectInv?.memberRemoved != true) || cInfo.contact.sendMsgToConnect) { + MaterialTheme.colors.primary + } else if (!cInfo.contact.sndReady) { + MaterialTheme.colors.secondary + } else { + Color.Unspecified + } + chatPreviewTitleText(color = color) } - is ChatInfo.Group -> - when (cInfo.groupInfo.membership.memberStatus) { - GroupMemberStatus.MemInvited -> chatPreviewTitleText( - if (inProgress || deleting) - MaterialTheme.colors.secondary - else - if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary - ) - GroupMemberStatus.MemAccepted, GroupMemberStatus.MemRejected -> chatPreviewTitleText(MaterialTheme.colors.secondary) - else -> chatPreviewTitleText( - if (deleting) - MaterialTheme.colors.secondary - else - Color.Unspecified - ) + } + is ChatInfo.Group -> { + val color = if (deleting) { + MaterialTheme.colors.secondary + } else { + when (cInfo.groupInfo.membership.memberStatus) { + GroupMemberStatus.MemInvited -> if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary + GroupMemberStatus.MemAccepted, GroupMemberStatus.MemRejected -> MaterialTheme.colors.secondary + else -> if (cInfo.groupInfo.nextConnectPrepared) MaterialTheme.colors.primary else Color.Unspecified + } } + chatPreviewTitleText(color = color) + } else -> chatPreviewTitleText() } } @Composable - fun chatPreviewText() { - val ci = chat.chatItems.lastOrNull() - if (ci != null) { - if (showChatPreviews || (chatModelDraftChatId == chat.id && chatModelDraft != null)) { - val sp20 = with(LocalDensity.current) { 20.sp.toDp() } - val (text: CharSequence, inlineTextContent) = when { - chatModelDraftChatId == chat.id && chatModelDraft != null -> remember(chatModelDraft) { chatModelDraft.message.text to messageDraft(chatModelDraft, sp20) } - ci.meta.itemDeleted == null -> ci.text to null - else -> markedDeletedText(ci, chat.chatInfo) to null - } - val formattedText = when { - chatModelDraftChatId == chat.id && chatModelDraft != null -> null - ci.meta.itemDeleted == null -> ci.formattedText - else -> null - } - val prefix = when (val mc = ci.content.msgContent) { - is MsgContent.MCReport -> - buildAnnotatedString { - withStyle(SpanStyle(color = Color.Red, fontStyle = FontStyle.Italic)) { - append(if (text.isEmpty()) mc.reason.text else "${mc.reason.text}: ") - } - } - else -> null + fun chatPreviewInfoText(): Pair? { + return when (cInfo) { + is ChatInfo.Direct -> + if (cInfo.contact.isContactCard) { + stringResource(MR.strings.contact_tap_to_connect) to MaterialTheme.colors.primary + } else if (cInfo.contact.isBot && cInfo.contact.nextConnectPrepared) { + stringResource(MR.strings.open_to_use_bot) to Color.Unspecified + } else if (cInfo.contact.sendMsgToConnect) { + stringResource(MR.strings.open_to_connect) to Color.Unspecified + } else if (cInfo.contact.nextAcceptContactRequest) { + stringResource(MR.strings.open_to_accept) to Color.Unspecified + } else if (!cInfo.contact.sndReady && cInfo.contact.activeConn != null && cInfo.contact.active) { + if ((cInfo.contact.preparedContact?.uiConnLinkType == ConnectionMode.Con && !cInfo.contact.isBot) || cInfo.contact.contactGroupMemberId != null) { + stringResource(MR.strings.contact_should_accept) to Color.Unspecified + } else { + stringResource(MR.strings.contact_connection_pending) to Color.Unspecified + } + } else { + null } - MarkdownText( - text, - formattedText, - sender = when { - chatModelDraftChatId == chat.id && chatModelDraft != null -> null - cInfo is ChatInfo.Group && !ci.chatDir.sent -> ci.memberDisplayName + is ChatInfo.Group -> + if (cInfo.groupInfo.nextConnectPrepared) { + stringResource( + if (cInfo.groupInfo.businessChat?.chatType == BusinessChatType.Business) MR.strings.open_to_connect + else MR.strings.group_preview_open_to_join + ) to Color.Unspecified + } else { + when (cInfo.groupInfo.membership.memberStatus) { + GroupMemberStatus.MemRejected -> stringResource(MR.strings.group_preview_rejected) to Color.Unspecified + GroupMemberStatus.MemInvited -> groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo) to Color.Unspecified + GroupMemberStatus.MemAccepted -> stringResource(MR.strings.group_connection_pending) to Color.Unspecified + GroupMemberStatus.MemPendingReview, GroupMemberStatus.MemPendingApproval -> + stringResource(MR.strings.reviewed_by_admins) to MaterialTheme.colors.secondary else -> null - }, - mentions = ci.mentions, - userMemberId = when { - cInfo is ChatInfo.Group -> cInfo.groupInfo.membership.memberId - else -> null - }, - toggleSecrets = false, - linkMode = linkMode, - senderBold = true, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - style = TextStyle( - fontFamily = Inter, - fontSize = 15.sp, - color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, - lineHeight = 21.sp - ), - inlineContent = inlineTextContent, - modifier = Modifier.fillMaxWidth(), - prefix = prefix - ) + } + } + + else -> null + } + } + + @Composable + fun chatPreviewText() { + val previewText = chatPreviewInfoText() + val ci = chat.chatItems.lastOrNull() + if (chatModelDraftChatId == chat.id && chatModelDraft != null) { + val sp20 = with(LocalDensity.current) { 20.sp.toDp() } + val (text: CharSequence, inlineTextContent) = remember(chatModelDraft) { chatModelDraft.message.text to messageDraft(chatModelDraft, sp20) } + val formattedText = null + MarkdownText( + text, + formattedText, + toggleSecrets = false, + linkMode = linkMode, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = TextStyle( + fontFamily = Inter, + fontSize = 15.sp, + color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, + lineHeight = 21.sp + ), + inlineContent = inlineTextContent, + modifier = Modifier.fillMaxWidth() + ) + } else if (ci?.content?.hasMsgContent != true && previewText != null) { + Text(previewText.first, color = previewText.second) + } else if (ci != null && showChatPreviews) { + val (text: CharSequence, inlineTextContent) = when { + ci.meta.itemDeleted == null -> ci.text to null + else -> markedDeletedText(ci, chat.chatInfo) to null } - } else { - when (cInfo) { - is ChatInfo.Direct -> - if (cInfo.contact.activeConn == null && cInfo.contact.profile.contactLink != null && cInfo.contact.active) { - Text(stringResource(MR.strings.contact_tap_to_connect), color = MaterialTheme.colors.primary) - } else if (!cInfo.contact.sndReady && cInfo.contact.activeConn != null) { - if (cInfo.contact.nextSendGrpInv) { - Text(stringResource(MR.strings.member_contact_send_direct_message), color = MaterialTheme.colors.secondary) - } else if (cInfo.contact.active) { - Text(stringResource(MR.strings.contact_connection_pending), color = MaterialTheme.colors.secondary) + val formattedText = when { + ci.meta.itemDeleted == null -> ci.formattedText + else -> null + } + val prefix = when (val mc = ci.content.msgContent) { + is MsgContent.MCReport -> + buildAnnotatedString { + withStyle(SpanStyle(color = Color.Red, fontStyle = FontStyle.Italic)) { + append(if (text.isEmpty()) mc.reason.text else "${mc.reason.text}: ") } } - is ChatInfo.Group -> - when (cInfo.groupInfo.membership.memberStatus) { - GroupMemberStatus.MemRejected -> Text(stringResource(MR.strings.group_preview_rejected)) - GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo)) - GroupMemberStatus.MemAccepted -> Text(stringResource(MR.strings.group_connection_pending), color = MaterialTheme.colors.secondary) - else -> {} - } - else -> {} + + else -> null } + + MarkdownText( + text, + formattedText, + sender = when { + cInfo is ChatInfo.Group && !ci.chatDir.sent && !ci.meta.showGroupAsSender -> ci.memberDisplayName + else -> null + }, + mentions = ci.mentions, + userMemberId = when { + cInfo is ChatInfo.Group -> cInfo.groupInfo.membership.memberId + else -> null + }, + toggleSecrets = false, + linkMode = linkMode, + senderBold = true, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = TextStyle( + fontFamily = Inter, + fontSize = 15.sp, + color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, + lineHeight = 21.sp + ), + inlineContent = inlineTextContent, + modifier = Modifier.fillMaxWidth(), + prefix = prefix + ) } } @@ -257,37 +298,9 @@ fun ChatPreviewView( val uriHandler = LocalUriHandler.current when (mc) { is MsgContent.MCLink -> SmallContentPreview { - val linkClicksEnabled = remember { appPrefs.privacyChatListOpenLinks.state }.value != PrivacyChatListOpenLinksMode.NO - IconButton({ - when (appPrefs.privacyChatListOpenLinks.get()) { - PrivacyChatListOpenLinksMode.YES -> uriHandler.openUriCatching(mc.preview.uri) - PrivacyChatListOpenLinksMode.NO -> defaultClickAction() - PrivacyChatListOpenLinksMode.ASK -> AlertManager.shared.showAlertDialogButtonsColumn( - title = generalGetString(MR.strings.privacy_chat_list_open_web_link_question), - text = mc.preview.uri, - buttons = { - Column { - if (chatModel.chatId.value != chat.id) { - SectionItemView({ - AlertManager.shared.hideAlert() - defaultClickAction() - }) { - Text(stringResource(MR.strings.open_chat), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) - } - } - SectionItemView({ - AlertManager.shared.hideAlert() - uriHandler.openUriCatching(mc.preview.uri) - } - ) { - Text(stringResource(MR.strings.privacy_chat_list_open_web_link), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) - } - } - } - ) - } - }, - if (linkClicksEnabled) Modifier.desktopPointerHoverIconHand() else Modifier, + IconButton( + { openBrowserAlert(mc.preview.uri, uriHandler) }, + Modifier.desktopPointerHoverIconHand(), ) { Image(base64ToBitmap(mc.preview.image), null, contentScale = ContentScale.Crop) } @@ -337,7 +350,10 @@ fun ChatPreviewView( @Composable fun chatStatusImage() { if (cInfo is ChatInfo.Direct) { - if (cInfo.contact.active && cInfo.contact.activeConn != null) { + if ( + cInfo.contact.active && + (cInfo.contact.activeConn?.connStatus == ConnStatus.Ready || cInfo.contact.activeConn?.connStatus == ConnStatus.SndReady) + ) { val descr = contactNetworkStatus?.statusString when (contactNetworkStatus) { is NetworkStatus.Connected -> @@ -363,7 +379,11 @@ fun ChatPreviewView( if (progressByTimeout) { progressView() } else if (chat.chatStats.reportsCount > 0) { - GroupReportsIcon() + FlagIcon(color = MaterialTheme.colors.error) + } else if (chat.supportUnreadCount > 0) { + FlagIcon(color = MaterialTheme.colors.primary) + } else if (chat.chatInfo.groupInfo_?.membership?.memberPending == true) { + FlagIcon(color = MaterialTheme.colors.secondary) } else { IncognitoIcon(chat.chatInfo.incognito) } @@ -465,17 +485,10 @@ fun ChatPreviewView( ) } } else { - Text( - if (n > 0) unreadCountStr(n) else "", - color = Color.White, - fontSize = 10.sp, - style = TextStyle(textAlign = TextAlign.Center), - modifier = Modifier - .offset(y = 3.sp.toDp()) - .background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape) - .badgeLayout() - .padding(horizontal = 2.sp.toDp()) - .padding(vertical = 1.sp.toDp()) + UnreadBadge( + text = if (n > 0) unreadCountStr(n) else "", + backgroundColor = if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, + yOffset = 3.dp ) } } @@ -548,11 +561,11 @@ fun IncognitoIcon(incognito: Boolean) { } @Composable -fun GroupReportsIcon() { +fun FlagIcon(color: Color) { Icon( painterResource(MR.images.ic_flag), contentDescription = null, - tint = MaterialTheme.colors.error, + tint = color, modifier = Modifier .size(21.sp.toDp()) .offset(x = 2.sp.toDp()) @@ -567,6 +580,26 @@ private fun groupInvitationPreviewText(currentUserProfileDisplayName: String?, g stringResource(MR.strings.group_preview_you_are_invited) } +@Composable +fun UnreadBadge( + text: String, + backgroundColor: Color, + yOffset: Dp? = null +) { + Text( + text, + color = Color.White, + fontSize = 10.sp, + style = TextStyle(textAlign = TextAlign.Center), + modifier = Modifier + .offset(y = yOffset ?: 0.dp) + .background(backgroundColor, shape = CircleShape) + .badgeLayout() + .padding(horizontal = 2.sp.toDp()) + .padding(vertical = 1.sp.toDp()) + ) +} + @Composable fun unreadCountStr(n: Int): String { return if (n < 1000) "$n" else "${n / 1000}" + stringResource(MR.strings.thousand_abbreviation) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index acbc72ff48..55ac9c8810 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -120,7 +120,7 @@ fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, hasSess: Boolean, leadi val netCfg = rememberUpdatedState(chatModel.controller.getNetCfg()) val statusColorAndPercentage = subscriptionStatusColorAndPercentage(chatModel.networkInfo.value.online, netCfg.value.socksProxy, subs, hasSess) val pref = remember { chatModel.controller.appPrefs.networkShowSubscriptionPercentage } - val percentageText = "${(floor(statusColorAndPercentage.statusPercent * 100)).toInt()}%" + val percentageText = if (subs.total > 0 || hasSess) "${(floor(statusColorAndPercentage.statusPercent * 100)).toInt()}%" else "%" Row( verticalAlignment = Alignment.CenterVertically, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 185ec3925f..13351a2111 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -634,7 +634,7 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) { } @Composable -fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) { +fun BoxScope.userUnreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) { Text( if (unreadCount > 0) unreadCountStr(unreadCount) else "", color = Color.White, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt index 4e65a3649e..371e9072ea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt @@ -53,13 +53,7 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State, showDel click = { hideKeyboard(view) when (contactType) { - ContactType.RECENT -> { - withApi { - openChat(secondaryChatsCtx = null, rhId, chat.chatInfo) - ModalManager.start.closeModals() - } - } - ContactType.CHAT_DELETED -> { + ContactType.RECENT, ContactType.CONTACT_WITH_REQUEST, ContactType.CHAT_DELETED -> { withApi { openChat(secondaryChatsCtx = null, rhId, chat.chatInfo) ModalManager.start.closeModals() @@ -79,7 +73,28 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State, showDel }, dropdownMenuItems = { tryOrShowError("${chat.id}ContactListNavLinkDropdown", error = {}) { - DeleteContactAction(chat, chatModel, showMenu) + if (contactType == ContactType.CONTACT_WITH_REQUEST) { + if (chat.chatInfo.contact.contactRequestId != null) { + ContactRequestMenuItems( + rhId = chat.remoteHostId, + contactRequestId = chat.chatInfo.contact.contactRequestId, + chatModel = chatModel, + showMenu = showMenu, + onSuccess = { onRequestAccepted(it) } + ) + } else if (chat.chatInfo.contact.groupDirectInv != null && !chat.chatInfo.contact.groupDirectInv.memberRemoved) { + MemberContactRequestMenuItems( + rhId = chat.remoteHostId, + contact = chat.chatInfo.contact, + showMenu = showMenu, + onSuccess = { onRequestAccepted(it) } + ) + } else { + DeleteContactAction(chat, chatModel, showMenu) + } + } else { + DeleteContactAction(chat, chatModel, showMenu) + } } }, showMenu, @@ -108,7 +123,7 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State, showDel tryOrShowError("${chat.id}ContactListNavLinkDropdown", error = {}) { ContactRequestMenuItems( rhId = chat.remoteHostId, - chatInfo = chat.chatInfo, + contactRequestId = chat.chatInfo.apiId, chatModel = chatModel, showMenu = showMenu, onSuccess = { onRequestAccepted(it) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactPreviewView.kt index dd03bca921..636887275c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactPreviewView.kt @@ -31,18 +31,23 @@ fun ContactPreviewView( Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) } + val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) } + + val textColor = when { + deleting -> MaterialTheme.colors.secondary + contactType == ContactType.CARD -> MaterialTheme.colors.primary + contactType == ContactType.CONTACT_WITH_REQUEST -> + if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.groupDirectInv?.memberRemoved == true) + MaterialTheme.colors.secondary + else + MaterialTheme.colors.primary + contactType == ContactType.REQUEST -> MaterialTheme.colors.primary + contactType == ContactType.RECENT -> if (chat.chatInfo.nextConnect) MaterialTheme.colors.primary else Color.Unspecified + else -> Color.Unspecified + } + @Composable fun chatPreviewTitle() { - val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) } - - val textColor = when { - deleting -> MaterialTheme.colors.secondary - contactType == ContactType.CARD -> MaterialTheme.colors.primary - contactType == ContactType.REQUEST -> MaterialTheme.colors.primary - contactType == ContactType.RECENT && chat.chatInfo.incognito -> Indigo - else -> Color.Unspecified - } - when (cInfo) { is ChatInfo.Direct -> Row(verticalAlignment = Alignment.CenterVertically) { @@ -85,11 +90,11 @@ fun ContactPreviewView( Spacer(Modifier.fillMaxWidth().weight(1f)) - if (chat.chatInfo is ChatInfo.ContactRequest) { + if (chat.chatInfo is ChatInfo.ContactRequest || contactType == ContactType.CONTACT_WITH_REQUEST) { Icon( painterResource(MR.images.ic_check), contentDescription = null, - tint = MaterialTheme.colors.primary, + tint = textColor, modifier = Modifier .size(23.dp) ) @@ -103,6 +108,9 @@ fun ContactPreviewView( modifier = Modifier .size(21.dp) ) + if (chat.chatInfo.incognito) { + Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON)) + } } if (showDeletedChatIcon && chat.chatInfo.chatDeleted) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt index 6bfcf2809f..dc0a86b8fb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatModel @@ -253,7 +254,8 @@ class AlertManager { CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp) } } - } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) } } @@ -266,6 +268,88 @@ class AlertManager { hostDevice: Pair? = null, ) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm, hostDevice) + fun showOpenChatAlert( + profileName: String, + profileFullName: String, + profileImage: @Composable () -> Unit, + confirmText: String = generalGetString(MR.strings.connect_plan_open_chat), + onConfirm: () -> Unit, + dismissText: String = generalGetString(MR.strings.cancel_verb), + onDismiss: (() -> Unit)?, + ) { + showAlert { + AlertDialog( + onDismissRequest = { + onDismiss?.invoke() + hideAlert() + }, + buttons = { + AlertContent(text = null as String?, null) { + Column( + Modifier + .padding(top = DEFAULT_PADDING_HALF) + .width(360.dp), + verticalArrangement = Arrangement.SpaceEvenly + ) { + Column( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), + horizontalAlignment = Alignment.CenterHorizontally + ) { + profileImage() + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + Text( + profileName, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.h4, + lineHeight = 20.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + modifier = Modifier.fillMaxWidth() + ) + + if (profileFullName.isNotEmpty() && profileFullName != profileName) { + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + Text( + profileFullName, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.body2, + maxLines = 2, + modifier = Modifier.fillMaxWidth() + ) + } + } + + Column( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING_HALF).padding(top = DEFAULT_PADDING, bottom = 2.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + // Wait before focusing to prevent auto-confirming if a user used Enter key on hardware keyboard + delay(200) + focusRequester.requestFocus() + } + TextButton(onClick = { + onConfirm.invoke() + hideAlert() + }, Modifier.focusRequester(focusRequester)) { + Text(confirmText) + } + TextButton(onClick = { + onDismiss?.invoke() + hideAlert() + }) { + Text(dismissText) + } + } + } + } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) + ) + } + } + @Composable fun showInView() { alertViews.collectAsState().value.lastOrNull()?.invoke() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt index 078cdde9da..a55b1a0d56 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt @@ -2,12 +2,14 @@ package chat.simplex.common.views.helpers import androidx.compose.animation.core.* -fun chatListAnimationSpec() = tween(durationMillis = 250, easing = FastOutSlowInEasing) - -fun newChatSheetAnimSpec() = tween(256, 0, LinearEasing) +fun chatListAnimationSpec() = tween(durationMillis = 350, easing = FastOutSlowInEasing) fun audioProgressBarAnimationSpec() = tween(durationMillis = 30, easing = LinearEasing) -fun userPickerAnimSpec() = tween(256, 0, FastOutSlowInEasing) +fun userPickerAnimSpec() = tween(350, 0, FastOutSlowInEasing) -fun mentionPickerAnimSpec() = tween(256, 0, FastOutSlowInEasing) +fun mentionPickerAnimSpec() = tween(350, 0, FastOutSlowInEasing) + +fun commandMenuAnimSpec() = tween(350, 0, FastOutSlowInEasing) + +fun contextUserPickerAnimSpec() = tween(350, 0, FastOutSlowInEasing) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt index c3e97dd27b..72ef8c623d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt @@ -31,13 +31,9 @@ import kotlin.math.max fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, shadow: Boolean = false) { val icon = when (chatInfo) { - is ChatInfo.Group -> - when (chatInfo.groupInfo.businessChat?.chatType) { - BusinessChatType.Business -> MR.images.ic_work_filled_padded - BusinessChatType.Customer -> MR.images.ic_account_circle_filled - null -> MR.images.ic_supervised_user_circle_filled - } + is ChatInfo.Group -> chatInfo.groupInfo.chatIconName is ChatInfo.Local -> MR.images.ic_folder_filled + is ChatInfo.Direct -> chatInfo.contact.chatIconName else -> MR.images.ic_account_circle_filled } ProfileImage(size, chatInfo.image, icon, if (chatInfo is ChatInfo.Local) NoteFolderIconColor else iconColor) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index 7ed91adbd9..893ff5a467 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -46,7 +46,7 @@ fun ExposedDropDownSetting( horizontalArrangement = Arrangement.End ) { Text( - values.first { it.first == selection.value }.second + (if (label != null) " $label" else ""), + (values.firstOrNull { it.first == selection.value }?.second ?: "") + (if (label != null) " $label" else ""), Modifier.widthIn(max = maxWidth), maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -120,8 +120,10 @@ fun ExposedDropDownSettingWithIcon( ), contentAlignment = Alignment.Center ) { - val choice = values.first { it.first == selection.value } - Icon(painterResource(choice.second), choice.third, Modifier.padding(boxSize * iconPaddingPercent).fillMaxSize(), tint = iconColor) + val choice = values.firstOrNull { it.first == selection.value } + if (choice != null) { + Icon(painterResource(choice.second), choice.third, Modifier.padding(boxSize * iconPaddingPercent).fillMaxSize(), tint = iconColor) + } } DefaultExposedDropdownMenu( modifier = Modifier.widthIn(min = minWidth), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index af207d1381..21520f5424 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -85,7 +85,8 @@ class ModalData(val keyboardCoversBar: Boolean = true) { } enum class ModalViewId { - SECONDARY_CHAT + SECONDARY_CHAT, + CONTEXT_USER_PICKER_INCOGNITO } class ModalManager(private val placement: ModalPlacement? = null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt index 5fa097fb6b..3d21cfaf0f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt @@ -58,6 +58,7 @@ class ProcessedErrors (val interval: Long) { text = generalGetString(MR.strings.agent_internal_error_desc).format(error.internalErr), ) } + else -> {} } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index 0d188bb73c..7ee52af784 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -91,7 +91,7 @@ fun SectionViewSelectable( } } } - SectionTextFooter(values.first { it.value == currentValue.value }.description) + SectionTextFooter(values.firstOrNull { it.value == currentValue.value }?.description ?: AnnotatedString("")) } @Composable @@ -221,7 +221,7 @@ fun SectionItemWithValue( horizontalArrangement = Arrangement.End ) { Text( - values.first { it.value == currentValue.value }.title + (if (label != null) " $label" else ""), + (values.firstOrNull { it.value == currentValue.value }?.title ?: "") + (if (label != null) " $label" else ""), maxLines = 1, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colors.secondary diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 65e1864935..8021a605db 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -82,7 +82,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( } var profile: Profile? = null if (!displayName.isNullOrEmpty()) { - profile = Profile(displayName = displayName, fullName = "") + profile = Profile(displayName = displayName, fullName = "", shortDescr = null) } val createdUser = m.controller.apiCreateActiveUser(null, profile, pastTimestamp = true) m.currentUser.value = createdUser diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 3d913cf957..e8084e055a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -25,6 +25,7 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.* import chat.simplex.common.views.chat.group.GroupLinkView +import chat.simplex.common.views.chatlist.openGroupChat import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -44,9 +45,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c if (groupInfo != null) { withContext(Dispatchers.Main) { chatModel.chatsContext.updateGroup(rhId = rhId, groupInfo) - chatModel.chatsContext.chatItems.clearAndNotify() - chatModel.chatsContext.chatItemStatuses.clear() - chatModel.chatId.value = groupInfo.id + openGroupChat(rhId, groupInfo.groupId) } setGroupMembers(rhId, groupInfo, chatModel) closeAll.invoke() @@ -57,7 +56,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c } } else { ModalManager.end.showModalCloseable(true) { close -> - GroupLinkView(chatModel, rhId, groupInfo, connLinkContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close) + GroupLinkView(chatModel, rhId, groupInfo, groupLink = null, onGroupLinkUpdated = null, creatingGroup = true, close) } } } @@ -141,6 +140,7 @@ fun AddGroupLayout( createGroup(incognito.value, GroupProfile( displayName = displayName.value.trim(), fullName = "", + shortDescr = null, image = profileImage.value, groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON)) )) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 330c80b7a2..434cb6ce27 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -3,8 +3,11 @@ package chat.simplex.common.views.newchat import SectionItemView import androidx.compose.foundation.layout.* import androidx.compose.material.* +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.model.* import chat.simplex.common.platform.* @@ -20,11 +23,28 @@ enum class ConnectionLinkType { suspend fun planAndConnect( rhId: Long?, shortOrFullLink: String, - incognito: Boolean?, close: (() -> Unit)?, cleanup: (() -> Unit)? = null, filterKnownContact: ((Contact) -> Unit)? = null, filterKnownGroup: ((GroupInfo) -> Unit)? = null, +): CompletableDeferred { + connectProgressManager.cancelConnectProgress() + val inProgress = mutableStateOf(true) + connectProgressManager.startConnectProgress(generalGetString(MR.strings.loading_profile)) { + inProgress.value = false + cleanup?.invoke() + } + return planAndConnectTask(rhId, shortOrFullLink, close, cleanup, filterKnownContact, filterKnownGroup, inProgress) +} + +private suspend fun planAndConnectTask( + rhId: Long?, + shortOrFullLink: String, + close: (() -> Unit)?, + cleanup: (() -> Unit)? = null, + filterKnownContact: ((Contact) -> Unit)? = null, + filterKnownGroup: ((GroupInfo) -> Unit)? = null, + inProgress: MutableState ): CompletableDeferred { val completable = CompletableDeferred() val close: (() -> Unit) = { @@ -36,21 +56,30 @@ suspend fun planAndConnect( cleanup?.invoke() completable.complete(!completable.isActive) } - val result = chatModel.controller.apiConnectPlan(rhId, shortOrFullLink) + val result = chatModel.controller.apiConnectPlan(rhId, shortOrFullLink, inProgress = inProgress) + connectProgressManager.stopConnectProgress() + if (!inProgress.value) { return completable } if (result != null) { val (connectionLink, connectionPlan) = result val link = strHasSingleSimplexLink(shortOrFullLink.trim()) val linkText = if (link?.format is Format.SimplexLink) - "

${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}" + "

${link.format.simplexLinkText}" else "" when (connectionPlan) { is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) { - InvitationLinkPlan.Ok -> { - Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito") - if (incognito != null) { - connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) + is InvitationLinkPlan.Ok -> + if (connectionPlan.invitationLinkPlan.contactSLinkData_ != null) { + Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, short link data present") + showPrepareContactAlert( + rhId, + connectionLink, + connectionPlan.invitationLinkPlan.contactSLinkData_, + close, + cleanup + ) } else { + Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, no short link data") askCurrentOrIncognitoProfileAlert( chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_via_invitation_link), @@ -59,43 +88,24 @@ suspend fun planAndConnect( cleanup = cleanup, ) } - } InvitationLinkPlan.OwnLink -> { - Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink, incognito=$incognito") - if (incognito != null) { - AlertManager.privacySensitive.showAlertDialog( - title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, - confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, - onDismiss = cleanup, - onDismissRequest = cleanup, - destructive = true, - hostDevice = hostDevice(rhId), - ) - } else { - askCurrentOrIncognitoProfileAlert( - chatModel, rhId, connectionLink, connectionPlan, close, - title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, - connectDestructive = true, - cleanup = cleanup, - ) - } + Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink") + askCurrentOrIncognitoProfileAlert( + chatModel, rhId, connectionLink, connectionPlan, close, + title = generalGetString(MR.strings.connect_plan_connect_to_yourself), + text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, + connectDestructive = true, + cleanup = cleanup, + ) } is InvitationLinkPlan.Connecting -> { - Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito") + Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting") val contact = connectionPlan.invitationLinkPlan.contact_ if (contact != null) { if (filterKnownContact != null) { filterKnownContact(contact) } else { - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) + showOpenKnownContactAlert(chatModel, rhId, close, contact) cleanup() } } else { @@ -108,27 +118,29 @@ suspend fun planAndConnect( } } is InvitationLinkPlan.Known -> { - Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito") + Log.d(TAG, "planAndConnect, .InvitationLink, .Known") val contact = connectionPlan.invitationLinkPlan.contact if (filterKnownContact != null) { filterKnownContact(contact) } else { - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) + showOpenKnownContactAlert(chatModel, rhId, close, contact) cleanup() } } } is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) { - ContactAddressPlan.Ok -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito") - if (incognito != null) { - connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) + is ContactAddressPlan.Ok -> + if (connectionPlan.contactAddressPlan.contactSLinkData_ != null) { + Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, short link data present") + showPrepareContactAlert( + rhId, + connectionLink, + connectionPlan.contactAddressPlan.contactSLinkData_, + close, + cleanup + ) } else { + Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, no short link data") askCurrentOrIncognitoProfileAlert( chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_via_contact_link), @@ -137,109 +149,66 @@ suspend fun planAndConnect( cleanup, ) } - } ContactAddressPlan.OwnLink -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink, incognito=$incognito") - if (incognito != null) { - AlertManager.privacySensitive.showAlertDialog( - title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, - confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, - destructive = true, - onDismiss = cleanup, - onDismissRequest = cleanup, - hostDevice = hostDevice(rhId), - ) - } else { - askCurrentOrIncognitoProfileAlert( - chatModel, rhId, connectionLink, connectionPlan, close, - title = generalGetString(MR.strings.connect_plan_connect_to_yourself), - text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, - connectDestructive = true, - cleanup = cleanup, - ) - } + Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink") + askCurrentOrIncognitoProfileAlert( + chatModel, rhId, connectionLink, connectionPlan, close, + title = generalGetString(MR.strings.connect_plan_connect_to_yourself), + text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, + connectDestructive = true, + cleanup = cleanup, + ) } ContactAddressPlan.ConnectingConfirmReconnect -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect, incognito=$incognito") - if (incognito != null) { - AlertManager.privacySensitive.showAlertDialog( - title = generalGetString(MR.strings.connect_plan_repeat_connection_request), - text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, - confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, - onDismiss = cleanup, - onDismissRequest = cleanup, - destructive = true, - hostDevice = hostDevice(rhId), - ) - } else { - askCurrentOrIncognitoProfileAlert( - chatModel, rhId, connectionLink, connectionPlan, close, - title = generalGetString(MR.strings.connect_plan_repeat_connection_request), - text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, - connectDestructive = true, - cleanup = cleanup, - ) - } + Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect") + askCurrentOrIncognitoProfileAlert( + chatModel, rhId, connectionLink, connectionPlan, close, + title = generalGetString(MR.strings.connect_plan_repeat_connection_request), + text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, + connectDestructive = true, + cleanup = cleanup, + ) } is ContactAddressPlan.ConnectingProhibit -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito") + Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit") val contact = connectionPlan.contactAddressPlan.contact if (filterKnownContact != null) { filterKnownContact(contact) } else { - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) + showOpenKnownContactAlert(chatModel, rhId, close, contact) cleanup() } } is ContactAddressPlan.Known -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito") + Log.d(TAG, "planAndConnect, .ContactAddress, .Known") val contact = connectionPlan.contactAddressPlan.contact if (filterKnownContact != null) { filterKnownContact(contact) } else { - openKnownContact(chatModel, rhId, close, contact) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) + showOpenKnownContactAlert(chatModel, rhId, close, contact) cleanup() } } is ContactAddressPlan.ContactViaAddress -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress, incognito=$incognito") + Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress") val contact = connectionPlan.contactAddressPlan.contact - if (incognito != null) { - close() - connectContactViaAddress(chatModel, rhId, contact.contactId, incognito) - } else { - askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false) - } + askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false) cleanup() } } is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) { - GroupLinkPlan.Ok -> { - Log.d(TAG, "planAndConnect, .GroupLink, .Ok, incognito=$incognito") - if (incognito != null) { - AlertManager.privacySensitive.showAlertDialog( - title = generalGetString(MR.strings.connect_via_group_link), - text = generalGetString(MR.strings.you_will_join_group) + linkText, - confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, - onDismiss = cleanup, - onDismissRequest = cleanup, - hostDevice = hostDevice(rhId), + is GroupLinkPlan.Ok -> + if (connectionPlan.groupLinkPlan.groupSLinkData_ != null) { + Log.d(TAG, "planAndConnect, .GroupLink, .Ok, short link data present") + showPrepareGroupAlert( + rhId, + connectionLink, + connectionPlan.groupLinkPlan.groupSLinkData_, + close, + cleanup ) } else { + Log.d(TAG, "planAndConnect, .GroupLink, .Ok, no short link data") askCurrentOrIncognitoProfileAlert( chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_via_group_link), @@ -248,41 +217,27 @@ suspend fun planAndConnect( cleanup = cleanup, ) } - } is GroupLinkPlan.OwnLink -> { - Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito") + Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink") val groupInfo = connectionPlan.groupLinkPlan.groupInfo if (filterKnownGroup != null) { filterKnownGroup(groupInfo) } else { - ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, incognito, connectionPlan, groupInfo, close, cleanup) + ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, connectionPlan, groupInfo, close, cleanup) } } GroupLinkPlan.ConnectingConfirmReconnect -> { - Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito") - if (incognito != null) { - AlertManager.privacySensitive.showAlertDialog( - title = generalGetString(MR.strings.connect_plan_repeat_join_request), - text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, - confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, - onDismiss = cleanup, - onDismissRequest = cleanup, - destructive = true, - hostDevice = hostDevice(rhId), - ) - } else { - askCurrentOrIncognitoProfileAlert( - chatModel, rhId, connectionLink, connectionPlan, close, - title = generalGetString(MR.strings.connect_plan_repeat_join_request), - text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, - connectDestructive = true, - cleanup = cleanup, - ) - } + Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect") + askCurrentOrIncognitoProfileAlert( + chatModel, rhId, connectionLink, connectionPlan, close, + title = generalGetString(MR.strings.connect_plan_repeat_join_request), + text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, + connectDestructive = true, + cleanup = cleanup, + ) } is GroupLinkPlan.ConnectingProhibit -> { - Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito") + Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit") val groupInfo = connectionPlan.groupLinkPlan.groupInfo_ if (groupInfo != null) { if (groupInfo.businessChat == null) { @@ -306,43 +261,28 @@ suspend fun planAndConnect( cleanup() } is GroupLinkPlan.Known -> { - Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito") + Log.d(TAG, "planAndConnect, .GroupLink, .Known") val groupInfo = connectionPlan.groupLinkPlan.groupInfo if (filterKnownGroup != null) { filterKnownGroup(groupInfo) } else { - openKnownGroup(chatModel, rhId, close, groupInfo) - if (groupInfo.businessChat == null) { - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.connect_plan_group_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) - } else { - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.connect_plan_chat_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_connected_with_vName), groupInfo.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) - } + showOpenKnownGroupAlert(chatModel, rhId, close, groupInfo) cleanup() } } } is ConnectionPlan.Error -> { Log.d(TAG, "planAndConnect, error ${connectionPlan.chatError}") - if (incognito != null) { - connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan = null, close, cleanup) - } else { - askCurrentOrIncognitoProfileAlert( - chatModel, rhId, connectionLink, connectionPlan = null, close, - title = generalGetString(MR.strings.connect_plan_connect_via_link), - connectDestructive = false, - cleanup = cleanup, - ) - } + askCurrentOrIncognitoProfileAlert( + chatModel, rhId, connectionLink, connectionPlan = null, close, + title = generalGetString(MR.strings.connect_plan_connect_via_link), + connectDestructive = false, + cleanup = cleanup, + ) } } + } else { + cleanup() } return completable } @@ -433,6 +373,34 @@ fun askCurrentOrIncognitoProfileAlert( ) } +fun openChat_(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, chat: Chat) { + withBGApi { + close?.invoke() + openChat(secondaryChatsCtx = null, rhId, chat.chatInfo) + } +} + +val alertProfileImageSize = 138.dp + +private fun showOpenKnownContactAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) { + AlertManager.privacySensitive.showOpenChatAlert( + profileName = contact.profile.displayName, + profileFullName = contact.profile.fullName, + profileImage = { + ProfileImage( + size = alertProfileImageSize, + image = contact.profile.image, + icon = contact.chatIconName + ) + }, + confirmText = generalGetString(if (contact.nextConnectPrepared) MR.strings.connect_plan_open_new_chat else MR.strings.connect_plan_open_chat), + onConfirm = { + openKnownContact(chatModel, rhId, close, contact) + }, + onDismiss = null + ) +} + fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) { withBGApi { val c = chatModel.getContactChat(contact.contactId) @@ -448,7 +416,6 @@ fun ownGroupLinkConfirmConnect( rhId: Long?, connectionLink: CreatedConnLink, linkText: String, - incognito: Boolean?, connectionPlan: ConnectionPlan?, groupInfo: GroupInfo, close: (() -> Unit)?, @@ -467,38 +434,23 @@ fun ownGroupLinkConfirmConnect( }) { Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } - if (incognito != null) { - // Join incognito / Join with current profile - SectionItemView({ - AlertManager.privacySensitive.hideAlert() - withBGApi { - connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) - } - }) { - Text( - if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error - ) + // Use current profile + SectionItemView({ + AlertManager.privacySensitive.hideAlert() + withBGApi { + connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup) } - } else { - // Use current profile - SectionItemView({ - AlertManager.privacySensitive.hideAlert() - withBGApi { - connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup) - } - }) { - Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) - } - // Use new incognito profile - SectionItemView({ - AlertManager.privacySensitive.hideAlert() - withBGApi { - connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup) - } - }) { - Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) + }) { + Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) + } + // Use new incognito profile + SectionItemView({ + AlertManager.privacySensitive.hideAlert() + withBGApi { + connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup) } + }) { + Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) } // Cancel SectionItemView({ @@ -514,6 +466,31 @@ fun ownGroupLinkConfirmConnect( ) } +private fun showOpenKnownGroupAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) { + AlertManager.privacySensitive.showOpenChatAlert( + profileName = groupInfo.groupProfile.displayName, + profileFullName = groupInfo.groupProfile.fullName, + profileImage = { + ProfileImage( + size = alertProfileImageSize, + image = groupInfo.groupProfile.image, + icon = groupInfo.chatIconName + ) + }, + confirmText = generalGetString( + if (groupInfo.businessChat == null) { + if (groupInfo.nextConnectPrepared) MR.strings.connect_plan_open_new_group else MR.strings.connect_plan_open_group + } else { + if (groupInfo.nextConnectPrepared) MR.strings.connect_plan_open_new_chat else MR.strings.connect_plan_open_chat + } + ), + onConfirm = { + openKnownGroup(chatModel, rhId, close, groupInfo) + }, + onDismiss = null + ) +} + fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) { withBGApi { val g = chatModel.getGroupChat(groupInfo.groupId) @@ -523,3 +500,74 @@ fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, grou } } } + +fun showPrepareContactAlert( + rhId: Long?, + connectionLink: CreatedConnLink, + contactShortLinkData: ContactShortLinkData, + close: (() -> Unit)?, + cleanup: (() -> Unit)? +) { + AlertManager.privacySensitive.showOpenChatAlert( + profileName = contactShortLinkData.profile.displayName, + profileFullName = contactShortLinkData.profile.fullName, + profileImage = { + ProfileImage( + size = alertProfileImageSize, + image = contactShortLinkData.profile.image, + icon = + if (contactShortLinkData.business) MR.images.ic_work_filled_padded + else if (contactShortLinkData.profile.peerType == ChatPeerType.Bot) MR.images.ic_cube + else MR.images.ic_account_circle_filled + ) + }, + confirmText = generalGetString(MR.strings.connect_plan_open_new_chat), + onConfirm = { + AlertManager.privacySensitive.hideAlert() + withBGApi { + val chat = chatModel.controller.apiPrepareContact(rhId, connectionLink, contactShortLinkData) + if (chat != null) { + withContext(Dispatchers.Main) { + ChatController.chatModel.chatsContext.addChat(chat) + openChat_(chatModel, rhId, close, chat) + } + } + cleanup?.invoke() + } + }, + onDismiss = { + cleanup?.invoke() + } + ) +} + +fun showPrepareGroupAlert( + rhId: Long?, + connectionLink: CreatedConnLink, + groupShortLinkData: GroupShortLinkData, + close: (() -> Unit)?, + cleanup: (() -> Unit)? +) { + AlertManager.privacySensitive.showOpenChatAlert( + profileName = groupShortLinkData.groupProfile.displayName, + profileFullName = groupShortLinkData.groupProfile.fullName, + profileImage = { ProfileImage(size = alertProfileImageSize, image = groupShortLinkData.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled) }, + confirmText = generalGetString(MR.strings.connect_plan_open_new_group), + onConfirm = { + AlertManager.privacySensitive.hideAlert() + withBGApi { + val chat = chatModel.controller.apiPrepareGroup(rhId, connectionLink, groupShortLinkData) + if (chat != null) { + withContext(Dispatchers.Main) { + ChatController.chatModel.chatsContext.addChat(chat) + openChat_(chatModel, rhId, close, chat) + } + } + cleanup?.invoke() + } + }, + onDismiss = { + cleanup?.invoke() + } + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index cb4991c99f..ef6e426141 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -29,6 +29,7 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.CIFileViewScope import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.contacts.* @@ -40,6 +41,12 @@ import kotlinx.coroutines.flow.filter @Composable fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) { + DisposableEffect(Unit) { + onDispose { + connectProgressManager.cancelConnectProgress() + } + } + val oneHandUI = remember { appPrefs.oneHandUI.state } Box { @@ -73,7 +80,7 @@ fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) { } enum class ContactType { - CARD, REQUEST, RECENT, CHAT_DELETED, UNLISTED + CARD, CONTACT_WITH_REQUEST, REQUEST, RECENT, CHAT_DELETED, UNLISTED } fun chatContactType(chat: Chat): ContactType { @@ -81,9 +88,9 @@ fun chatContactType(chat: Chat): ContactType { is ChatInfo.ContactRequest -> ContactType.REQUEST is ChatInfo.Direct -> { val contact = cInfo.contact - when { - contact.activeConn == null && contact.profile.contactLink != null && contact.active -> ContactType.CARD + contact.nextAcceptContactRequest -> ContactType.CONTACT_WITH_REQUEST + contact.isContactCard -> ContactType.CARD contact.chatDeleted -> ContactType.CHAT_DELETED contact.contactStatus == ContactStatus.Active -> ContactType.RECENT else -> ContactType.UNLISTED @@ -127,7 +134,7 @@ private fun ModalData.NewChatSheetLayout( val searchShowingSimplexLink = remember { mutableStateOf(false) } val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } val showUnreadAndFavorites = remember { ChatController.appPrefs.showUnreadAndFavorites.state }.value - val baseContactTypes = remember { listOf(ContactType.CARD, ContactType.RECENT, ContactType.REQUEST) } + val baseContactTypes = remember { listOf(ContactType.CARD, ContactType.CONTACT_WITH_REQUEST, ContactType.REQUEST, ContactType.RECENT) } val contactTypes by remember(searchText.value.text.isEmpty()) { derivedStateOf { contactTypesSearchTargets(baseContactTypes, searchText.value.text.isEmpty()) } } @@ -472,6 +479,13 @@ private fun ContactsSearchBar( ) { searchText.value = searchText.value.copy(it) } + + if (connectProgressManager.showConnectProgress != null) { + Box(Modifier.padding(end = DEFAULT_PADDING_HALF)) { + CIFileViewScope.progressIndicator(sizeMultiplier = 0.75f) + } + } + val hasText = remember { derivedStateOf { searchText.value.text.isNotEmpty() } } if (hasText.value) { val hideSearchOnBack: () -> Unit = { searchText.value = TextFieldValue() } @@ -505,10 +519,8 @@ private fun ContactsSearchBar( // if SimpleX link is pasted, show connection dialogue hideKeyboard(view) if (link.format is Format.SimplexLink) { - val linkText = - link.simplexLinkText(link.format.linkType, link.format.smpHosts) - searchText.value = - searchText.value.copy(linkText, selection = TextRange.Zero) + val linkText = link.format.simplexLinkText + searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero) } searchShowingSimplexLink.value = true searchChatFilteredBySimplexLink.value = null @@ -522,8 +534,11 @@ private fun ContactsSearchBar( if (it.isNotEmpty()) { // if some other text is pasted, enter search mode focusRequester.requestFocus() - } else if (listState.layoutInfo.totalItemsCount > 0) { - listState.scrollToItem(0) + } else { + connectProgressManager.cancelConnectProgress() + if (listState.layoutInfo.totalItemsCount > 0) { + listState.scrollToItem(0) + } } searchShowingSimplexLink.value = false searchChatFilteredBySimplexLink.value = null @@ -557,7 +572,6 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState< planAndConnect( chatModel.remoteHostId(), link, - incognito = null, filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, close = close, cleanup = cleanup, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 1b3138d21c..f520a86999 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.* import androidx.compose.runtime.* @@ -34,6 +35,7 @@ import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.CIFileViewScope import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* @@ -379,14 +381,7 @@ fun ActiveProfilePicker( } }, image = { - Spacer(Modifier.width(8.dp)) - Icon( - painterResource(MR.images.ic_theater_comedy_filled), - contentDescription = stringResource(MR.strings.incognito), - Modifier.size(32.dp), - tint = Indigo, - ) - Spacer(Modifier.width(2.dp)) + IncognitoOptionImage() }, onInfo = { ModalManager.start.showModal { IncognitoView() } }, ) @@ -409,7 +404,10 @@ fun ActiveProfilePicker( val activeProfile = filteredProfiles.firstOrNull { it.activeUser } if (activeProfile != null) { - val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId } + val otherProfiles = + filteredProfiles + .filter { it.userId != activeProfile.userId } + .sortedByDescending { it.activeOrder } item { when { !showIncognito -> @@ -537,14 +535,28 @@ private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contact @Composable fun ToggleShortLinkButton(short: MutableState) { - Text( - stringResource(if (short.value) MR.strings.full_link_button_text else MR.strings.short_link_button_text), - modifier = Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null - ) { short.value = !short.value }, - style = MaterialTheme.typography.body2, fontSize = 14.sp, color = MaterialTheme.colors.primary + if (appPrefs.developerTools.state.value) { + Text( + stringResource(if (short.value) MR.strings.full_link_button_text else MR.strings.short_link_button_text), + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { short.value = !short.value }, + style = MaterialTheme.typography.body2, fontSize = 14.sp, color = MaterialTheme.colors.primary + ) + } +} + +@Composable +fun IncognitoOptionImage() { + Spacer(Modifier.width(8.dp)) + Icon( + painterResource(MR.images.ic_theater_comedy_filled), + contentDescription = stringResource(MR.strings.incognito), + Modifier.size(32.dp), + tint = Indigo, ) + Spacer(Modifier.width(2.dp)) } @Composable @@ -566,6 +578,12 @@ fun AddContactLearnMoreButton() { @Composable private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState, pastedLink: MutableState, close: () -> Unit) { + DisposableEffect(Unit) { + onDispose { + connectProgressManager.cancelConnectProgress() + } + } + SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 5.dp) { PasteLinkView(rhId, pastedLink, showQRCodeScanner, close) } @@ -606,10 +624,25 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState, showQRC ) } }) { - Text(stringResource(MR.strings.tap_to_paste_link)) + Box(Modifier.weight(1f)) { + Text(stringResource(MR.strings.tap_to_paste_link)) + } + if (connectProgressManager.showConnectProgress != null) { + CIFileViewScope.progressIndicator(sizeMultiplier = 0.6f) + } } } else { - LinkTextView(pastedLink.value, false) + Row( + Modifier.padding(end = DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically + ) { + Box(Modifier.weight(1f)) { + LinkTextView(pastedLink.value, false) + } + if (connectProgressManager.showConnectProgress != null) { + CIFileViewScope.progressIndicator(sizeMultiplier = 0.6f) + } + } } } @@ -687,8 +720,7 @@ private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanu rhId, link, close = close, - cleanup = cleanup, - incognito = null + cleanup = cleanup ).await() private fun createInvitation( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index bacb5ab802..74c9a55ecf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -31,6 +31,7 @@ fun SimpleXCreatedLinkQRCode( ) { QRCode( connLink.simplexChatUri(short), + small = short && connLink.connShortLink != null, modifier, padding, tintColor, @@ -50,6 +51,7 @@ fun SimpleXLinkQRCode( ) { QRCode( simplexChatLink(connReq), + small = connReq.count() < 200, modifier, padding, tintColor, @@ -61,6 +63,7 @@ fun SimpleXLinkQRCode( @Composable fun QRCode( connReq: String, + small: Boolean = false, modifier: Modifier = Modifier, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), tintColor: Color = Color(0xff062d56), @@ -68,9 +71,11 @@ fun QRCode( onShare: (() -> Unit)? = null, ) { val scope = rememberCoroutineScope() + val logoSize = if (small) 0.21f else 0.16f + val errorLevel = if (small) QrCode.ErrorLevel.M else QrCode.ErrorLevel.L val qr = remember(connReq, tintColor, withLogo) { - qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } + qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo(logoSize) else it } } Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Image( @@ -79,12 +84,13 @@ fun QRCode( Modifier .padding(padding) .widthIn(max = 400.dp) + .fillMaxWidth(if (small) 0.63f else 1f) .aspectRatio(1f) .then(modifier) .clickable { scope.launch { - val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } + val image = qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo(logoSize) else it } val file = saveTempImageUncompressed(image, true) if (file != null) { shareFile("", CryptoFile.plain(file.absolutePath)) @@ -96,8 +102,8 @@ fun QRCode( } } -fun qrCodeBitmap(content: String, size: Int = 1024): ImageBitmap { - val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() +fun qrCodeBitmap(content: String, size: Int = 1024, errorLevel: QrCode.ErrorLevel): ImageBitmap { + val qrCode = QrCodeEncoder().addAutomatic(content).setError(errorLevel).fixate() /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ val numModules = QrCode.totalModules(qrCode.version) // Hide border on light themes to make it fit to the same place as camera in QRCodeScanner. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 52eea3dd9d..f64f1dcecd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -15,6 +15,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatController.appPrefs @@ -24,8 +26,11 @@ import chat.simplex.common.model.ChatController.setConditionsNotified import chat.simplex.common.model.ServerOperator.Companion.dummyOperatorInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.CIFileViewScope import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.UserAddressView import chat.simplex.common.views.usersettings.networkAndServers.UsageConditionsView +import chat.simplex.common.views.usersettings.showAddShortLinkAlert import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @@ -817,7 +822,64 @@ private val versionDescriptions: List = listOf( ) ), ) - ) + ), + VersionDescription( + version = "v6.4", + post = "https://simplex.chat/blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.html", + features = listOf( + VersionFeature.FeatureDescription( + icon = MR.images.ic_person, + titleId = MR.strings.v6_4_connect_faster, + descrId = MR.strings.v6_4_connect_faster_descr + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_chat_person, + titleId = MR.strings.v6_4_review_members, + descrId = MR.strings.v6_4_review_members_descr + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_contact_support, + titleId = MR.strings.v6_4_support_chat, + descrId = MR.strings.v6_4_support_chat_descr + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_flag, + titleId = MR.strings.v6_4_role_moderator, + descrId = MR.strings.v6_4_role_moderator_descr + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_battery_3_bar, + titleId = MR.strings.v5_8_message_delivery, + descrId = MR.strings.v6_4_message_delivery_descr + ), + ) + ), + VersionDescription( + version = "v6.4.1", + post = "https://simplex.chat/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.html", + features = listOf( + VersionFeature.FeatureDescription( + icon = MR.images.ic_waving_hand, + titleId = MR.strings.v6_4_1_welcome_contacts, + descrId = MR.strings.v6_4_1_welcome_contacts_descr + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_timer, + titleId = MR.strings.v6_4_1_keep_chats_clean, + descrId = MR.strings.v6_4_1_keep_chats_clean_descr + ), + VersionFeature.FeatureView( + icon = null, + titleId = MR.strings.v6_4_1_short_address, + view = { modalManager -> CreateUpdateAddressShortLinkView(modalManager) } + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_translate, + titleId = MR.strings.v6_4_1_new_interface_languages, + descrId = MR.strings.v6_4_1_new_interface_languages_descr, + ), + ) + ), ) private val lastVersion = versionDescriptions.last().version @@ -834,6 +896,85 @@ fun shouldShowWhatsNew(m: ChatModel): Boolean { return v != lastVersion } +@Composable +fun CreateUpdateAddressShortLinkView(modalManager: ModalManager) { + val clipboard = LocalClipboardManager.current + val progressIndicator = remember { mutableStateOf(false) } + + fun share(userAddress: String) { clipboard.shareText(userAddress) } + + Column(modifier = Modifier.padding(bottom = 12.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(bottom = 4.dp) + ) { + Icon(painterResource(MR.images.ic_link), stringResource(MR.strings.v6_4_1_short_address), tint = MaterialTheme.colors.secondary) + Text( + generalGetString(MR.strings.v6_4_1_short_address), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 6.dp) + ) + } + val addr = chatModel.userAddress.value + if (addr != null) { + if (addr.shouldBeUpgraded) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + stringResource(MR.strings.v6_4_1_short_address_update), + color = MaterialTheme.colors.primary, + fontSize = 15.sp, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + showAddShortLinkAlert(progressIndicator = progressIndicator, share = ::share) + } + ) + if (progressIndicator.value) { + CIFileViewScope.progressIndicator(sizeMultiplier = 0.5f) + } + } + } else { + Text( + stringResource(MR.strings.v6_4_1_short_address_share), + color = MaterialTheme.colors.primary, + fontSize = 15.sp, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + share(addr.connLinkContact.simplexChatUri(short = true)) + } + ) + } + } else { + Text( + stringResource(MR.strings.v6_4_1_short_address_create), + color = MaterialTheme.colors.primary, + fontSize = 15.sp, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + modalManager.showModalCloseable { close -> + UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = close) + } + } + ) + } + } +} + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 7a1fc21b17..e24c09afd0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -195,7 +195,9 @@ object AppearanceScope { ) ) } - SettingsPreferenceItem(icon = null, stringResource(MR.strings.settings_message_shape_tail), appPreferences.chatItemTail) + if (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) > 27) { + SettingsPreferenceItem(icon = null, stringResource(MR.strings.settings_message_shape_tail), appPreferences.chatItemTail) + } } } } @@ -1104,6 +1106,7 @@ object AppearanceScope { "fi" to "Suomi", "fr" to "Français", "hu" to "Magyar", + "in" to "Indonesia", "it" to "Italiano", "iw" to "עִברִית", "ja" to "日本語", @@ -1111,10 +1114,12 @@ object AppearanceScope { "nl" to "Nederlands", "pl" to "Polski", "pt-BR" to "Português, Brasil", + "ro" to "Română", "ru" to "Русский", "th" to "ภาษาไทย", "tr" to "Türkçe", "uk" to "Українська", + "vi" to "Tiếng Việt", "zh-CN" to "简体中文" ) val values by remember(appPrefs.appLanguage.state.value) { mutableStateOf(supportedLanguages.map { it.key to it.value }) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index c5a4ae5f70..dcb71a552d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -2,18 +2,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced -import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler -import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import dev.icerock.moko.resources.compose.painterResource @@ -67,7 +59,15 @@ fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) -> SettingsPreferenceItem(painterResource(MR.images.ic_avg_pace), stringResource(MR.strings.show_slow_api_calls), appPreferences.showSlowApiCalls) } } - SectionBottomSpacer() + SectionDividerSpaced(maxTopPadding = true) + SectionView(stringResource(MR.strings.deprecated_options_section).uppercase()) { + val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode + SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = { + simplexLinkMode.set(it) + chatModel.simplexLinkMode.value = it + }) + SectionBottomSpacer() + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt index 5af5d5fb90..2fc427cd2e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt @@ -4,10 +4,8 @@ import SectionBottomSpacer import SectionTextFooter import SectionView import SectionViewSelectable -import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.capitalize @@ -15,7 +13,6 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import chat.simplex.common.model.* import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlin.collections.ArrayList @@ -62,7 +59,7 @@ fun NotificationsSettingsLayout( if (appPlatform == AppPlatform.ANDROID) { SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notifications_mode_title), { showPage(CurrentPage.NOTIFICATIONS_MODE) }) { Text( - modes.first { it.value == notificationsMode.value }.title, + modes.firstOrNull { it.value == notificationsMode.value }?.title ?: "", maxLines = 1, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colors.secondary @@ -71,7 +68,7 @@ fun NotificationsSettingsLayout( } SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notification_preview_mode_title), { showPage(CurrentPage.NOTIFICATION_PREVIEW_MODE) }) { Text( - previewModes.first { it.value == notificationPreviewMode.value }.title, + previewModes.firstOrNull { it.value == notificationPreviewMode.value }?.title ?: "", maxLines = 1, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colors.secondary diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index 72fa45b936..fe9137ee35 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -69,9 +69,18 @@ private fun PreferencesLayout( ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_preferences)) val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.allow) } - TimedMessagesFeatureSection(timedMessages) { - applyPrefs(preferences.copy(timedMessages = TimedMessagesPreference(allow = if (it) FeatureAllowed.YES else FeatureAllowed.NO))) + val onTTLUpdated = { ttl: Int? -> + applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl))) } + TimedMessagesFeatureSection( + preferences, + timedMessages, + onSelected = { + applyPrefs(preferences.copy(timedMessages = TimedMessagesPreference(allow = if (it) FeatureAllowed.YES else FeatureAllowed.NO))) + }, + onTTLUpdated = onTTLUpdated + ) + SectionDividerSpaced(true, maxBottomPadding = false) val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.allow) } FeatureSection(ChatFeature.FullDelete, allowFullDeletion) { @@ -117,7 +126,13 @@ private fun FeatureSection(feature: ChatFeature, allowFeature: State, onSelected: (Boolean) -> Unit) { +private fun TimedMessagesFeatureSection( + preferences: FullChatPreferences, + allowFeature: State, + onSelected: (Boolean) -> Unit, + onTTLUpdated: (Int?) -> Unit +) { + val ttl = rememberSaveable(preferences) { mutableStateOf(preferences.timedMessages.ttl) } SectionView { PreferenceToggleWithIcon( ChatFeature.TimedMessages.text, @@ -127,8 +142,23 @@ private fun TimedMessagesFeatureSection(allowFeature: State, onS extraPadding = false, onChange = onSelected ) + if (allowFeature.value == FeatureAllowed.ALWAYS || allowFeature.value == FeatureAllowed.YES) { + ExposedDropDownSettingRow( + generalGetString(MR.strings.delete_after), + TimedMessagesPreference.profileLevelTTLValues.map { v -> v to timeText(v) }, + ttl, + icon = null, + onSelected = onTTLUpdated + ) + } } - SectionTextFooter(ChatFeature.TimedMessages.allowDescription(allowFeature.value)) + SectionTextFooter( + if ((allowFeature.value == FeatureAllowed.ALWAYS || allowFeature.value == FeatureAllowed.YES) && ttl.value != null) { + ChatFeature.TimedMessages.allowDescription(allowFeature.value) + "\n" + generalGetString(MR.strings.time_to_disappear_is_set_only_for_new_contacts) + } else { + ChatFeature.TimedMessages.allowDescription(allowFeature.value) + } + ) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 569f4ff5f8..2771b5ac62 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -56,16 +56,22 @@ fun PrivacySettingsView( setPerformLA: (Boolean) -> Unit ) { ColumnWithScrollBar { - val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode AppBarTitle(stringResource(MR.strings.your_privacy)) PrivacyDeviceSection(showSettingsModal, setPerformLA) SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_chats)) { - SettingsPreferenceItem(painterResource(MR.images.ic_travel_explore), stringResource(MR.strings.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews) - ChatListLinksOptions(appPrefs.privacyChatListOpenLinks.state, onSelected = { - appPrefs.privacyChatListOpenLinks.set(it) - }) + SettingsPreferenceItem( + painterResource(MR.images.ic_travel_explore), + stringResource(MR.strings.send_link_previews), + chatModel.controller.appPrefs.privacyLinkPreviews, + onChange = { _ -> chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.set(false) } // to avoid showing alert to current users, show alert in v6.5 + ) + SettingsPreferenceItem( + painterResource(MR.images.ic_link), + stringResource(MR.strings.sanitize_links_toggle), + chatModel.controller.appPrefs.privacySanitizeLinks + ) SettingsPreferenceItem( painterResource(MR.images.ic_chat_bubble), stringResource(MR.strings.privacy_show_last_messages), @@ -84,17 +90,6 @@ fun PrivacySettingsView( chatModel.draftChatId.value = null } }) - SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = { - simplexLinkMode.set(it) - chatModel.simplexLinkMode.value = it - }) - if (appPrefs.developerTools.get()) { - SettingsPreferenceItem( - null, - stringResource(MR.strings.privacy_short_links), - chatModel.controller.appPrefs.privacyShortLinks - ) - } } SectionDividerSpaced() @@ -168,7 +163,22 @@ fun PrivacySettingsView( } } + fun setAutoAcceptGrpDirectInvs(enable: Boolean) { + withApi { + chatModel.controller.apiSetUserAutoAcceptMemberContacts(currentUser, enable) + chatModel.currentUser.value = currentUser.copy(autoAcceptMemberContacts = enable) + } + } + if (!chatModel.desktopNoUserNoRemote) { + SectionDividerSpaced(maxTopPadding = true) + ContacRequestsFromGroupsSection( + currentUser = currentUser, + setAutoAcceptGrpDirectInvs = { enable -> + setAutoAcceptGrpDirectInvs(enable) + } + ) + SectionDividerSpaced(maxTopPadding = true) DeliveryReceiptsSection( currentUser = currentUser, @@ -210,27 +220,7 @@ fun PrivacySettingsView( } @Composable -private fun ChatListLinksOptions(state: State, onSelected: (PrivacyChatListOpenLinksMode) -> Unit) { - val values = remember { - PrivacyChatListOpenLinksMode.entries.map { - when (it) { - PrivacyChatListOpenLinksMode.YES -> it to generalGetString(MR.strings.privacy_chat_list_open_links_yes) - PrivacyChatListOpenLinksMode.NO -> it to generalGetString(MR.strings.privacy_chat_list_open_links_no) - PrivacyChatListOpenLinksMode.ASK -> it to generalGetString(MR.strings.privacy_chat_list_open_links_ask) - } - } - } - ExposedDropDownSettingRow( - generalGetString(MR.strings.privacy_chat_list_open_links), - values, - state, - icon = painterResource(MR.images.ic_open_in_new), - onSelected = onSelected - ) -} - -@Composable -private fun SimpleXLinkOptions(simplexLinkModeState: State, onSelected: (SimplexLinkMode) -> Unit) { +fun SimpleXLinkOptions(simplexLinkModeState: State, onSelected: (SimplexLinkMode) -> Unit) { val modeValues = listOf(SimplexLinkMode.DESCRIPTION, SimplexLinkMode.FULL) val pickerValues = modeValues + if (modeValues.contains(simplexLinkModeState.value)) emptyList() else listOf(simplexLinkModeState.value) val values = remember { @@ -282,6 +272,34 @@ expect fun PrivacyDeviceSection( setPerformLA: (Boolean) -> Unit, ) +@Composable +private fun ContacRequestsFromGroupsSection( + currentUser: User, + setAutoAcceptGrpDirectInvs: (Boolean) -> Unit +) { + SectionView(stringResource(MR.strings.settings_section_title_contact_requests_from_groups)) { + SettingsActionItemWithContent(painterResource(MR.images.ic_check), stringResource(MR.strings.auto_accept_contact)) { + DefaultSwitch( + checked = currentUser.autoAcceptMemberContacts, + onCheckedChange = { enable -> + setAutoAcceptGrpDirectInvs(enable) + } + ) + } + } + SectionTextFooter( + remember(currentUser.displayName) { + buildAnnotatedString { + append(generalGetString(MR.strings.this_setting_is_for_your_current_profile) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentUser.displayName) + } + append(".") + } + } + ) +} + @Composable private fun DeliveryReceiptsSection( currentUser: User, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 8c7c2d8416..3b6cf34b7c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -16,16 +16,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.TextAlign import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.chat.ShareAddressButton import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel import chat.simplex.common.model.MsgContent import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR @@ -38,34 +39,41 @@ fun UserAddressView( ) { // TODO close when remote host changes val shareViaProfile = remember { mutableStateOf(shareViaProfile) } - var progressIndicator by remember { mutableStateOf(false) } + val progressIndicator = remember { mutableStateOf(false) } val user = remember { chatModel.currentUser } + val clipboard = LocalClipboardManager.current KeyChangeEffect(user.value?.remoteHostId, user.value?.userId) { close() } + fun setProfileAddress(on: Boolean) { - progressIndicator = true + progressIndicator.value = true withBGApi { try { - val u = chatModel.controller.apiSetProfileAddress(user?.value?.remoteHostId, on) + val u = chatModel.controller.apiSetProfileAddress(user.value?.remoteHostId, on) if (u != null) { chatModel.updateUser(u) } } catch (e: Exception) { Log.e(TAG, "UserAddressView apiSetProfileAddress: ${e.stackTraceToString()}") } finally { - progressIndicator = false + progressIndicator.value = false } } } fun createAddress() { withBGApi { - progressIndicator = true - val short = appPreferences.privacyShortLinks.get() - val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId, short = short) + progressIndicator.value = true + val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId) if (connReqContact != null) { - chatModel.userAddress.value = UserContactLinkRec(connReqContact) + val slDataSet = connReqContact.connShortLink != null + chatModel.userAddress.value = UserContactLinkRec( + connReqContact, + shortLinkDataSet = slDataSet, + shortLinkLargeDataSet = slDataSet, + addressSettings = AddressSettings(businessAddress = false, autoAccept = null, autoReply = null) + ) AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.share_address_with_contacts_question), @@ -77,30 +85,34 @@ fun UserAddressView( } ) } - progressIndicator = false + progressIndicator.value = false } } + fun share(userAddress: String) { clipboard.shareText(userAddress) } + LaunchedEffect(autoCreateAddress) { if (chatModel.userAddress.value == null && autoCreateAddress) { createAddress() } } val userAddress = remember { chatModel.userAddress } - val clipboard = LocalClipboardManager.current val uriHandler = LocalUriHandler.current val showLayout = @Composable { UserAddressLayout( user = user.value, userAddress = userAddress.value, shareViaProfile, - createAddress = { createAddress() }, + createAddress = ::createAddress, + showAddShortLinkAlert = { shareAddress: (() -> Unit)? -> + showAddShortLinkAlert(progressIndicator = progressIndicator, share = ::share, shareAddress = shareAddress) + }, learnMore = { ModalManager.start.showModal { UserAddressLearnMore() } }, - share = { userAddress: String -> clipboard.shareText(userAddress) }, + share = ::share, sendEmail = { userAddress -> uriHandler.sendEmail( generalGetString(MR.strings.email_invite_subject), @@ -114,26 +126,26 @@ fun UserAddressView( text = if (shareViaProfile.value) generalGetString(MR.strings.all_your_contacts_will_remain_connected_update_sent) else generalGetString(MR.strings.all_your_contacts_will_remain_connected), confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { - progressIndicator = true + progressIndicator.value = true withBGApi { - val u = chatModel.controller.apiDeleteUserAddress(user?.value?.remoteHostId) + val u = chatModel.controller.apiDeleteUserAddress(user.value?.remoteHostId) if (u != null) { chatModel.userAddress.value = null chatModel.updateUser(u) shareViaProfile.value = false - progressIndicator = false + progressIndicator.value = false } } }, destructive = true, ) }, - saveAas = { aas: AutoAcceptState, savedAAS: MutableState -> + saveAddressSettings = { settings: AddressSettingsState, savedSettings: MutableState -> withBGApi { - val address = chatModel.controller.userAddressAutoAccept(user?.value?.remoteHostId, aas.autoAccept) + val address = chatModel.controller.apiSetUserAddressSettings(user.value?.remoteHostId, settings.addressSettings) if (address != null) { chatModel.userAddress.value = address - savedAAS.value = aas + savedSettings.value = settings } } }, @@ -144,7 +156,7 @@ fun UserAddressView( showLayout() } - if (progressIndicator) { + if (progressIndicator.value) { Box( Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -163,18 +175,88 @@ fun UserAddressView( } } +private fun addShortLink( + progressIndicator: MutableState, + share: (String) -> Unit, + shareOnCompletion: Boolean = false +) { + withBGApi { + progressIndicator.value = true + val userAddress = chatModel.controller.apiAddMyAddressShortLink(chatModel.currentUser.value?.remoteHostId) + if (userAddress != null) { + chatModel.userAddress.value = userAddress + if (shareOnCompletion) { + share(userAddress.connLinkContact.simplexChatUri(short = true)) + } + } + progressIndicator.value = false + } +} + +fun showAddShortLinkAlert( + progressIndicator: MutableState, + share: (String) -> Unit, + shareAddress: (() -> Unit)? = null +) { + AlertManager.shared.showAlertDialogButtonsColumn( + title = generalGetString(MR.strings.share_profile_via_link), + text = generalGetString(MR.strings.share_profile_via_link_alert_text), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + addShortLink(progressIndicator = progressIndicator, share = share, shareOnCompletion = shareAddress != null) + }) { + Text( + generalGetString(MR.strings.share_profile_via_link_alert_confirm), + Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary + ) + } + + if (shareAddress != null) { + SectionItemView({ + AlertManager.shared.hideAlert() + shareAddress() + }) { + Text( + generalGetString(MR.strings.share_old_address_alert_button), + Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary + ) + } + } + // Cancel + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text( + stringResource(MR.strings.cancel_verb), + Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary + ) + } + } + } + ) +} + @Composable private fun UserAddressLayout( user: User?, userAddress: UserContactLinkRec?, shareViaProfile: MutableState, createAddress: () -> Unit, + showAddShortLinkAlert: ((() -> Unit)?) -> Unit, learnMore: () -> Unit, share: (String) -> Unit, sendEmail: (UserContactLinkRec) -> Unit, setProfileAddress: (Boolean) -> Unit, deleteAddress: () -> Unit, - saveAas: (AutoAcceptState, MutableState) -> Unit, + saveAddressSettings: (AddressSettingsState, MutableState) -> Unit, ) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId)) @@ -198,8 +280,8 @@ private fun UserAddressLayout( LearnMoreButton(learnMore) } } else { - val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) } - val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) } + val addressSettingsState = remember { mutableStateOf(AddressSettingsState(settings = userAddress.addressSettings)) } + val savedAddressSettingsState = remember { mutableStateOf(addressSettingsState.value) } val showShortLink = remember { mutableStateOf(true) } SectionViewWithButton( @@ -207,17 +289,26 @@ private fun UserAddressLayout( titleButton = if (userAddress.connLinkContact.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null ) { SimpleXCreatedLinkQRCode(userAddress.connLinkContact, short = showShortLink.value) - ShareAddressButton { share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) } + if (userAddress.shouldBeUpgraded) { + AddShortLinkButton(text = stringResource(MR.strings.add_short_link)) { showAddShortLinkAlert(null) } + } + ShareAddressButton { + if (userAddress.shouldBeUpgraded) { + showAddShortLinkAlert { share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) } + } else { + share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) + } + } // ShareViaEmailButton { sendEmail(userAddress) } - BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } - AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas) + BusinessAddressToggle(addressSettingsState) { saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) } + AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAddressSettings) - if (autoAcceptState.value.business) { + if (addressSettingsState.value.businessAddress) { SectionTextFooter(stringResource(MR.strings.add_your_team_members_to_conversations)) } } - SectionDividerSpaced(maxTopPadding = autoAcceptState.value.business) + SectionDividerSpaced(maxTopPadding = addressSettingsState.value.businessAddress) SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) { CreateOneTimeLinkButton() } @@ -248,6 +339,17 @@ private fun CreateAddressButton(onClick: () -> Unit) { ) } +@Composable +private fun AddShortLinkButton(text: String, onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_arrow_upward), + text, + onClick, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + @Composable private fun CreateOneTimeLinkButton() { val closeAll = { ModalManager.start.closeModals() } @@ -290,14 +392,14 @@ private fun AddressSettingsButton( userAddress: UserContactLinkRec, shareViaProfile: MutableState, setProfileAddress: (Boolean) -> Unit, - saveAas: (AutoAcceptState, MutableState) -> Unit, + saveAddressSettings: (AddressSettingsState, MutableState) -> Unit, ) { SettingsActionItem( painterResource(MR.images.ic_settings), stringResource(MR.strings.address_settings), click = { ModalManager.start.showCustomModal { close -> - UserAddressSettings(user, userAddress, shareViaProfile, setProfileAddress, saveAas, close = close) + UserAddressSettings(user, userAddress, shareViaProfile, setProfileAddress, saveAddressSettings, close = close) } } ) @@ -309,20 +411,20 @@ private fun ModalData.UserAddressSettings( userAddress: UserContactLinkRec, shareViaProfile: MutableState, setProfileAddress: (Boolean) -> Unit, - saveAas: (AutoAcceptState, MutableState) -> Unit, + saveAddressSettings: (AddressSettingsState, MutableState) -> Unit, close: () -> Unit ) { - val autoAcceptState = remember { stateGetOrPut("autoAcceptState") { (AutoAcceptState(userAddress)) } } - val autoAcceptStateSaved = remember { stateGetOrPut("autoAcceptStateSaved") { (autoAcceptState.value) } } + val addressSettingsState = remember { stateGetOrPut("autoAcceptState") { (AddressSettingsState(userAddress.addressSettings)) } } + val savedAddressSettingsState = remember { stateGetOrPut("autoAcceptStateSaved") { (addressSettingsState.value) } } - fun onClose(close: () -> Unit): Boolean = if (autoAcceptState.value == autoAcceptStateSaved.value) { + fun onClose(close: () -> Unit): Boolean = if (addressSettingsState.value == savedAddressSettingsState.value) { chatModel.centerPanelBackgroundClickHandler = null close() false } else { showUnsavedChangesAlert( save = { - saveAas(autoAcceptState.value, autoAcceptStateSaved) + saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) chatModel.centerPanelBackgroundClickHandler = null close() }, @@ -351,12 +453,20 @@ private fun ModalData.UserAddressSettings( ) { SectionView { ShareWithContactsButton(shareViaProfile, setProfileAddress) - AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } + AutoAcceptToggle(addressSettingsState) { saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) } + if (addressSettingsState.value.autoAccept && !chatModel.addressShortLinkDataSet() && !addressSettingsState.value.businessAddress) { + AcceptIncognitoToggle(addressSettingsState) + } } + SectionDividerSpaced() - if (autoAcceptState.value.enable) { - SectionDividerSpaced() - AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas) + SectionView(stringResource(MR.strings.address_welcome_message).uppercase()) { + AutoReplyEditor(addressSettingsState) + } + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + + saveAddressSettingsButton(addressSettingsState.value == savedAddressSettingsState.value) { + saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) } } } @@ -404,33 +514,53 @@ fun ShareWithContactsButton(shareViaProfile: MutableState, setProfileAd } @Composable -private fun BusinessAddressToggle(autoAcceptState: MutableState, saveAas: (AutoAcceptState) -> Unit) { +private fun BusinessAddressToggle(addressSettingsState: MutableState, saveAddressSettings: (AddressSettingsState) -> Unit) { PreferenceToggleWithIcon( stringResource(MR.strings.business_address), painterResource(MR.images.ic_work), - checked = autoAcceptState.value.business, - ) { ba -> - autoAcceptState.value = if (ba) - AutoAcceptState(enable = true, incognito = false, business = true, autoAcceptState.value.welcomeText) + checked = addressSettingsState.value.businessAddress, + ) { businessToggle -> + addressSettingsState.value = if (businessToggle) + AddressSettingsState( + businessAddress = true, + autoAccept = true, + autoAcceptIncognito = false, + autoReply = addressSettingsState.value.autoReply + ) else - AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, business = false, autoAcceptState.value.welcomeText) - saveAas(autoAcceptState.value) + AddressSettingsState( + businessAddress = false, + autoAccept = addressSettingsState.value.autoAccept, + autoAcceptIncognito = addressSettingsState.value.autoAcceptIncognito, + autoReply = addressSettingsState.value.autoReply + ) + saveAddressSettings(addressSettingsState.value) } } @Composable -private fun AutoAcceptToggle(autoAcceptState: MutableState, saveAas: (AutoAcceptState) -> Unit) { +private fun AutoAcceptToggle(addressSettingsState: MutableState, saveAddressSettings: (AddressSettingsState) -> Unit) { PreferenceToggleWithIcon( stringResource(MR.strings.auto_accept_contact), painterResource(MR.images.ic_check), - disabled = autoAcceptState.value.business, - checked = autoAcceptState.value.enable - ) { - autoAcceptState.value = if (!it) - AutoAcceptState() + disabled = addressSettingsState.value.businessAddress, + checked = addressSettingsState.value.autoAccept + ) { autoAcceptToggle -> + addressSettingsState.value = if (autoAcceptToggle) + AddressSettingsState( + businessAddress = addressSettingsState.value.businessAddress, + autoAccept = true, + autoAcceptIncognito = false, + autoReply = "" + ) else - AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.business, autoAcceptState.value.welcomeText) - saveAas(autoAcceptState.value) + AddressSettingsState( + businessAddress = false, + autoAccept = false, + autoAcceptIncognito = false, + autoReply = "" + ) + saveAddressSettings(addressSettingsState.value) } } @@ -445,103 +575,93 @@ private fun DeleteAddressButton(onClick: () -> Unit) { ) } -private class AutoAcceptState { - var enable: Boolean = false +private class AddressSettingsState { + var businessAddress: Boolean = false private set - var incognito: Boolean = false + var autoAccept: Boolean = false private set - var business: Boolean = false + var autoAcceptIncognito: Boolean = false private set - var welcomeText: String = "" + var autoReply: String = "" private set - constructor(enable: Boolean = false, incognito: Boolean = false, business: Boolean = false, welcomeText: String = "") { - this.enable = enable - this.incognito = incognito - this.business = business - this.welcomeText = welcomeText + constructor(businessAddress: Boolean = false, autoAccept: Boolean = false, autoAcceptIncognito: Boolean = false, autoReply: String = "") { + this.businessAddress = businessAddress + this.autoAccept = autoAccept + this.autoAcceptIncognito = autoAcceptIncognito + this.autoReply = autoReply } - constructor(contactLink: UserContactLinkRec) { - contactLink.autoAccept?.let { aa -> - enable = true - incognito = aa.acceptIncognito - business = aa.businessAddress - aa.autoReply?.let { msg -> - welcomeText = msg.text - } ?: run { - welcomeText = "" - } - } + constructor(settings: AddressSettings) { + this.businessAddress = settings.businessAddress + this.autoAccept = settings.autoAccept != null + this.autoAcceptIncognito = settings.autoAccept?.acceptIncognito == true + this.autoReply = settings.autoReply?.text ?: "" } - val autoAccept: AutoAccept? + val addressSettings: AddressSettings get() { - if (enable) { - var autoReply: MsgContent? = null - val s = welcomeText.trim() - if (s != "") { - autoReply = MsgContent.MCText(s) - } - return AutoAccept(business, incognito, autoReply) - } - return null + return AddressSettings( + businessAddress = this.businessAddress, + autoAccept = if (this.autoAccept) AutoAccept(acceptIncognito = this.autoAcceptIncognito) else null, + autoReply = if (this.autoReply.isEmpty()) null else MsgContent.MCText(this.autoReply) + ) } override fun equals(other: Any?): Boolean { - if (other !is AutoAcceptState) return false - return this.enable == other.enable && this.incognito == other.incognito && this.business == other.business && this.welcomeText == other.welcomeText + if (other !is AddressSettingsState) return false + return ( + this.businessAddress == other.businessAddress + && this.autoAccept == other.autoAccept + && this.autoAcceptIncognito == other.autoAcceptIncognito + && this.autoReply == other.autoReply + ) } override fun hashCode(): Int { - var result = enable.hashCode() - result = 31 * result + incognito.hashCode() - result = 31 * result + business.hashCode() - result = 31 * result + welcomeText.hashCode() + var result = businessAddress.hashCode() + result = 31 * result + autoAccept.hashCode() + result = 31 * result + autoAcceptIncognito.hashCode() + result = 31 * result + autoReply.hashCode() return result } } @Composable -private fun AutoAcceptSection( - autoAcceptState: MutableState, - savedAutoAcceptState: MutableState, - saveAas: (AutoAcceptState, MutableState) -> Unit -) { - SectionView(stringResource(MR.strings.auto_accept_contact).uppercase()) { - if (!autoAcceptState.value.business) { - AcceptIncognitoToggle(autoAcceptState) - } - WelcomeMessageEditor(autoAcceptState) - SaveAASButton(autoAcceptState.value == savedAutoAcceptState.value) { saveAas(autoAcceptState.value, savedAutoAcceptState) } - } -} - -@Composable -private fun AcceptIncognitoToggle(autoAcceptState: MutableState) { +private fun AcceptIncognitoToggle(addressSettingsState: MutableState) { PreferenceToggleWithIcon( stringResource(MR.strings.accept_contact_incognito_button), - if (autoAcceptState.value.incognito) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), - if (autoAcceptState.value.incognito) Indigo else MaterialTheme.colors.secondary, - checked = autoAcceptState.value.incognito, - ) { - autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.business, autoAcceptState.value.welcomeText) + if (addressSettingsState.value.autoAcceptIncognito) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), + if (addressSettingsState.value.autoAcceptIncognito) Indigo else MaterialTheme.colors.secondary, + checked = addressSettingsState.value.autoAcceptIncognito, + ) { incognitoToggle -> + addressSettingsState.value = AddressSettingsState( + businessAddress = addressSettingsState.value.businessAddress, + autoAccept = addressSettingsState.value.autoAccept, + autoAcceptIncognito = incognitoToggle, + autoReply = addressSettingsState.value.autoReply + ) } } @Composable -private fun WelcomeMessageEditor(autoAcceptState: MutableState) { - val welcomeText = rememberSaveable { mutableStateOf(autoAcceptState.value.welcomeText) } - TextEditor(welcomeText, Modifier.height(100.dp), placeholder = stringResource(MR.strings.enter_welcome_message_optional)) - LaunchedEffect(welcomeText.value) { - if (welcomeText.value != autoAcceptState.value.welcomeText) { - autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, autoAcceptState.value.business, welcomeText.value) +private fun AutoReplyEditor(addressSettingsState: MutableState) { + val autoReply = rememberSaveable { mutableStateOf(addressSettingsState.value.autoReply) } + TextEditor(autoReply, Modifier.height(100.dp), placeholder = stringResource(MR.strings.enter_welcome_message_optional)) + LaunchedEffect(autoReply.value) { + if (autoReply.value != addressSettingsState.value.autoReply) { + addressSettingsState.value = AddressSettingsState( + businessAddress = addressSettingsState.value.businessAddress, + autoAccept = addressSettingsState.value.autoAccept, + autoAcceptIncognito = addressSettingsState.value.autoAcceptIncognito, + autoReply = autoReply.value + ) } } } @Composable -private fun SaveAASButton(disabled: Boolean, onClick: () -> Unit) { +private fun saveAddressSettingsButton(disabled: Boolean, onClick: () -> Unit) { SectionItemView(onClick, disabled = disabled) { Text(stringResource(MR.strings.save_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } @@ -559,9 +679,10 @@ fun PreviewUserAddressLayoutNoAddress() { user = User.sampleData, userAddress = null, createAddress = {}, + showAddShortLinkAlert = {}, share = { _ -> }, deleteAddress = {}, - saveAas = { _, _ -> }, + saveAddressSettings = { _, _ -> }, setProfileAddress = { _ -> }, learnMore = {}, shareViaProfile = remember { mutableStateOf(false) }, @@ -590,11 +711,17 @@ fun PreviewUserAddressLayoutAddressCreated() { SimpleXTheme { UserAddressLayout( user = User.sampleData, - userAddress = UserContactLinkRec(CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null)), + userAddress = UserContactLinkRec( + CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null), + shortLinkDataSet = false, + shortLinkLargeDataSet = false, + addressSettings = AddressSettings(businessAddress = false, autoAccept = null, autoReply = null) + ), createAddress = {}, + showAddShortLinkAlert = {}, share = { _ -> }, deleteAddress = {}, - saveAas = { _, _ -> }, + saveAddressSettings = { _, _ -> }, setProfileAddress = { _ -> }, learnMore = {}, shareViaProfile = remember { mutableStateOf(false) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index 90122bd29d..45cdee6108 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -40,9 +40,10 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { UserProfileLayout( profile = profile, close, - saveProfile = { displayName, fullName, image -> + saveProfile = { displayName, fullName, shortDescr, image -> withBGApi { - val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, profile.copy(displayName = displayName.trim(), fullName = fullName, image = image)) + val updatedProfile = profile.copy(displayName = displayName.trim(), fullName = fullName.trim(), shortDescr = shortDescr.trim().ifEmpty { null }, image = image) + val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, updatedProfile) if (updated != null) { val (newProfile, _) = updated chatModel.updateCurrentUser(user.remoteHostId, newProfile) @@ -59,11 +60,12 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { fun UserProfileLayout( profile: Profile, close: () -> Unit, - saveProfile: (String, String, String?) -> Unit, + saveProfile: (String, String, String, String?) -> Unit, ) { val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = remember { mutableStateOf(profile.displayName) } val fullName = remember { mutableStateOf(profile.fullName) } + val shortDescr = remember { mutableStateOf(profile.shortDescr ?: "") } val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(profile.image) } val scope = rememberCoroutineScope() @@ -85,14 +87,15 @@ fun UserProfileLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { val dataUnchanged = - displayName.value == profile.displayName && - fullName.value == profile.fullName && + displayName.value.trim() == profile.displayName && + fullName.value.trim() == profile.fullName && + shortDescr.value.trim() == (profile.shortDescr ?: "") && profile.image == profileImage.value val closeWithAlert = { - if (dataUnchanged || !canSaveProfile(displayName.value, profile)) { + if (dataUnchanged || !canSaveProfile(displayName.value, shortDescr.value, profile)) { close() } else { - showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, profileImage.value) }, close) + showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) }, close) } } ModalView(close = closeWithAlert) { @@ -144,9 +147,29 @@ fun UserProfileLayout( ) ProfileNameField(fullName) } + Spacer(Modifier.height(DEFAULT_PADDING)) - val enabled = !dataUnchanged && canSaveProfile(displayName.value, profile) - val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, profileImage.value) } + + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(MR.strings.short_descr__field), + fontSize = 16.sp, + ) + if (!bioFitsLimit(shortDescr.value)) { + Spacer(Modifier.size(DEFAULT_PADDING_HALF)) + IconButton( + onClick = { AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.bio_too_large)) }, + Modifier.size(20.dp) + ) { + Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error) + } + } + } + ProfileNameField(shortDescr) + + Spacer(Modifier.height(DEFAULT_PADDING)) + val enabled = !dataUnchanged && canSaveProfile(displayName.value, shortDescr.value, profile) + val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) } val saveColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary Text( stringResource(MR.strings.save_and_notify_contacts), @@ -209,10 +232,10 @@ private fun isValidNewProfileName(displayName: String, profile: Profile): Boolea displayName == profile.displayName || isValidDisplayName(displayName.trim()) private fun showFullName(profile: Profile): Boolean = - profile.fullName.isNotEmpty() && profile.fullName != profile.displayName + profile.fullName.trim().isNotEmpty() && profile.fullName.trim() != profile.displayName.trim() -private fun canSaveProfile(displayName: String, profile: Profile): Boolean = - displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, profile) +private fun canSaveProfile(displayName: String, shortDescr: String, profile: Profile): Boolean = + displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, profile) && bioFitsLimit(shortDescr) @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, @@ -225,7 +248,7 @@ fun PreviewUserProfileLayoutEditOff() { UserProfileLayout( profile = Profile.sampleData, close = {}, - saveProfile = { _, _, _ -> } + saveProfile = { _, _, _, _ -> } ) } } @@ -241,7 +264,7 @@ fun PreviewUserProfileLayoutEditOn() { UserProfileLayout( profile = Profile.sampleData, close = {}, - saveProfile = { _, _, _ -> } + saveProfile = { _, _, _, _ -> } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt index 0c38b0c045..8c38070c98 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt @@ -33,7 +33,7 @@ import chat.simplex.res.MR import java.text.DecimalFormat @Composable -fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> Unit, close: () -> Unit) { +fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() -> Unit) -> Unit, close: () -> Unit) { val currentRemoteHost by remember { chatModel.currentRemoteHost } val developerTools = remember { appPrefs.developerTools.get() } @@ -48,8 +48,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) } val smpWebPortServers = remember { mutableStateOf(currentCfgVal.smpWebPortServers) } - val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) } - val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) } + val networkTCPConnectTimeoutInteractive = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout.interactiveTimeout) } + val networkTCPConnectTimeoutBackground = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout.backgroundTimeout) } + val networkTCPTimeoutInteractive = remember { mutableStateOf(currentCfgVal.tcpTimeout.interactiveTimeout) } + val networkTCPTimeoutBackground = remember { mutableStateOf(currentCfgVal.tcpTimeout.backgroundTimeout) } val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) } val networkRcvConcurrency = remember { mutableStateOf(currentCfgVal.rcvConcurrency) } val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) } @@ -86,8 +88,14 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U smpProxyMode = smpProxyMode.value, smpProxyFallback = smpProxyFallback.value, smpWebPortServers = smpWebPortServers.value, - tcpConnectTimeout = networkTCPConnectTimeout.value, - tcpTimeout = networkTCPTimeout.value, + tcpConnectTimeout = NetworkTimeout( + backgroundTimeout = networkTCPConnectTimeoutBackground.value, + interactiveTimeout = networkTCPConnectTimeoutInteractive.value + ) , + tcpTimeout = NetworkTimeout( + backgroundTimeout = networkTCPTimeoutBackground.value, + interactiveTimeout = networkTCPTimeoutInteractive.value + ), tcpTimeoutPerKb = networkTCPTimeoutPerKb.value, rcvConcurrency = networkRcvConcurrency.value, tcpKeepAlive = tcpKeepAlive, @@ -101,8 +109,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U smpProxyMode.value = cfg.smpProxyMode smpProxyFallback.value = cfg.smpProxyFallback smpWebPortServers.value = cfg.smpWebPortServers - networkTCPConnectTimeout.value = cfg.tcpConnectTimeout - networkTCPTimeout.value = cfg.tcpTimeout + networkTCPConnectTimeoutInteractive.value = cfg.tcpConnectTimeout.interactiveTimeout + networkTCPConnectTimeoutBackground.value = cfg.tcpConnectTimeout.backgroundTimeout + networkTCPTimeoutInteractive.value = cfg.tcpTimeout.interactiveTimeout + networkTCPTimeoutBackground.value = cfg.tcpTimeout.backgroundTimeout networkTCPTimeoutPerKb.value = cfg.tcpTimeoutPerKb networkRcvConcurrency.value = cfg.rcvConcurrency networkSMPPingInterval.value = cfg.smpPingInterval @@ -156,8 +166,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U smpProxyMode = smpProxyMode, smpProxyFallback = smpProxyFallback, smpWebPortServers, - networkTCPConnectTimeout, - networkTCPTimeout, + networkTCPConnectTimeoutInteractive, + networkTCPConnectTimeoutBackground, + networkTCPTimeoutInteractive, + networkTCPTimeoutBackground, networkTCPTimeoutPerKb, networkRcvConcurrency, networkSMPPingInterval, @@ -189,8 +201,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U smpProxyMode: MutableState, smpProxyFallback: MutableState, smpWebPortServers: MutableState, - networkTCPConnectTimeout: MutableState, - networkTCPTimeout: MutableState, + networkTCPConnectTimeoutInteractive: MutableState, + networkTCPConnectTimeoutBackground: MutableState, + networkTCPTimeoutInteractive: MutableState, + networkTCPTimeoutBackground: MutableState, networkTCPTimeoutPerKb: MutableState, networkRcvConcurrency: MutableState, networkSMPPingInterval: MutableState, @@ -202,7 +216,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U updateSessionMode: (TransportSessionMode) -> Unit, updateSMPProxyMode: (SMPProxyMode) -> Unit, updateSMPProxyFallback: (SMPProxyFallback) -> Unit, - showModal: (ModalData.() -> Unit) -> Unit, + showModal: (@Composable ModalData.() -> Unit) -> Unit, resetDisabled: Boolean, reset: () -> Unit, saveDisabled: Boolean, @@ -242,14 +256,26 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U SectionView(stringResource(MR.strings.network_option_tcp_connection).uppercase()) { SectionItemView { TimeoutSettingRow( - stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeout, - listOf(10_000000, 15_000000, 20_000000, 30_000000, 45_000000, 60_000000, 90_000000), secondsLabel + stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeoutInteractive, + listOf(10_000000, 15_000000, 20_000000, 30_000000), secondsLabel ) } SectionItemView { TimeoutSettingRow( - stringResource(MR.strings.network_option_protocol_timeout), networkTCPTimeout, - listOf(5_000000, 7_000000, 10_000000, 15_000000, 20_000_000, 30_000_000), secondsLabel + stringResource(MR.strings.network_option_tcp_connection_timeout_background), networkTCPConnectTimeoutBackground, + listOf(30_000000, 45_000000, 60_000000, 90_000000), secondsLabel + ) + } + SectionItemView { + TimeoutSettingRow( + stringResource(MR.strings.network_option_protocol_timeout), networkTCPTimeoutInteractive, + listOf(5_000000, 7_000000, 10_000000, 15_000000, 20_000_000), secondsLabel + ) + } + SectionItemView { + TimeoutSettingRow( + stringResource(MR.strings.network_option_protocol_timeout_background), networkTCPTimeoutBackground, + listOf(15_000000, 20_000000, 30_000000, 45_000000, 60_000_000), secondsLabel ) } SectionItemView { @@ -555,8 +581,10 @@ fun PreviewAdvancedNetworkSettingsLayout() { smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, smpProxyFallback = remember { mutableStateOf(SMPProxyFallback.Allow) }, smpWebPortServers = remember { mutableStateOf(SMPWebPortServers.Preset) }, - networkTCPConnectTimeout = remember { mutableStateOf(10_000000) }, - networkTCPTimeout = remember { mutableStateOf(10_000000) }, + networkTCPConnectTimeoutInteractive = remember { mutableStateOf(15_000000) }, + networkTCPConnectTimeoutBackground = remember { mutableStateOf(45_000000) }, + networkTCPTimeoutInteractive = remember { mutableStateOf(10_000000) }, + networkTCPTimeoutBackground = remember { mutableStateOf(30_000000) }, networkTCPTimeoutPerKb = remember { mutableStateOf(10_000) }, networkRcvConcurrency = remember { mutableStateOf(8) }, networkSMPPingInterval = remember { mutableStateOf(10_000000) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index 98f671ddc4..26ecf151ff 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -594,7 +594,7 @@ fun UseOnionHosts( onSelected = {} ) } - SectionTextFooter(values.first { it.value == onionHosts.value }.description) + SectionTextFooter(values.firstOrNull { it.value == onionHosts.value }?.description ?: AnnotatedString("")) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt index 1ec2534ab1..6a999aa89d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt @@ -43,6 +43,13 @@ fun ModalData.NewServerView( if (isActive) { newServer.value = res.first testing.value = false + val failure = res.second + if (failure != null) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_test_failed), + text = failure.localizedDescription + ) + } } } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt index bebc96a28c..ccad962313 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt @@ -93,6 +93,13 @@ fun ProtocolServerView( if (isActive) { draftServer.value = res.first testing.value = false + val failure = res.second + if (failure != null) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_test_failed), + text = failure.localizedDescription + ) + } } } }, @@ -189,7 +196,7 @@ fun CustomServer( if (valid.value) { SectionDividerSpaced() SectionView(stringResource(MR.strings.smp_servers_add_to_another_device).uppercase()) { - QRCode(serverAddress.value) + QRCode(serverAddress.value, small = true) } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 4bb2244785..96c539109d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -30,22 +30,22 @@ أضِف خوادم مُعدة مسبقًا أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! - الوصول إلى الخوادم عبر وكيل SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تفعيل هذا الخيار. - أضِف خادم + الوصول إلى الخوادم عبر وسيط SOCKS على المنفذ %d؟ يجب بدء تشغيل الوسيط قبل تفعيل هذا الخيار. + أضف خادم إعدادات الشبكة المتقدمة سيبقى جميع أعضاء المجموعة على اتصال. السماح باختفاء الرسائل فقط إذا سمحت جهة اتصالك بذلك. السماح بحذف الرسائل بشكل لا رجوع فيه فقط إذا سمحت لك جهة الاتصال بذلك. (24 ساعة) المُدير - أضِف ملف التعريف + أضف ملف التعريف السماح بإرسال رسائل مباشرة إلى الأعضاء. - قبول التخفي + اقبل التخفي أضِف رسالة ترحيب أضف الخوادم عن طريق مسح رموز QR. يمكن للمُدراء إنشاء روابط للانضمام إلى المجموعات. قبول طلب الاتصال؟ سيتم حذف جميع الرسائل - لا يمكن التراجع عن هذا! سيتم حذف الرسائل فقط من أجلك. - مكالمة مقبولة + قُبلت المكالمة السماح بالمكالمات فقط إذا سمحت جهة اتصالك بذلك. اسمح بردود الفعل على الرسائل فقط إذا سمحت جهة اتصالك بذلك. يتم استخدام Android Keystore لتخزين عبارة المرور بشكل آمن - فهو يسمح لخدمة الإشعارات بالعمل. @@ -110,7 +110,7 @@ لون إضافي ثانوي " \nمتوفر في v5.1" - مكالمات الصوت/الفيديو محظورة. + مكالمات الصوت/الفيديو ممنوعة. عن طريق ملف تعريف الدردشة (افتراضي) أو عن طريق الاتصال (تجريبي). يمكن تعطيله عبر الإعدادات - سيستمر عرض الإشعارات أثناء تشغيل التطبيق.]]> مكالمات الصوت والفيديو @@ -143,7 +143,7 @@ قبول تلقائي المكالمات لا يمكن دعوة جهات الاتصال! - تم تغيير العنوان من أجلك + غُيِّر العنوان من أجلك طلب لاستلام الفيديو مكالمتك تحت الإجراء تغيير عبارة مرور قاعدة البيانات؟ @@ -175,13 +175,13 @@ نٌسخت إلى الحافظة مسح امسح الدردشة - إنشاء عنوان + أنشئ عنوان الدردشات تأكيد عبارة المرور الجديدة… تعمية قاعدة البيانات؟ قاعدة البيانات مُعمّاة غيرت دور %s إلى %s - تغيير عنوان الاستلام + غيّر عنوان الاستلام خطأ في إنشاء ملف التعريف! خطأ في الإتصال انتهت مهلة الاتصال @@ -189,13 +189,13 @@ خطأ في حذف طلب جهة الاتصال خطأ في حذف المجموعة اتصل - إنشاء ملف - إنشاء قائمة انتظار + أنشئ ملف + أنشئ قائمة انتظار قارن الملف خطأ - إنشاء مجموعة سرية + أنشئ مجموعة سرية خطأ في إحباط تغيير العنوان - تفعيل قفل SimpleX + فعّل قفل SimpleX تأكد من بيانات اعتمادك أنشئ عنوان SimpleX متابعة @@ -210,12 +210,12 @@ الاتصال ملف تعريف الدردشة الإصدار الأساسي: v%s - إنشاء ملف تعريف + أنشئ ملف تعريف جار الاتصال… انتهى متصل %1$d تخطت الرسائل - تفعيل القفل + فعّل القفل تأكيد رمز المرور خطأ في حذف قاعدة بيانات الدردشة خطأ في تعمية قاعدة البيانات @@ -224,7 +224,7 @@ جاري الاتصال (أعلن) الاتصال إحباط تغيير العنوان - إنشاء مجموعة سرية + أنشئ مجموعة سرية قارن رموز الأمان مع جهات اتصالك. الواجهة الصينية والاسبانية مسح @@ -244,7 +244,7 @@ مسح خطأ في إنشاء رابط المجموعة (حاضِر) - تفعيل أبقِ TCP على قيد الحياة + فعّل أبقِ TCP على قيد الحياة جار الاتصال… جار الاتصال… أرسلت طلب الاتصال! @@ -253,8 +253,8 @@ جار الاتصال (قُبِل) فُحصت جهة الاتصال %1$s أعضاء - إنشاء رابط المجموعة - إنشاء رابط + أنشئ رابط المجموعة + أنشئ رابط تأكيد كلمة المرور توقفت الدردشة قاعدة بيانات الدردشة @@ -263,7 +263,7 @@ خطأ في تغيير العنوان %1$d رسائل تخطت تغيير وضع القفل - تفعيل رمز التدمير الذاتي + فعّل رمز التدمير الذاتي تغيير دور المجموعة؟ تفضيلات الدردشة أدخل عبارة المرور الصحيحة. @@ -306,17 +306,17 @@ سيتم حذف جهة الاتصال وجميع الرسائل - لا يمكن التراجع عن هذا! الحد الأقصى لحجم الملف المدعوم حاليًا هو %1$s. تواصل عبر الرابط / رمز QR - إنشاء رابط دعوة لمرة واحدة + أنشئ رابط دعوة لمرة واحدة تحقق من عنوان الخادم وحاول مرة أخرى. امسح التحقُّق أنشئ عنوانًا للسماح للأشخاص بالتواصل معك. أدخل الخادم يدويًا ملون لدى جهة الاتصال التعمية بين الطريفين - إنشاء + أنشئ أنشئ ملف تعريفك مكالمة جارية... - تفعيل التدمير الذاتي + فعّل التدمير الذاتي الموافقة على التعمية… الموافقة على التعمية لـ%s… متصل (مقدم) @@ -332,27 +332,27 @@ سمة داكنة حُذفت عبارة مرور قاعدة البيانات وتصديرها - حذف جميع الملفات - حذف بعد - حذف الملف + احذف جميع الملفات + احذف بعد + احذف الملف حذف حذف رسالة العضو؟ - حذف - حذف الرسائل - حذف الرسائل بعد + احذف + احذف الرسائل + احذف الرسائل بعد قاعدة البيانات مُعمّاة! تختلف عبارة مرور قاعدة البيانات عن تلك المحفوظة في Keystore. خطأ في قاعدة البيانات ترقية قاعدة البيانات حُددت %d جهة اتصال - حذف المجموعة + احذف المجموعة حذف المجموعة؟ - حذف الرابط + احذف الرابط حُذفت في: %s المجموعة حُذفت - حذف الصورة + احذف الصورة تخصيص السمات - حذف قاعدة البيانات + احذف قاعدة البيانات حذف ملف تعريف الدردشة؟ حذف الملفات لجميع ملفات تعريف الدردشة قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية، يمكنك تغييرها. @@ -361,31 +361,31 @@ إصدار قاعدة البيانات أحدث من التطبيق، ولكن لا يوجد ترحيل لأسفل ل%s معرّف قاعدة البيانات: %d حذف ملف تعريف الدردشة؟ - حذف ملف تعريف الدردشة ل - حذف للجميع + احذف ملف تعريف الدردشة ل + احذف للجميع أيام - حذف العنوان + احذف العنوان سيتم تحديث عبارة مرور تعمية قاعدة البيانات. حذف الرابط؟ الرجوع إلى إصدار سابق من قاعدة البيانات قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية. يُرجى تغييره قبل التصدير. %d يوم سيتم تعمية قاعدة البيانات. - حذف + احذف حذف الملفات والوسائط؟ - حذف جهة الاتصال + احذف جهة الاتصال تخصيص السمة داكن الافتراضي %s حذف الاتصال قيد الانتظار؟ - حذف ملف تعريف الدردشة + احذف ملف تعريف الدردشة خطأ في فك التعمية حذف الرسالة؟ معرفات قاعدة البيانات وخيار عزل النقل. حذف العنوان؟ خطأ في فك الترميز حذف جهة الاتصال؟ - حذف بالنسبة لي + احذف بالنسبة لي وقت مخصّص لامركزي عبارة مرور قاعدة البيانات @@ -399,11 +399,11 @@ تخصيص ومشاركة سمات الألوان. الخروج بدون حفظ أدوات المطور - حذف قائمة الانتظار + احذف قائمة الانتظار خطأ في تحديث خصوصية المستخدم مسح رمز QR.]]> - حذف ملف التعريف - حذف الخادم + احذف ملف التعريف + احذف الخادم خطأ في تحديث رابط المجموعة الوصف توسيع تحديد الدور @@ -428,7 +428,7 @@ مزيد من تقليل استخدام البطارية المجموعة للجميع - لم يتم العثور على الملف + لم يُعثر على الملف من المعرض الواجهة الفرنسية المساعدة @@ -486,7 +486,7 @@ سيتم استلام الصورة عندما يكتمل جهة اتصالك من رفعِها. اعرض رمز QR في مكالمة الفيديو، أو شارك الرابط.]]> ثبّت SimpleX Chat لطرفية - إذا قمت بالتأكيد، فستتمكن خوادم المراسلة من رؤية عنوان IP الخاص بك ومزود الخدمة الخاص بك - أي الخوادم التي تتصل بها. + إذا أكّدت، فستتمكن خوادم المُراسلة من رؤية عنوان IP الخاص بك ومزود خدمتك - أي الخوادم التي تتصل بها. إخفاء: إذا أدخلت رمز المرور هذا عند فتح التطبيق، فستتم إزالة جميع بيانات التطبيق نهائيًا! استيراد قاعدة بيانات الدردشة؟ @@ -547,18 +547,18 @@ \n3. اُخترق الاتصال. واجهة أستخدام يابانية وبرتغالية يمكن أن يحدث ذلك عندما تستخدم أنت أو اتصالك النُسخة الاحتياطية القديمة لقاعدة البيانات. - انضمام ك%s + انضم ك%s رمز QR غير صالح الواجهة الإيطالية الرابط غير صالح! دعوة الأصدقاء خطأ في Keychain دعوة للمجموعة - يٌمنع حذف الرسائل بشكل لا رجعة فيه. + يُمنع حذف الرسائل بشكل لا رجعة فيه. تنسيق الرسالة غير صالح البيانات غير صالحة بيانات ملف التعريف المحلية فقط - يٌمنع حذف الرسائل بشكل لا رجعة فيه في هذه الدردشة. + يُمنع حذف الرسائل بشكل لا رجعة فيه في هذه الدردشة. دعوة الأعضاء غادِر المجموعة الاسم المحلي: @@ -590,7 +590,7 @@ مغادرة المجموعة؟ غادر إيصالات التسليم مُعطَّلة! - تعطيل + عطّل رسائل تختفي استيثاق الجهاز مُعطَّل. جارِ إيقاف تشغيل قفل SimpleX. %d شهر @@ -601,7 +601,7 @@ قطع الاتصال مصادقة الجهاز غير مفعّلة. يمكنك تشغيل قفل SimpleX عبر الإعدادات، بمجرد تفعيل مصادقة الجهاز. نزّل الملف - تعطيل قفل SimpleX + عطّل قفل SimpleX تحرير اسم ملف التعريف: البريد الإلكتروني @@ -620,7 +620,7 @@ %d أسبوع لا يمكن أن يحتوي اسم العرض على مسافة فارغة. مكالمة فيديو مُعمّاة بين الطريفين - الرسائل المباشرة بين الأعضاء ممنوعة في هذه المجموعة. + يُمنع إرسال الرسائل المباشرة بين الأعضاء في هذه المجموعة. %d ساعة %d ساعة %d ساعات @@ -629,8 +629,8 @@ %d ملف/ات بإجمالي الحجم %s %d ثانية جهات الاتصال - تعطيل للجميع - تعطيل (الاحتفاظ بالتجاوزات) + عطّل للجميع + عطّل (الاحتفاظ بالتجاوزات) تعطيل الإيصالات؟ الهجرة المختلفة في التطبيق / قاعدة البيانات: %s / %s يختفي في @@ -669,16 +669,16 @@ تأكد من أن الملف يحتوي على بناء جملة YAML الصحيح. تصدير النسق للحصول على مثال لهيكل ملف النسق. 40 ثانية كحد أقصى، يتم استلامها على الفور. خطأ في حفظ خوادم ICE - إجراء اتصال خاص + أجري اتصال خاص خطأ في إيقاف الدردشة خطأ في تحميل التفاصيل - تفعيل المكالمات من شاشة القفل عبر الإعدادات. + فعّل المكالمات من شاشة القفل عبر الإعدادات. تفعيل الحذف التلقائي للرسائل؟ خطأ في إزالة العضو خطأ في تحديد العنوان علّم مقروءة - تفعيل للجميع - تفعيل (الاحتفاظ بالتجاوزات) + فعِّل للجميع + فعّل (الاحتفاظ بالتجاوزات) تفعيل الإيصالات؟ خطأ في بدء الدردشة خطأ في تصدير قاعدة بيانات الدردشة @@ -686,7 +686,7 @@ اجعل ملف التعريف خاصًا! تصفية الدردشات غير المقروءة والمفضلة. البحث عن الدردشات بشكل أسرع - تفعيل + فعّل حتى عندما يتم تعطيله في المحادثة. إصلاح التعمية بعد استعادة النُسخ الاحتياطية. اجعل رسالة واحدة تختفي @@ -793,12 +793,12 @@ جهة اتصالك فقط يمكنها حذف الرسائل بشكل لا رجعة فيه (يمكنك تعليم الرسالة للحذف). (24 ساعة) أنت فقط يمكنك إرسال رسائل صوتية. افتح - لم يتم تغيير رمز المرور! - تم تغيير رمز المرور + لم يُغيّر رمز المرور! + غُيّر رمز المرور! جارِ فتح قاعدة البيانات… جهة اتصالك فقط يمكنها إرسال رسائل صوتية. ألصق - لم يتم العثور على عبارة المرور في Keystore، يُرجى إدخالها يدويًا. ربما حدث هذا إذا استعدت بيانات التطبيق باستخدام أداة النسخ الاحتياطي. إذا لم يكن الأمر كذلك، يُرجى التواصل مع المطورين. + لم يُعثر على عبارة المرور في Keystore، يُرجى إدخالها يدويًا. ربما حدث هذا إذا استعدت بيانات التطبيق باستخدام أداة النسخ الاحتياطي. إذا لم يكن الأمر كذلك، يُرجى التواصل مع المطورين. افتح الدردشة قد يؤدي فتح الرابط في المتصفح إلى تقليل خصوصية الاتصال وأمانه. ستظهر روابط SimpleX غير الموثوقة باللون الأحمر. أنت فقط يمكنك إضافة ردود الفعل على الرسالة. @@ -826,7 +826,7 @@ الإشعارات الدورية مُعطَّلة صورة ملف التعريف الإشعارات خاصة - يرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. + يُرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. يُرجى تحديث التطبيق والتواصل مع المطورين. دليل المستخدم.]]> غيّر ملفات تعريف الدردشة @@ -836,20 +836,20 @@ رفض قيم التطبيق منفذ - حفظ إعدادات القبول التلقائي + احفظ إعدادات عنوان SimpleX إعادة تعريف الخصوصية الرجاء الإبلاغ للمطورين. الخصوصية والأمان - إزالة + أزل إزالة عبارة المرور من Keystore؟ الرجاء إدخال عبارة المرور الحالية الصحيحة. - يرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. - منع مكالمات الصوت/الفيديو. - منع حذف الرسائل التي لا رجعة فيها. + يُرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. + امنع مكالمات الصوت/الفيديو. + امنع حذف الرسائل التي لا رجعة فيها. استلام الملفات غير معتمد حتى الآن الرجاء التحقق من اتصالك بالشبكة مع %1$s وحاول مرة أخرى. ستتم إزالة خوادم WebRTC ICE المحفوظة. - منع إرسال الملفات والوسائط. + امنع إرسال الملفات والوسائط. استلمت إجابة… مستودع GitHub.]]> رفض @@ -858,9 +858,9 @@ استعادة النسخة الاحتياطية لقاعدة البيانات؟ حفظ اتصالات ملف التعريف والخادم - منع ردود فعل الرسالة. - منع إرسال الرسائل الصوتية. - منع ردود فعل الرسائل. + امنع ردود فعل الرسالة. + امنع إرسال الرسائل الصوتية. + امنع ردود فعل الرسائل. قراءة المزيد انخفاض استخدام البطارية سجل رسالة صوتية @@ -888,27 +888,27 @@ تشغيل الدردشة استعادة النسخة الاحتياطية لقاعدة البيانات خطأ في استعادة قاعدة البيانات - أُزيلت %1$s - أزالتك + أُزيل %1$s + أزالك استلمت في - إزالة العضو - إزالة + أزل العضو + أزل صفّر إلى الإعدادات الافتراضية بينج الفاصل الزمني كلمة مرور ملف التعريف - منع إرسال الرسائل التي تختفي. + امنع إرسال الرسائل التي تختفي. مهلة البروتوكول مهلة البروتوكول لكل كيلوبايت - منع إرسال الرسائل الصوتية. - منع إرسال رسائل مباشرة إلى الأعضاء. - منع إرسال الرسائل التي تختفي. + امنع إرسال الرسائل الصوتية. + امنع إرسال رسائل مباشرة إلى الأعضاء. + امنع إرسال الرسائل التي تختفي. الاحتفاظ بمسودة الرسالة الأخيرة، مع المرفقات. أسماء ملفات خاصة احمِ ملفات تعريف دردشتك بكلمة مرور! رُفضت المكالمة حماية شاشة التطبيق - أُزيلت - يرجى تذكرها أو تخزينها بأمان - لا توجد طريقة لاستعادة كلمة المرور المفقودة! + أُزيل + يُرجى تذكرها أو تخزينها بأمان - لا توجد طريقة لاستعادة كلمة المرور المفقودة! معاينة من المحتمل أن الملف المرجعي للشهادة في عنوان الخادم غير صحيح يتم استلام الرسائل… @@ -918,7 +918,7 @@ سحب وصول الملف سحب وصول الملف؟ رٌفض الإذن! - يرجى مطالبة جهة اتصالك بتفعيل إرسال الرسائل الصوتية. + يُرجى مطالبة جهة اتصالك بتفعيل إرسال الرسائل الصوتية. العنصر النائب لصورة ملف التعريف رمز QR صفّر @@ -955,7 +955,7 @@ الرسائل المرسلة سيتم حذفها بعد المدة المحدّدة. تعيين رسالة تظهر للأعضاء الجدد! عيّن رمز المرور - تم إرساله في: %s + أُرسلت في: %s %s (الحالي) رسالة مرسلة عيّن تفضيلات المجموعة @@ -973,7 +973,7 @@ مسح الرمز أرسل أسئلة وأفكار مشاركة العنوان مع جهات الاتصال؟ - مشاركة العنوان + شارك العنوان حفظ رسالة الترحيب؟ احفظ الخوادم أرسل إيصالات التسليم إلى @@ -982,7 +982,7 @@ عيّن عبارة المرور للتصدير تم تغيير رمز الأمان إيصالات الإرسال - تم إرساله في + أُرسلت في حدد سيتم تفعيل إرسال إيصالات التسليم لجميع جهات الاتصال. سيتم تفعيل إرسال إيصالات التسليم لجميع جهات الاتصال في جميع ملفات تعريف الدردشة المرئية. @@ -1004,7 +1004,7 @@ فعّلت رمز المرور للتدمير الذاتي! الإعدادات دعوة SimpleX لمرة واحدة - عرض جهة الاتصال والرسالة + أظهر جهة الاتصال والرسالة قفل SimpleX لم يتحقق من %s قفل SimpleX @@ -1017,18 +1017,18 @@ مشاركة الملف… شعار SimpleX فريق SimpleX - عرض رمز QR + أظهر رمز QR تم التحقق %s فشلت بعض الخوادم في الاختبار: إرسال معاينات الرابط تخطي دعوة الأعضاء إيقاف الدردشة؟ - عرض + أظهر حدثت بعض الأخطاء غير الفادحة أثناء الاستيراد: - وكيل SOCKS + وسيط SOCKS تم تدقيق أمان SimpleX Chat بواسطة Trail of Bits. إيقاف - عرض المعاينة + أظهر المعاينة السماعة متوقفة SimpleX وضع القفل مشاركة الرابط @@ -1046,13 +1046,13 @@ أوقف الدردشة لتصدير أو استيراد أو حذف قاعدة بيانات الدردشة. لن تتمكّن من استلام الرسائل وإرسالها أثناء إيقاف الدردشة. %s ثانية/ثواني يبدأ… - تم تشغيل القفل SimpleX - عرض: - عرض خيارات المطور + شُغّل قفل SimpleX + أظهر: + أظهر خيارات المطور simplexmq: v%s (%2s) يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور - عرض جهة الاتصال فقط + أظهر جهة الاتصال فقط مكالمات SimpleX Chat خدمة SimpleX Chat يبدأ بشكل دوري @@ -1060,10 +1060,10 @@ إيقاف الملف التوقف عن إرسال الملف؟ عنوان SimpleX - استخدم مضيفي .onion إلى "لا" إذا كان وكيل SOCKS لا يدعمها.]]> + استخدم مضيفي .onion إلى "لا" إذا كان وسيط SOCKS لا يدعمها.]]> مشاركة مع جهات الاتصال إيقاف التشغيل؟ - إعدادات وكيل SOCKS + إعدادات وسيط SOCKS إيقاف التشغيل السماعة قيد التشغيل أرسل @@ -1108,7 +1108,7 @@ عزل النقل هذه السلسلة ليست رابط اتصال! هذه الإعدادات لملف تعريفك الحالي - يمكن تجاوزها في إعدادات الاتصال و المجموعة. + يمكن تجاوزها في إعدادات الاتصال والمجموعة. انتهت مهلة اتصال TCP لحماية المنطقة الزمنية، تستخدم ملفات الصور / الصوت التوقيت العالمي المنسق (UTC). فقدنا القراد الثاني! ✅ @@ -1127,7 +1127,7 @@ تجزئة الرسالة السابقة مختلفة. معرف الرسالة التالية غير صحيح (أقل أو يساوي السابق). \nيمكن أن يحدث ذلك بسبب بعض العلل أو عندما يُخترق الاتصال. - إزالة من المفضلة + أزل من المفضلة محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه. اختيار ملف إرسال غير مصرح به @@ -1160,9 +1160,9 @@ ستكون متصلاً بالمجموعة عندما يكون جهاز مضيف المجموعة متصلاً بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! ستكون متصلاً عندما يتم قبول طلب اتصالك، يُرجى الانتظار أو التحقق لاحقًا! تستخدم خوادم SimpleX Chat. - استخدم وكيل SOCKS + استخدم وسيط SOCKS استخدم مضيفي onion. - استخدام وكيل SOCKS؟ + استخدام وسيط SOCKS؟ عندما تكون متاحة ستبقى جهات اتصالك متصلة. لا نقوم بتخزين أي من جهات اتصالك أو رسائلك (بمجرد تسليمها) على الخوادم. @@ -1205,7 +1205,7 @@ يجب عليك استخدام أحدث إصدار من قاعدة بيانات دردشتك على جهاز واحد فقط، وإلا فقد تتوقف عن تلقي الرسائل من بعض جهات الاتصال. سيتم استلام الفيديو عندما تكون جهة اتصالك متصلة بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! يمكنك مشاركة هذا العنوان مع جهات اتصالك للسماح لهم بالاتصال بـ%s. - أُزيلت %1$s + أزلت %1$s حدّث قاعدة بيانات دردشتك غير مُعمّاة - عيّن عبارة مرور لحمايتها. عبارة مرور قاعدة بيانات خاطئة @@ -1271,13 +1271,12 @@ لقد شاركت رابط لمرة واحدة متخفي عبر المتصفح يجب عليك إدخال عبارة المرور في كل مرة يبدأ فيها التطبيق - لا يتم تخزينها على الجهاز. - قم بالترقية وفتح الدردشة + رقِّ وافتح الدردشة رسالة الترحيب عبر رابط عنوان الاتصال ما لم يحذف جهة الاتصال الاتصال أو استُخدم هذا الرابط بالفعل، فقد يكون خطأ - الرجاء الإبلاغ عنه. \nللاتصال، يُرجى مطالبة جهة اتصالك بإنشاء رابط اتصال آخر والتحقق من أن لديك اتصال شبكة ثابت. - سيتم إرسال ملف تعريف الدردشة الخاص بك -\nإلى جهة اتصالك + سيتم إرسال ملف تعريف دردشتك\nإلى جهة اتصالك إلغاء الإخفاء ملفك التعريفي العشوائي ستستمر في استلام المكالمات والإشعارات من الملفات التعريفية المكتومة عندما تكون نشطة. @@ -1290,7 +1289,7 @@ رسالة صوتية رسالة صوتية… أنت مدعو إلى المجموعة - لا يمكنك إرسال رسائل! + أنت المراقب! تحتاج إلى السماح لجهة اتصالك بإرسال رسائل صوتية لتتمكن من إرسالها. أرسلت جهة اتصالك ملفًا أكبر من الحجم الأقصى المعتمد حاليًا (%1$s). الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> @@ -1301,17 +1300,17 @@ مع رسالة ترحيب اختيارية. قريباً! هذه الميزة ليست مدعومة بعد. جرب الإصدار القادم. - تعطيل (الاحتفاظ بتجاوزات المجموعة) - تفعيل لجميع المجموعات + عطّل (الاحتفاظ بتجاوزات المجموعة) + فعِّل لجميع المجموعات إرسال الإيصالات مفعّلة لـ%d مجموعات - تعطيل لجميع المجموعات + عطّل لجميع المجموعات الإيصالات مُعطَّلة %s: %s تضم هذه المجموعة أكثر من %1$d عضو، ولا يتم إرسال إيصالات التسليم. التوصيل مُعطَّل تعطيل الإيصالات للمجموعات؟ - تفعيل (الاحتفاظ بتجاوزات المجموعة) + فعّل (الاحتفاظ بتجاوزات المجموعة) تفعيل الإيصالات للمجموعات؟ لا توجد معلومات عن التسليم لا توجد دردشة محدّدة @@ -1321,7 +1320,7 @@ سيتم إرسال طلب الاتصال لعضو المجموعة هذا. اتصال متخفي استخدم ملف التعريف الحالي - تعطيل الإشعارات + عطّل الإشعارات افتح إعدادات التطبيق لا يمكن تشغيل SimpleX في الخلفية. ستستلم الإشعارات فقط عندما يكون التطبيق قيد التشغيل. سيتم مشاركة ملف تعريف عشوائي جديد. @@ -1336,12 +1335,11 @@ أنت دعوت جهة اتصال مسودة الرسالة %s و %s متصل - إظهار الرسائل الأخيرة + أظهر الرسائل الأخيرة %s، %s و %d أعضاء آخرين متصلون %s، %s و %s متصل سيتم تعمية قاعدة البيانات وتخزين عبارة المرور في الإعدادات. - يُخزين عبارة المرور العشوائية في الإعدادات كنص عادي. -\nيمكنك تغييره لاحقا. + يُخزين عبارة المرور العشوائية في الإعدادات كنص عادي.\nيمكنك تغييره لاحقًا. سيتم تحديث عبارة مرور تعمية قاعدة البيانات وتخزينها في الإعدادات. هل تريد إزالة عبارة المرور من الإعدادات؟ استخدم عبارة مرور عشوائية @@ -1351,9 +1349,9 @@ افتح مجلد قاعدة البيانات سيتم تخزين عبارة المرور في الإعدادات كنص عادي بعد تغييرها أو إعادة تشغيل التطبيق. عبارة المرور مخزنة في الإعدادات كنص عادي. - يُرجى الملاحظة: يتم توصيل مرحلات الرسائل والملفات عبر وكيل SOCKS. تستخدم المكالمات وإرسال معاينات الروابط الاتصال المباشر.]]> + يُرجى الملاحظة: يتم توصيل مُرحلات الرسائل والملفات عبر وسيط SOCKS. تستخدم المكالمات وإرسال معاينات الروابط الاتصال المباشر.]]> عَمِّ الملفات المحلية - عَمِّ الملفات والوسائط المخزنة + عمِّ الملفات والوسائط المخزنة تطبيق سطح المكتب الجديد! 6 لغات واجهة جديدة يُعمِّي الملفات المحلية الجديدة (باستثناء مقاطع الفيديو). @@ -1366,8 +1364,8 @@ أرسل رسالة مباشرة للاتصال وضع التخفي اصبح أسهل فعّل وضع التخفي عند الاتصال. - أرسل رسالة مباشرة - متصل مباشرةً + أرسل لاتصال + طُلب اتصال جارٍ الاتصال بالفعل! مجموعات أفضل و%d أحداث أخرى @@ -1398,7 +1396,7 @@ %d رسالة محظورة حظر العضو الجوّال متصل - حذف وإشعار جهة الاتصال + احذف وإشعار جهة الاتصال انتهى الاتصال اتصل بسطح المكتب قطع الاتصال @@ -1438,7 +1436,7 @@ %s و%s و%d عضو هذا الجهاز %1$d من الرسائل أُشرف عليها بواسطة %2$s - إلغاء حظر العضو + ألغِ حظر العضو %s قُطع اتصاله]]> في انتظار سطح المكتب… انضمام أسرع ورسائل أكثر موثوقية. @@ -1452,7 +1450,7 @@ فشلت إعادة التفاوض على التعمية. غير متوافق! ربط الجوّال - إزالة العضو + أزل العضو إلغاء حظر العضو؟ استخدم من سطح المكتب رمز الجلسة @@ -1470,15 +1468,15 @@ خطأ لقد شاركت مسار ملف غير صالح. أبلغ عن المشكلة لمطوري التطبيق. اسم غير صالح! - لصق عنوان سطح المكتب + ألصق عنوان سطح المكتب %1$s!]]> تحقق من الرمز مع سطح المكتب مسح رمز QR من سطح المكتب - إلغاء الحظر + ألغِ الحظر - إشعار اختياريًا جهات الاتصال المحذوفة.\n- أسماء الملفات التعريفية بمسافات.\n- و اكثر! مسار الملف غير صالح لقد طلبت بالفعل الاتصال عبر هذا العنوان! - إظهار وحدة التحكم في نافذة جديدة + أظهر وحدة التحكم في نافذة جديدة المسح من الجوّال تحقق من الاتصالات من فضلك، انتظر حتى يتم تحميل الملف من الجوّال المرتبط @@ -1525,7 +1523,7 @@ ابحث أو ألصِق رابط SimpleX بدء الدردشة؟ توقفت الدردشة. إذا كنت قد استخدمت قاعدة البيانات هذه بالفعل على جهاز آخر، فيجب عليك نقلها مرة أخرى قبل بدء الدردشة. - اعرض الأخطاء الداخلية + أظهر الأخطاء الداخلية خطأ فادح خطأ داخلي يُرجى إبلاغ المطورين بذلك: @@ -1558,7 +1556,7 @@ يحتوي سطح المكتب على رمز دعوة خاطئ سطح المكتب مشغول يحتوي سطح المكتب على إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين - العضو السابق %1$s + العضو %1$s وظيفة بطيئة خيارات المطور تغيّر العضو %1$s إلى %2$s @@ -1587,11 +1585,11 @@ أُنشئ في: %s رسالة محفوظة إلغاء حظر العضو للجميع؟ - إلغاء الحظر للجميع + ألغِ الحظر للجميع حدث خطأ أثناء حظر العضو للجميع حُظرت %d رسالة من قبل المُدير محظور %s - أُلغيت حظر %s + أُلغيَ حظر %s حظرت %s أُلغيت حظر %s محظور @@ -1608,16 +1606,16 @@ أنهيّ المكالمة متصفح الويب الافتراضي مطلوب للمكالمات. يُرجى تضبيط المتصفح الافتراضي في النظام، ومشاركة المزيد من المعلومات مع المطورين. حدث خطأ أثناء فتح المتصفح - أرشفة و رفع + أرشف وأرفع يمكن للمُدراء حظر عضو للجميع. ترحيل بيانات التطبيق جارِ أرشفة قاعدة البيانات - جميع جهات الاتصال، المحادثات والملفات الخاصة بك سيتم تشفيرها بأمان ورفعها على شكل أجزاء إلى موجهات XFTP المُعدة. + جميع جهات الاتصال، المحادثات والملفات الخاصة بك سيتم تعميتها بأمان ورفعها على شكل أجزاء إلى موجهات XFTP المُعدة. طبّق يُرجى ملاحظة: استخدام نفس قاعدة البيانات على جهازين سيؤدي إلى كسر فك تعمية الرسائل من اتصالاتك، كحماية أمنية.]]> تحذير: سيتم حذف الأرشيف.]]> - التعمية بين الطرفين مع توفير السرية التامة لإعادة التوجيه والرفض واستعادة عمليات الاقتحام.]]> - التعمية بين الطرفين المقاوم للكم مع توفير السرية التامة لإعادة التوجيه والرفض واستعادة عمليات الاقتحام.]]> + التعمية بين الطرفين مع توفير السرية التامة لإعادة التوجيه والرفض واستعادة عمليات الاقتحام.]]> + التعمية بين الطرفين المقاوم للكم مع توفير السرية التامة لإعادة التوجيه والرفض واستعادة عمليات الاقتحام.]]> هذه الدردشة محمية بالتعمية بين الطرفين. هذه الدردشة محمية بالتعمية بين الطرفين المقاوم للكم. افتح شاشة الترحيل @@ -1694,7 +1692,7 @@ رحّل من جهاز آخر على الجهاز الجديد و امسح رمز QR ضوئيًا.]]> تحذير: بدء الدردشة على أجهزة متعددة غير مدعوم وسيؤدي إلى فشل تسليم الرسائل إتصال شبكة - محوّلة + مُحوّلة خلوي إيثرنت سلكية لا إتصال شبكة @@ -1704,15 +1702,15 @@ الرسائل الصوتية غير مسموح بها روابط SimpleX السماح بإرسال روابط SimpleX. - منع إرسال روابط SimpleX + امنع إرسال روابط SimpleX كل الأعضاء يمكن للأعضاء إرسال روابط SimpleX. - روابط SimpleX محظورة. + روابط SimpleX ممنوعة. المُدراء مفعّل لـ المالكون الملفات والوسائط غير مسموح بها - محوّلة + مُحوّلة حوّل الرسالة… تلقي التزامن لا يستطيع المُستلم/ون معرفة مَن أرسل هذه الرسالة. @@ -1722,7 +1720,7 @@ السماعة سماعة الأذن سماعات الرأس - محوّلة مِن + مُحوّلة مِن حُفظت نزّل حوّل @@ -1741,13 +1739,11 @@ عنوان الخادم غير متوافق مع إعدادات الشبكة. إصدار الخادم غير متوافق مع إعدادات الشبكة. مفتاح خاطئ أو اتصال غير معروف - على الأرجح حُذف هذا الاتصال. - تم تجاوز السعة - لم يتلق المُستلم الرسائل المُرسلة مسبقًا. + تجاوزت السعة - لم يتلق المُستلم الرسائل المُرسلة مسبقًا. خطأ في خادم الوجهة: %1$s خطأ: %1$s - خادم التحويل: %1$s -\nخطأ في الخادم الوجهة: %2$s - خادم التحويل: %1$s -\nخطأ: %2$s + خادم التحويل: %1$s\nخطأ في خادم الوجهة: %2$s + خادم التحويل: %1$s\nخطأ: %2$s تحذير تسليم الرسالة مشكلات الشبكة - انتهت صلاحية الرسالة بعد عِدة محاولات لإرسالها. نعم @@ -1772,7 +1768,7 @@ استخدم دائمًا التوجيه الخاص. الملفات مطلقًا - سيطلب التطبيق تأكيد التنزيلات من خوادم ملفات غير معروفة (باستثناء .onion أو عند تفعيل وكيل SOCKS). + سيطلب التطبيق تأكيد التنزيلات من خوادم ملفات غير معروفة (باستثناء .onion أو عند تفعيل وسيط SOCKS). أرسل الرسائل مباشرة عندما يكون عنوان IP محميًا ولا يدعم الخادم الوجهة لديك التوجيه الخاص. خوادم غير معروفة خوادم غير معروفة! @@ -1832,12 +1828,12 @@ خطأ في الملف خطأ في الملف مؤقت حالة الرسالة - لم يتم العثور على الملف - على الأرجح حُذف الملف أو إلغاؤه. + لم يُعثر على الملف - على الأرجح حُذف الملف أو أُلغيَ. حالة الملف حالة الملف: %s حالة الرسالة: %s خطأ في النسخ - تم استخدام هذا الرابط مع جهاز محمول آخر، يُرجى إنشاء رابط جديد على سطح المكتب. + استُخدم هذا الرابط مع جوّال آخر، يُرجى إنشاء رابط جديد على سطح المكتب. يُرجى التحقق من اتصال الهاتف المحمول وسطح المكتب بنفس الشبكة المحلية، وأن جدار حماية سطح المكتب يسمح بالاتصال. \nيُرجى مشاركة أي مشاكل أُخرى مع المطورين. لا يمكن إرسال الرسالة @@ -1847,13 +1843,13 @@ أرسلت الإجمالي الحجم الملفات المرفوعة - يُرجى المحاولة لاحقا. + يُرجى المحاولة لاحقًا. خطأ في التوجيه الخاص عنوان الخادم غير متوافق مع إعدادات الشبكة: %1$s. إصدار الخادم غير متوافق مع تطبيقك: %1$s. العضو غير نشط - رسالة محوّلة - لا يوجد اتصال مباشر حتى الآن، يتم تحويل من قِبل المُدير. + رسالة مُحوّلة + لا يوجد اتصال مباشر حتى الآن، الرسالة مُحوّلة بواسطة المُدير. امسح / ألصِق الرابط خوادم SMP المهيأة خوادم SMP أخرى @@ -1868,13 +1864,13 @@ ثُبّت بنجاح افتح مكان الملف يُرجى إعادة تشغيل التطبيق. - تذكر لاحقا + تذكر لاحقًا تخطي هذه النسخة أُلغيت تنزيل التحديث مُعطَّل غير نشط معلومات الخوادم - عرض المعلومات ل + يُظهر معلومات ل الأخطاء الرسائل المُرسلة الإجمالي @@ -1910,7 +1906,7 @@ مؤمن أرسل الأخطاء أُرسلت مباشرةً - مُرسَل عبر الوكيل + مُرسَل عبر الوسيط مشترك أخطاء الاشتراك رفع الأخطاء @@ -1952,16 +1948,16 @@ أعِد توصيل الخوادم؟ عنوان الخادم الإحصائيات - تم تجاهل الاشتراكات + تجاهلت الاشتراكات جلسات النقل لكي يتم إعلامك بالإصدارات الجديدة، شغّل الفحص الدوري للإصدارات المستقرة أو التجريبية. أنت غير متصل بهذه الخوادم. يتم استخدام التوجيه الخاص لتسليم الرسائل إليهم. قرّب - حدث خطأ أثناء الاتصال بخادم التحويل %1$s. يُرجى المحاولة لاحقا. + حدث خطأ أثناء الاتصال بخادم التحويل %1$s. يُرجى المحاولة لاحقًا. عنوان خادم التحويل غير متوافق مع إعدادات الشبكة: %1$s. عنوان خادم الوجهة %1$s غير متوافق مع إعدادات خادم التحويل %2$s. إصدار الخادم الوجهة %1$s غير متوافق مع خادم التحويل %2$s. - فشل خادم التحويل %1$s في الاتصال بالخادم الوجهة %2$s. يُرجى المحاولة لاحقا. + فشل خادم التحويل %1$s في الاتصال بالخادم الوجهة %2$s. يُرجى المحاولة لاحقًا. إصدار خادم التحويل غير متوافق مع إعدادات الشبكة: %1$s. مطفي قوي @@ -2011,7 +2007,7 @@ خوادم الوسائط والملفات خوادم الرسائل متابعة - وكيل SOCKS + وسيط SOCKS يمكنك ترحيل قاعدة البيانات المُصدرة. يمكنك حفظ الأرشيف المُصدر. حالة الاتصال والخوادم. @@ -2046,35 +2042,35 @@ لم يتم تنزيل %1$d ملف/ات. نزّل شارك ملف التعريف - استخدم بيانات اعتماد الوكيل المختلفة لكل اتصال. + استخدم بيانات اعتماد الوسيط المختلفة لكل اتصال. اسم المستخدم قد يتم إرسال بيانات اعتمادك غير مُعمَّاة. - خطأ في حفظ الوكيل + خطأ في حفظ الوسيط إزالة الأرشيف؟ وضع النظام سيتم إزالة أرشيف قاعدة البيانات المرفوعة نهائيًا من الخوادم. - استخدم بيانات اعتماد الوكيل المختلفة لكل ملف تعريف. + استخدم بيانات اعتماد الوسيط المختلفة لكل ملف تعريف. استخدم بيانات اعتماد عشوائية قاعدة بيانات الدردشة حُذف %1$d ملف/ات. لا يزال يتم تنزيل %1$d ملف/ات. - لا تستخدم بيانات الاعتماد مع الوكيل. + لا تستخدم بيانات الاعتماد مع الوسيط. خطأ في تحويل الرسائل خطأ في تبديل ملف التعريف حدد ملف تعريف الدردشة - لقد تم نقل اتصالك إلى %s ولكن حدث خطأ غير متوقع أثناء إعادة توجيهك إلى ملف التعريف. + نُقل اتصالك إلى %s ولكن حدث خطأ عند تبديل ملف التعريف. تحويل %1$s رسالة؟ لم يحوّل %1$s من الرسائل جارِ تحويل %1$s رسالة حوّل الرسائل… تحويل الرسائل بدون ملفات؟ جارِ حفظ %1$s رسالة - تأكد من صحة تضبيط الوكيل. + تأكد من صحة تضبيط الوسيط. %1$d خطأ في ملف آخر. حُذفت الرسائل بعد تحديدها. لا يوجد شيء لتحويله! كلمة المرور - استيثاق الوكيل + استيثاق الوسيط سيتم حذف الرسائل - لا يمكن التراجع عن هذا! الصوت مكتوم حدث خطأ أثناء تهيئة WebView. تأكد من تثبيت WebView وأن بنيته المدعومة هي arm64.\nالخطأ: %s @@ -2185,7 +2181,7 @@ شارك عنوان SimpleX على وسائل التواصل الاجتماعي. عنوان SimpleX والروابط لمرة واحدة آمنة للمشاركة عبر أي برنامج مُراسلة. انقر فوق أنشئ عنوان SimpleX في القائمة لإنشائه لاحقًا. - حُذفت هذه الرسالة أو لم يتم استلامها بعد. + حُذفت هذه الرسالة أو لم تُستلم بعد. استخدم للرسائل يحمي التطبيق خصوصيتك من خلال استخدام مُشغلين مختلفين في كل محادثة. %s.]]> @@ -2197,7 +2193,7 @@ أضف أعضاء الفريق أضف أصدقاء أضف أعضاء فريقك إلى المحادثات. - يُحظر إرسال الرسائل المباشرة بين الأعضاء في هذه الدردشة. + يُمنع إرسال الرسائل المباشرة بين الأعضاء في هذه الدردشة. أجهزة Xiaomi: يُرجى تفعيل التشغيل التلقائي (Autostart) في إعدادات النظام لكي تعمل الإشعارات.]]> مُعمَّاة بين الطرفين، مع أمان ما بعد الكم في الرسائل المباشرة.]]> تحقق من الرسائل كل 10 دقائق @@ -2245,7 +2241,7 @@ لا دردشات لا توجد محادثات في القائمة %s. لا توجد محادثات غير مقروءة - لم يتم العثور على أي محادثات + لم يُعثر على أي محادثات المفضلات أضف القائمة الكل @@ -2323,7 +2319,7 @@ ذّكورات غير مقروءة يمكنك ذكر ما يصل إلى %1$s من الأعضاء في الرسالة الواحدة! السماح بالإبلاغ عن الرسائل إلى المشرفين. - منع الإبلاغ عن الرسائل للمشرفين. + امنع الإبلاغ عن الرسائل للمشرفين. أرشفة كافة البلاغات؟ أرشف البلاغات لكل المشرفين @@ -2366,7 +2362,6 @@ لا يمكن الوصول إلى الدردشات الخاصة والمجموعات وجهات اتصالك لمشغلي الخادم. باستخدام SimpleX Chat، توافق على:\n- إرسال المحتوى القانوني فقط في المجموعات العامة.\n- احترام المستخدمين الآخرين – لا سبام. اقبل - استخدم روابط قصيرة (تجريبي) يتطلب هذا الرابط إصدار تطبيق أحدث. يُرجى ترقية التطبيق أو اطلب من جهة اتصالك إرسال رابط متوافق. رابط كامل رابط قصير @@ -2376,4 +2371,150 @@ إيقاف التشغيل الخوادم المُعدة مسبقًا جميع الخوادم + خطأ في قبول العضو + دردشة واحدة مع عضو + %d دردشة/ات + %d دردشات مع الأعضاء + %d رسائل + أُرسِل البلاغ للمشرفين + يمكنك عرض تقاريرك في \"دردش مع المُدراء\". + اقبل كمراقب + حُذفت جهة الاتصال + عُطِّلت جهة الاتصال + جهة الاتصال غير جاهزة + لا يمكنك إرسال الرسائل! + غير متزامن + قبلت %1$s + قبِلك + لقد قبلت هذا العضو. + الرجاء الانتظار ريثما يراجع مشرفو المجموعة طلبك للانضمام إليها. + دردش مع المُدراء + راجع الأعضاء + غير مفعّل + اقبل + عضو جديد يريد الانضمام للمجموعة. + الكل + دخول العضو + راجع الأعضاء قبل القبول (الطرق). + حدث خطأ أثناء حذف الدردشة مع العضو. + لا يمكن إرسال الرسائل + رُفض طلب الانضمام + غادرت + حُذفت المجموعة + أُزيل من المجموعة + حفظ إعدادات القبول؟ + قيد المراجعة + مراجعة + دردش مع عضو + حدّد دخول العضو + دردش مع المُدراء + احذف الدردشة + حذف الدردشة مع العضو؟ + ارفض + اقبل كعضو + اقبل العضو + سينضم العضو إلى المجموعة، هل تقبل العضو؟ + رفض العضو؟ + دردشات مع الأعضاء + رُجع من قِبل المُدراء + لا دردشات مع الأعضاء + العضو لديه نُسخة قديمة + رقِّ العنوان + اقبل طلب جهة الاتصال + أضف رسالة + التعمية بين الطرفين.]]> + إلا بعد قبول طلبك.]]> + اتصل + يجب أن يقبل جهة الاتصال… + خطأ في تغيير مستخدم جهة الاتصال + خطأ في فتح الدردشة + خطأ في فتح المجموعة + خطأ في رفض طلب جهة الاتصال + انضم للمجموعة + افتح الدردشة + افتح دردشة جديدة + افتح مجموعة جديدة + افتح للقبول + افتح لاتصال + افتح لانضمام + سيكون العنوان قصيراً، وسيتم مشاركة ملف تعريفك عبر العنوان. + ارفض طلب جهة الاتصال + أُرسل الطلب + إرسال طلب جهة اتصال؟ + أرسل طلب + أرسل طلب دون رسالة + أُرسل إلى جهة اتصالك بعد الاتصال. + ترقية رابط المجموعة؟ + رقِّ + ترقية العنوان؟ + لن يتم إشعار المرسل. + رسالة الترحيب + ملف تعريفك + لا يمكن تغيير ملف التعريف + لاستخدام ملف تعريف آخر بعد محاولة الاتصال، احذف الدردشة واستخدم الرابط مرة أخرى. + الدردشة مع الالمُدراء + الدردشة مع الأعضاء قبل انضمامهم. + اتصل بشكل أسرع! 🚀 + تقليل حركة البيانات على شبكات الجوّال. + راسل فورًا بمجرد النقر على \"اتصل\". + دور جديد للمجموعة: مشرف + لا توجد جلسة توجيه خاصة + انتهت مهلة التوجيه الخاص + انتهت مهلة خلفية البروتوكول + يزيل الرسائل ويحظر الأعضاء. + مراجعة أعضاء المجموعة + أرسل ملاحظاتك الخاصة إلى المجموعات. + مهلة اتصال TCP في الخلفية + جارِ تحميل ملف التعريف… + السيرة الذاتية: + وصف قصير: + نبذتك: + النبذة كبيرة جدًا + الوصف كبير جدًا + اقبل طلب جهة الاتصال + اتصال عمل + مجموعة + انقر على \"اتصل\" للدردشة + انقر على \"اتصل\" لإرسال الطلب + انقر على \"انضم للمجموعة\" + جهة عمل اتصالك + جهة اتصالك + مجموعتك + وقت الاختفاء يتم تعيينه للجهات الاتصال الجديدة فقط. + استخدم ملف تعريف متخفي + 4 لغات واجهة جديدة + الكتالانية والإندونيسية والرومانية والفيتنامية - بفضل مستخدمينا! + أنشئ عنوانك + فعّل الرسائل ذاتية الاختفاء افتراضيًا. + أبقِ محادثاتك نظيفة + عيّن نبذة عن الملف التعريف ورسالة الترحيب. + شارك عنوانك + عنوان SimpleX قصير + حدّث عنوانك + رحب بجهات اتصالك 👋 + شارك العنوان القديم + شارك الرابط القديم + سيكون الرابط قصيراً، وسيتم مشاركة الملف التعريفي للمجموعة عبر الرابط. + رقِّ رابط المجموعة + طلبات الاتصال من المجموعات + حُذف العضو - لا يمكن قبول الطلب + طُلب اتصال من المجموعة %1$s + هذا الإعداد لملف تعريفك الحالي + اسمح بالملفات والوسائط فقط إذا سمح جهة اتصالك بذلك. + اسمح لجهات اتصالك بإرسال الملفات والوسائط. + بوت + يمكنك أنت وجهة اتصالك إرسال الملفات والوسائط. + يُمنع إرسال الملفات والوسائط في هذه الدردشة. + يمكنك أنت فقط إرسال الملفات والوسائط. + يمكن لجهة اتصالك فقط إرسال الملفات والوسائط. + افتح لاستخدام البوت + امنع إرسال الملفات والوسائط. + انقر على \"اتصل\" لاستخدام البوت + لإرسال الأوامر يجب أن تكون متصلاً. + خيارات مهملة + افتح الرابط النظيف + افتح الرابط الكامل + أزل تتبع الروابط + رابط مُرحل SimpleX + خطأ في وضع علامة \"مقروءة\" على الدردشة مع العضو diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 6726009a5f..b13e3f4046 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -9,10 +9,15 @@ Join group? Use current profile Use new incognito profile + Use incognito profile Your profile will be sent to the contact that you received this link from. You will connect to all group members. Connect Connect incognito + Open chat + Open new chat + Open group + Open new group Invalid link Please check that SimpleX link is correct. @@ -63,6 +68,7 @@ Decryption error Encryption re-negotiation error + end-to-end encryption.]]> end-to-end encryption with perfect forward secrecy, repudiation and break-in recovery.]]> quantum resistant e2e encryption with perfect forward secrecy, repudiation and break-in recovery.]]> This chat is protected by end-to-end encryption. @@ -92,13 +98,13 @@ SimpleX one-time invitation SimpleX group link SimpleX channel link + SimpleX relay link via %1$s SimpleX links Description Full link Via browser Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red. - Use short links (BETA) Spam @@ -140,13 +146,18 @@ Connection timeout Connection error + Fingerprint in server address does not match certificate: %1$s. Please check your network connection with %1$s and try again. Server address is incompatible with network settings: %1$s. Server version is incompatible with your app: %1$s. + Private routing timeout Private routing error + No private routing session + Fingerprint in forwarding server address does not match certificate: %1$s. Error connecting to forwarding server %1$s. Please try later. Forwarding server address is incompatible with network settings: %1$s. Forwarding server version is incompatible with network settings: %1$s. + Fingerprint in destination server address does not match certificate: %1$s. Forwarding server %1$s failed to connect to destination server %2$s. Please try later. Destination server address of %1$s is incompatible with forwarding server %2$s settings. Destination server version of %1$s is incompatible with forwarding server %2$s. @@ -158,6 +169,9 @@ Error loading details Error adding member(s) Error joining group + Error accepting member + Error marking read + Error deleting chat Cannot receive file Sender cancelled file transfer. Unknown servers! @@ -179,6 +193,7 @@ Undelivered messages The connection reached the limit of undelivered messages, your contact may be offline. Error accepting contact request + Error rejecting contact request Sender may have deleted the connection request. Error deleting contact Error deleting group @@ -189,9 +204,9 @@ Error aborting address change Error synchronizing connection Test failed at step %s. - Server requires authorization to create queues, check password - Server requires authorization to upload, check password - Possibly, certificate fingerprint in server address is incorrect + Server requires authorization to create queues, check password. + Server requires authorization to upload, check password. + Fingerprint in server address does not match certificate. Error setting address Error Connect @@ -211,6 +226,9 @@ Error updating chat list Error creating chat list Error loading chat lists + Error opening chat + Error opening group + Error changing profile Instant notifications @@ -419,9 +437,10 @@ Chats Settings connecting… - send direct message - you are invited to group - join as %s + send to connect + Open to join + You are invited to group + Join as %s rejected connecting… Tap to start a new chat @@ -434,6 +453,10 @@ No chats No chats found Tap to Connect + Open to connect + Open to use bot + Open to accept + contact should accept… Connect with %1$s? Search or paste SimpleX link Tap Create SimpleX address in the menu to create it later. @@ -464,6 +487,23 @@ 1 report %d reports Member reports + %d messages + %d chats with members + 1 chat with a member + %d chat(s) + + + Tap Connect to chat + Tap Connect to send request + Tap Connect to use bot + Accept contact request + Your contact + Bot + Tap Join group + Your group + Group + Business connection + Your business contact Share message… @@ -486,7 +526,6 @@ Decoding error The image cannot be decoded. Please, try a different image or contact developers. The video cannot be decoded. Please, try a different video or contact developers. - you are observer Files and media prohibited! Only group owners can enable files and media. Send direct message to connect @@ -505,9 +544,19 @@ Report violation: only group moderators will see it. Report content: only group moderators will see it. Report other: only group moderators will see it. + Report sent to moderators + You can view your reports in Chat with admins. + Join group + Add message + Connect + Send contact request? + only after your request is accepted.]]> + Send request without message + Send request You can\'t send messages! contact not ready + request is sent contact deleted not synchronized contact disabled @@ -518,6 +567,10 @@ removed from group you left can\'t send messages + you are observer + reviewed by admins + member has old version + To send commands you must be connected. Image @@ -684,6 +737,12 @@ Accept Accept incognito Reject + Accept contact request + Reject contact request + The sender will NOT be notified. + + + Member is deleted - can\'t accept request Clear chat? @@ -815,7 +874,7 @@ Share profile Select chat profile Error switching profile - Your connection was moved to %s but an unexpected error occurred while redirecting you to the profile. + Your connection was moved to %s but an error happened when switchig profile. Or scan QR code Keep unused invitation? You can view invitation link again in connection details. @@ -826,6 +885,7 @@ Paste the link you received The text you pasted is not a SimpleX link. Tap to paste link + Loading profile… Invalid QR code The code you scanned is not a SimpleX link QR code. @@ -834,6 +894,11 @@ No filtered contacts Your contacts + + Your profile + Can\'t change profile + To use another profile after connection attempt, delete the chat and use the link again. + Scan code Incorrect security code! @@ -1012,6 +1077,7 @@ Enable logs Database IDs and Transport isolation option. Developer options + Deprecated options Show internal errors Show slow API calls Shutdown? @@ -1034,9 +1100,11 @@ Stop sharing address? Stop sharing Auto-accept + Sent to your contact after connection. + Welcome message Enter welcome message… (optional) Save settings? - Save auto-accept settings + Save SimpleX address settings Delete address Invite friends Let\'s talk in SimpleX Chat @@ -1048,6 +1116,15 @@ Address settings Business address Add your team members to the conversations. + Upgrade address + Upgrade address? + The address will be short, and your profile will be shared via the address. + Upgrade + Upgrade group link + Upgrade group link? + The link will be short, and group profile will be shared via the link. + Share old address + Share old link Continue @@ -1059,10 +1136,13 @@ Profile name: Full name: + Bio: + Bio too large Your current profile Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Edit image Delete image + Save admission settings? Save preferences? Save and notify contact Save and notify contacts @@ -1087,6 +1167,7 @@ The profile is only shared with your contacts. Display name cannot contain whitespace. Enter your name: + Your bio: Create Create profile Create @@ -1293,6 +1374,7 @@ The app will ask to confirm downloads from unknown file servers (except .onion or when SOCKS proxy is enabled). Without Tor or VPN, your IP address will be visible to file servers. Send link previews + Remove link tracking Show last messages Message draft App data backup @@ -1327,6 +1409,7 @@ An empty chat profile with the provided name is created, and the app opens as usual. If you enter this passcode when opening the app, all app data will be irreversibly removed! Set passcode + This setting is for your current profile These settings are for your current profile They can be overridden in contact and group settings. Contacts @@ -1358,6 +1441,8 @@ Ask Open web link? Open link + Open full link + Open clean link YOU @@ -1370,6 +1455,7 @@ CHATS FILES SEND DELIVERY RECEIPTS TO + CONTACT REQUESTS FROM GROUPS Restart Shutdown Developer tools @@ -1578,10 +1664,13 @@ deleted contact + requested connection from group %1$s invited %1$s connected + accepted %1$s + accepted you left changed role of %s to %s blocked %s @@ -1592,7 +1681,8 @@ deleted group updated group profile invited via your group link - connected directly + requested connection + New member wants to join the group. you changed role of %s to %s you changed role for yourself to %s you blocked %s @@ -1600,6 +1690,8 @@ you removed %1$s you left group profile updated + you accepted this member + Please wait for group moderators to review your request to join the group. %s connected %s and %s connected @@ -1659,6 +1751,8 @@ invited pending approval pending + pending review + review connecting (introduced) connecting (introduction invitation) connecting (accepted) @@ -1670,7 +1764,7 @@ connecting unknown - Past member %1$s + Member %1$s No contacts to add @@ -1729,6 +1823,7 @@ Receipts are disabled This group has over %1$d members, delivery receipts are not sent. Invite + Chat with admins FOR CONSOLE @@ -1765,7 +1860,7 @@ Remove member? Remove members? Remove member - + Chat with member Send direct message Member will be removed from group - this cannot be undone! Members will be removed from group - this cannot be undone! @@ -1856,6 +1951,8 @@ Fully decentralized – visible only to members. Enter group name: Group full name: + Short description: + Description too large Your chat profile will be sent to group members Your chat profile will be sent to chat members Create group @@ -1921,7 +2018,9 @@ Reset to defaults sec TCP connection timeout + TCP connection bg timeout Protocol timeout + Protocol background timeout Protocol timeout per KB Receiving concurrency PING interval @@ -2042,6 +2141,7 @@ Contact preferences Group preferences Set group preferences + Set member admission Your preferences Disappearing messages Direct messages @@ -2062,6 +2162,7 @@ Set 1 day Allow your contacts to send disappearing messages. Allow disappearing messages only if your contact allows them. + Time to disappear is set only for new contacts. Prohibit sending disappearing messages. Allow your contacts to irreversibly delete sent messages. (24 hours) Allow irreversible message deletion only if your contact allows it to you. (24 hours) @@ -2069,6 +2170,9 @@ Allow your contacts to send voice messages. Allow voice messages only if your contact allows them. Prohibit sending voice messages. + Allow your contacts to send files and media. + Allow files and media only if your contact allows them. + Prohibit sending files and media. Allow your contacts adding message reactions. Allow message reactions only if your contact allows them. Prohibit message reactions. @@ -2087,6 +2191,10 @@ Only you can send voice messages. Only your contact can send voice messages. Voice messages are prohibited in this chat. + Both you and your contact can send files and media. + Only you can send files and media. + Only your contact can send files and media. + Files and media are prohibited in this chat. Both you and your contact can add message reactions. Only you can add message reactions. Only your contact can add message reactions. @@ -2159,6 +2267,29 @@ owners Enabled for + + Member admission + Review members + Review members before admitting ("knocking"). + off + all + + + Chats with members + No chats with members + Delete chat + Delete chat with member? + + + Chat with admins + Reject + Reject member? + Accept + Accept member + Member will join the group, accept member? + Accept as member + Accept as observer + What\'s new New in %s @@ -2335,6 +2466,25 @@ Better groups performance Faster sending messages. Faster deletion of groups. + Connect faster! 🚀 + Message instantly once you tap Connect. + Review group members + Chat with members before they join. + Chat with admins + Send your private feedback to groups. + New group role: Moderator + Removes messages and blocks members. + Less traffic on mobile networks. + Welcome your contacts 👋 + Set profile bio and welcome message. + Keep your chats clean + Enable disappearing messages by default. + Short SimpleX address + Create your address + Update your address + Share your address + 4 new interface languages + Catalan, Indonesian, Romanian and Vietnamese - thanks to our users! View updated conditions @@ -2450,7 +2600,6 @@ You have already requested connection via this address! Join your group? %1$s!]]> - Open group Repeat join request? Group already exists! Chat already exists! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index c37d4e6924..bf04fd6a1e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -57,7 +57,7 @@ Спри визуализацията на файла променена е вашата ролята на %s Спри визуализацията на изображението - присъединяване като %s + Присъединяване като %s Показване само на контакт Не може да се осъществи достъп до Keystore, за да се запази паролата на базата данни Файлът не може да бъде получен @@ -67,7 +67,7 @@ ново съобщение Визуализация Показване на контакт и съобщение - вие сте поканени в групата + Вие сте поканени в групата Показване на визуализация Услуга за известията Позволи @@ -430,7 +430,7 @@ Избери файл Ако сте получили линк за покана за SimpleX Chat, можете да го отворите във вашия браузър: Сканирай QR код.]]> - Изтрий линк за предстоящата връзка? + Изтрий линк за чакаща връзка? Електронна поща Сподели еднократен линк Линк за еднократна покана @@ -592,9 +592,9 @@ Грешка при изтриване на контакт Грешка при изтриване на заявка за контакт Грешка при изтриване на група - Грешка при изтриване на предстоящата контактна връзка + Грешка при изтриване на чакащата контактна връзка Грешка при запазване на ICE сървърите - Предстояща връзка със сървъра + Чакаща връзка със сървъра Вашият контакт трябва да бъде онлайн, за да осъществите връзката. \nМожете да откажете тази връзка и да премахнете контакта (и да опитате по -късно с нов линк). Грешка при експортиране на базата данни @@ -788,7 +788,7 @@ Маркирай като проверено %s не е потвърдено %s е потвърдено - Оценете приложението + Оцени приложението Уверете се, че адресите на WebRTC ICE сървъра са в правилен формат, разделени на редове и не са дублирани. Мрежа и сървъри Разширени настройки @@ -895,14 +895,14 @@ Поканени сте в групата. Присъединете се, за да се свържете с членове на групата. Вие се присъединихте към тази група напусна - отстранен %1$s + премахнат %1$s ви острани напусна член наблюдател собственик кодът за сигурност е променен - отстранен + премахнат Нова членска роля Няма контакти за добавяне Напусни групата @@ -1006,13 +1006,13 @@ Сканирай код Сканирайте кода за сигурност от приложението на вашия контакт. Код за сигурност - Изпращайте въпроси и идеи + Изпрати въпроси и идеи Запази сървърите\? Запазените WebRTC ICE сървъри ще бъдат премахнати. Запази SOCKS прокси настройки Покажи опциите за разработчици - Запази настройките за автоматично приемане + Запази настройките за SimpleX адрес Запази настройките\? Запази паролата на профила Спри чата\? @@ -1056,7 +1056,7 @@ Нулиране Изпрати Започни нов чат - Изпратете ни имейл + Изпрати ни мейл Някои сървъри не минаха теста: Изключване\? таен @@ -1083,7 +1083,7 @@ Отзови файл Изпращането на файла ще бъде спряно. Запази сървърите - Изпратете съобщение на живо - то ще се актуализира за получателя(ите), докато го пишете + Изпрати съобщение на живо - то ще се актуализира за получателя(ите), докато го пишете Тестът на сървъра е неуспешен! Оценка на сигурността Сподели файл… @@ -1173,7 +1173,7 @@ Напълно децентрализирана – видима е само за членовете. Транспортна изолация Благодарение на потребителите – допринесете през Weblate! - Хешът на предишното съобщение е различен.\" + Хешът на предишното съобщение е различен. Тествай сървърите Благодарение на потребителите – допринесете през Weblate! За да не се разкрива часовата зона, файловете с изображения/глас използват UTC. @@ -1201,8 +1201,8 @@ Ще бъдете свързани, когато заявката ви за връзка бъде приета, моля, изчакайте или проверете по-късно! Ще бъдете свързани, когато устройството на вашия контакт е онлайн, моля, изчакайте или проверете по-късно! Няма да загубите контактите си, ако по-късно изтриете адреса си. - Вашите настройки - Вашият адрес в SimpleX + Настройки + SimpleX адрес Използвай за нови връзки Вашите XFTP сървъри Използвай сървърите на SimpleX Chat\? @@ -1250,7 +1250,7 @@ Добре дошли! Добре дошли %1$s! Нямате чатове - Не може да изпращате съобщения! + вие сте наблюдател Вашият сървър Използвай сървър Вашият адрес на сървъра @@ -1260,7 +1260,7 @@ Вие контролирате своя чат! Грешна парола! Гласови съобщения - Вашите настройки + Настройки Какво е новото Вашите контакти могат да позволят пълното изтриване на съобщението. Актуализирай паролата на базата данни @@ -1375,8 +1375,8 @@ Отвори Грешка при създаване на контакт с член Изпрати лично съобщение за свързване - изпрати лично съобщение - свързан директно + изпрати за свързване + заявка за връзка Разшири Блокиране на членове на групата Изпрати отново заявката за свързване? @@ -1532,7 +1532,7 @@ Докосни за сканиране Запази Докосни за поставяне на линк за връзка - Търсене или поставяне на SimpleX линк + Търси или постави SimpleX линк Стартирай чата? Чатът е спрян. Ако вече сте използвали тази база данни на друго устройство, трябва да я прехвърлите обратно, преди да стартирате чата отново. Настолното устройство има грешен код за връзка @@ -1559,7 +1559,7 @@ Настолното устройство е заето Връзката с настолното устройство бе прекъсната Критична грешка - Бивш член %1$s + Член %1$s неизвестен статус неизвестен Вътрешна грешка @@ -1851,7 +1851,7 @@ Грешен ключ или неизвестен адрес на файлово парче - най-вероятно файлът е изтрит. Файлов статус: %s Всички профили - Започвайки от %s.\nВсички данни се съхраняват поверително на вашето устройство.. + Започвайки от %s.\nВсички данни се съхраняват поверително на вашето устройство. Свържете отново сървъра, за да принудите доставката на съобщенията. Това използва допълнителен трафик. Грешка при нулиране на статистиката Сканирай / Постави линк @@ -1859,7 +1859,7 @@ Размер на шрифта Статус на съобщението: %s Задай тема по подразбиране - Потвърдете файловете от неизвестни сървъри. + Потвърди файлове от неизвестни сървъри. Изпратени съобщения Грешка при стартиране на WebView. Актуализирайте системата си до новата версия. Моля, свържете се с разработчиците.\nГрешка: %s Други XFTP сървъри @@ -1894,7 +1894,7 @@ неактивен Тъмен режим Активни връзки - Предстоящи + Чакащи Свързани сървъри Общо деактивирано @@ -1967,7 +1967,7 @@ Качени файлове Грешки при качване Размер - Парчета изтрити + Изтрити парчета Изтеглени парчета Изтеглени файлове Настройки @@ -2033,7 +2033,7 @@ Контактът е изтрит.. Трябва да разрешите на вашия контакт да може да ви се обажда, за да можете и вие да се обаждате. Постави линк - Връзката ви беше преместена към %s, но възникна неочаквана грешка при пренасочването ви към профила. + Връзката ви беше преместена към %s, но възникна грешка при превключване на профила. Сесия на приложението Сървър Изтегли %s (%s) @@ -2065,7 +2065,7 @@ Покани Звукът е заглушен Панели на приложението - Изпратете съобщение за да се активират обажданията. + Изпрати съобщение за да се активират обажданията. търсене видео Контактът е изтрит! @@ -2157,7 +2157,7 @@ Личните съобщения между членовете са забранени в този чат. Персонализирана форма на съобщенията. По-добри дати на съобщението. - Активиране на flux + Активиране на Flux в настройките Мрежа и сървъри за по -добра поверителност на метаданните. за по-добра поверителност на метаданните. Подобрена навигация в чата Грешка при запазване на базата данни @@ -2171,7 +2171,7 @@ Вижте актуализираните условия Xiaomi устройства : моля, активирайте Autostart в системните настройки, за да работят известията.]]> Няма съобщение - Или да се сподели лично + Или сподели лично криптирани от край до край, с постквантова сигурност в директните съобщения.]]> Без фонова услуга Проверявай за съобщения на всеки 10 минути @@ -2270,4 +2270,259 @@ Ще спрете да получавате съобщения от този чат. Историята на чата ще бъде запазена. Добави членове на екипа Заглушаване на всички + Използвай инкогнито профил + Отвори чат + Отвори нов чат + Отвори нова група + Само вие и модераторите го виждат + Само подателят и модераторите го виждат + архивиран доклад + архивиран доклад от %s + 1 чат с член + 1 доклад за нарушения + 1 година + 4 нови езика на интерфейса + Приеми + Приеми + Приеми като член + Приеми като наблюдател + Приеми заявка за контакт + Приеми заявка за контакт + %1$s приет + бяхте приети + Приеми член + Добави съобщение + Обнови адрес + всички + Всички нови съобщения от тези членове ще бъдат скрити! + Позволи докладването на съобщения на модераторите. + Всички доклади за нарушения ще бъдат архивирани за вас. + Всички сървъри + Друга причина + Архивирай + Архивиране на всички доклади за нарушения? + Архивиране на %d доклада? + Архивирай доклад за нарушения + Архивирай доклад за нарушения? + Архивирай докладите за нарушения + Попитай + По-добра производителност на групите + По-добра поверителност и сигурност + Биография: + Биографията е твърде дълга + Блокиране на членовете за всички? + Бизнес връзка + С използването на SimpleX Chat вие се съгласявате със:\n- изпращане само на легално съдържание в публични групи.\n- уважение към другите потребители – без спам. + Промяната на профила е невъзможна + съобщенията не могат да бъдат изпратени + Каталонски, индонезийски, румънски и виетнамски - благодарение на нашите потребители! + криптиране от край до край.]]> + само след като заявката ви бъде приета.]]> + Промяна на автоматичното изтриване на съобщения? + Промени списъка + Промени подредбата + Чатове с членовете + Чат с администраторите + Чат с администраторите + Чат с администраторите + Чат с член + Разговаряйте с членовете, преди да се присъединят. + Нарушение на правилата на общността + Конфигуриране на сървърни оператори + Свързване + Свържете се по-бързо! 🚀 + Връзката е блокирана + Връзката е блокирана от оператора на сървъра:\n%1$s. + контактът е изтрит + контактът е деактивиран + контактът не е готов + контактът трябва да приеме… + Съдържанието нарушава условията за ползване + Създайте вашия адрес + %d чат(а) + %d чата с членовете + по подразбиране (%s) + Изтрий чата + Изтрийте чат съобщенията от вашето устройство. + Изтриване на чата с члена? + Изтриване на доклад за нарушения + Описанието е твърде дълго + Деактивиране на автоматичното изтриване на съобщения? + Деактивиране на изтриването на съобщения + %d съобщения + Не пропускайте важни съобщения. + %d доклада за нарушения + Активирайте изчезващите съобщения по подразбиране. + Грешка при приемането на член + Грешка при промяна на профила + Грешка при създаването на доклад за нарушения + Грешка при изтриване на чата с член + Грешка при отваряне на чата + Грешка при отваряне на групата + Грешка при четене на паролата за базата данни + Грешка при отхвърляне на заявката за контакт + Грешка при запазване на настройките + По-бързо изтриване на групи. + По-бързо изпращане на съобщения. + Файлът е блокиран от оператора на сървъра:\n%1$s. + За всички модератори + За мен + Цял линк + Получавайте известия, когато бъдете споменати. + Група + групата е изтрита + Помага на администраторите да модерират своите групи. + Неприемливо съдържание + Неприемлив профил + Присъединяване към групата + Поддържайте чатовете си чисти + По-малко трафик през мобилните мрежи. + Зареждане на профил… + Приемане на членове + членът има стара версия + Доклади за нарушения от членовете + Членовете могат да докладват съобщения на модераторите. + Членовете ще бъдат премахнати от чата - това не може да бъде отменено! + Членовете ще бъдат премахнати от групата - това не може да бъде отменено! + Членът ще се присъедини към групата, приеми член? + Споменете членовете 👋 + Изпращайте съобщение веднага щом докоснете Свързване. + Съобщенията от тези членове ще бъдат показани! + Съобщенията в този чат никога няма да бъдат изтрити. + модератор + модератори + Нова групова роля: Модератор + Нов член иска да се присъедини към групата. + Не + Няма чатове с членове + Няма сесия за поверително рутиране + Бележки + несинхронизирано + изключено + Изключено + Отвори линк + Отваряне на линковете от чатовете в списъка + Отвори за приемане + Отвори за свързване + Отвори за присъединяване + Отвори уеб линк? + Организиране на чатовете в списъци + Паролата в хранилището за ключове не може да бъде прочетена, моля, въведете я ръчно. Това може да се е случило след системна актуализация, несъвместима с приложението. Ако случаят не е такъв, моля, свържете се с разработчиците. + Паролата в хранилището за ключове не може да бъде прочетена. Това може да се е случило след системна актуализация, несъвместима с приложението. Ако случаят не е такъв, моля, свържете се с разработчиците. + чакащо + чакащо одобрение + чакащо преглед + Моля, изчакайте модераторите на групата да прегледат заявката ви за присъединяване към групата. + Предварително зададени сървъри + Политика за поверителност и условия за ползване. + Личните чатове, групи и вашите контакти не са достъпни от операторите на сървърите. + Имена на лични медийни файлове. + Време за изчакване на поверително рутиране + Време за изчакване на протокола във фонов режим + Времето на изчакване за установяване на TCP връзка във фонов режим + Адресът ще бъде кратък и вашият профил ще бъде споделен чрез него. + Забранете докладването на съобщения на модератори. + Отхвърляне + Отхвърляне на заявка за контакт + отхвърлен + отхвърлен + Отхвърляне на член? + премахнат от групата + Премахване на членове? + Премахва съобщения и блокира членове. + Докладвай + Докладване на съдържание: само модераторите на групата ще го виждат. + Докладването на съобщения е забранено в тази група. + Докладване на профил на член: само модераторите на групата ще го виждат. + Докладвай друго: само модераторите на групата ще го виждат. + Причина за докладване? + Доклад: %s + Доклади + Докладът е изпратен до модераторите + Докладвай за спам: само модераторите на групата ще го виждат. + Докладване на нарушение: само модераторите на групата ще го виждат. + заявката е изпратена + заявката за присъединяване е отхвърлена + преглед + прегледано от администраторите + Преглед на членовете на групата + Преглед на членовете + Прегледайте членовете преди да ги приемете. + Запазване на настройките за достъп? + Изпращане на заявка за контакт? + Изпращане на лични доклади за нарушения + Изпрати заявка + Изпрати заявка без съобщение + Изпрати личната си обратна връзка до групи. + Изпратено до вашия контакт след осъществяване на връзка. + Задаване на име на чат… + Задаване на достъп за членове + Задаване на срок на валидност на съобщенията в чатовете. + Задайте биография на профила и съобщение при посрещанее. + Обнови групов линк? + Обнови + Обнови адрес? + Споделете адреса си + Кратко описание: + Кратък линк + Кратък SimpleX адрес + Линк за канала на SimpleX + Спам + Спам + Докосни Свързване за чат + Докосни Свързване за изпращане на заявка + Докосни Присъединяване към групата + TCP порт за съобщения + Докладът ще бъде архивиран за вас. + Подателят НЯМА да бъде уведомен. + Това действие не може да бъде отменено - съобщенията, изпратени и получени в този чат по-рано от избраното, ще бъдат изтрити. + Тази връзка изисква по-нова версия на приложението. Моля, актуализирайте приложението или помолете контакта да ви изпрати съвместим линк. + Времето за изчезване е зададено само за нови контакти. + За да използвате друг профил след опит за връзка, изтрийте чата и използвайте линка отново. + Отблокиране за всички членове? + Непрочетени споменавания + Неподдържан линк за връзка + Актуализирани условия + Актуализирайте адреса си + Използвай TCP порт %1$s, когато не е посочен порт. + Използвай TCP порт 443 само за предварително зададени сървъри. + Използвай уеб порт + Съобщение при посрещане + Посрещане на контактите 👋 + Да + Вие приехте този член + Можете да споменете до %1$s членове в съобщение! + Не можете да изпращате съобщения! + Можете да видите вашите доклади за нарушения в Чат с администратори. + вие напуснахте + Вашата биография: + Вашият бизнес контакт + Вашият контакт + Вашата група + Вашият профил + Сподели стар адрес + Сподели стар линк + Линкът ще бъде кратък и профилът на групата ще бъде споделен чрез него. + Обнови групов линк + ЗАЯВКИ ЗА КОНТАКТ ОТ ГРУПИ + Членът е изтрит - не може да се приеме заявката + заявка за връзка от група %1$s + Тази настройка е за текущия профил + Разреши файлове и медия само ако вашият контакт ги разрешава. + Позволи на вашите контактите да изпращат файлове и медия. + Бот + И вие, и вашият контакт можете да изпращате файлове и медия. + Отпадащи опции + Грешка при маркиране на чата като прочетен + Файлове и медията са забранени в този чат. + Само вие можете да изпращате файлове и медия. + Само вашият контакт може да изпраща файлове и медия. + Отвори почистен линк + Отвори пълен линк + Отвори за използване на бот + Забрани изпращането на файлове и медия. + Премахни параметри за проследяване от линковете + SimpleX реле линк + Натисни Свързване, за използване на бота + За да изпращате команди, трябва да сте свързани. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index 0e179fadd4..c9b1f79694 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -524,7 +524,7 @@ Confirmeu el nou mot de pas… Voleu canviar la frase de pas per a la base de dades? connectat - connectat directament + connexió sol·licitada canviant d\'adreça per %s… connectat complet @@ -1185,7 +1185,7 @@ Els teus contactes romandran connectats. Compartir l\'adreça amb els contactes? Compartir enllaç - Desa la configuració d\'acceptació automàtica + Desar la configuració d\'adreça SimpleX Deixar de compartir O per compartir en privat Podeu crear-la més tard @@ -1248,7 +1248,7 @@ Trucada pendent So silenciat Missatges omesos - El hash del missatge anterior és diferent.\" + El hash del missatge anterior és diferent. L\'identificador del missatge següent és incorrecte (menor o igual a l\'anterior).\nPot passar per algun error o quan la connexió està compromesa. Informeu-ho als desenvolupadors. Pot passar quan tu o el teu contacte feu servir la còpia de seguretat de la base de dades antiga. @@ -1692,8 +1692,8 @@ enviament no autoritzat Aquest text està disponible a la configuració Benvingut %1$s! - unir-te com a %s - enviar missatge directe + Entrar com a %s + enviar per connectar Toca per iniciar un xat nou Carregant xats… No hi ha xats filtrats @@ -1716,7 +1716,7 @@ La imatge no es pot descodificar. Si us plau, proveu amb una imatge diferent o contacteu amb els desenvolupadors. El vídeo no es pot descodificar. Si us plau, prova amb un vídeo diferent o contacta amb els desenvolupadors. ets observador - No pots enviar missatges! + ets observador(a) Poseu-vos en contacte amb l\'administrador del grup. Només els propietaris del grup poden activar fitxers i mitjans. Desant %1$s missatges @@ -1832,7 +1832,7 @@ El text enganxat no és un enllaç SimpleX. Aquesta cadena no és un enllaç de connexió! Podeu tornar a veure l\'enllaç d\'invitació als detalls de connexió. - La teva connexió s\'ha mogut a %s, però s\'ha produït un error inesperat en redirigir-te al perfil. + La connexió s\'ha mogut a %s però s\'ha produït un error en canviar de perfil. Marcar com a verificat Escaneja el codi de seguretat des de l\'aplicació del teu contacte. Contactes @@ -1999,7 +1999,7 @@ Convidar a xatejar Nou rol de membre No hi ha contactes per afegir - Membre anterior %1$s + Membre %1$s Ometre convidar membres desconegut Convidar membres @@ -2169,7 +2169,7 @@ "Invitació d\'un sol ús per SimpleX" Sense Tor o una VPN, la vostra adreça serà visible per als següents relays XFTP: %1$s. Funció lenta - heu estat convidat a un grup + Estat convidats(des) a un grup cerca (escaneja o enganxa del porta-retalls) Escaneja un codi QR @@ -2346,11 +2346,155 @@ Enllaç al canal SimpleX Aquest enllaç requereix una versió de l\'aplicació més recent. Actualitzeu l\'aplicació o demaneu al vostre contacte que enviï un enllaç compatible. Enllaç de connexió no compatible - Emprar enllaços curts (BETA) Enllaç complet Enllaç curt Tots els servidors Apagat Feu servir el port TCP 443 només per a servidors predefinits. Servidors predefinits + Error en acceptar membre + %d missatges + Informe enviat a la moderació + desactivat + Acceptar + Acceptar com a membre + Acceptar membre + El(la) membre s\'unirà al grup, l\'accepteu? + revisat per l\'administració + no es poden enviar missatges + contacte eliminat + contacte desactivat + el contacte no està a punt + el grup s\'ha suprimit + no sincronitzat + eliminat(da) del grup + sol·licitud d\'unió rebutjada + No podeu enviar missatges! + Podeu veure els vostres informes al xat amb administració. + Heu sortit + %1$s acceptat(da) + us ha acceptat + pendient de revisió + Establir l\'admissió de membres + Admissió de membre + Xat amb admins + No hi ha xats amb membres + Acceptar com a observador + Desar la configuració d\'admissió? + Error en suprimir el xat amb membre + %d xat(s) + el(la) membre té una versió antiga + Un(a) nou(va) membre vol unir-se al grup. + Si us plau, espereu que els moderadors del grup revisin la vostra sol·licitud per unir-vos-hi. + heu acceptat aquest(a) membre + per revisar + Xats amb admins + Xats amb membre + Eliminar xat + Rebutjar + Rebutjar membre? + Revisar membres + Revisar membres abans d\'admetre (trucar a la porta). + un xat amb un(a) membre + Xats amb membres + Suprimir el xat amb membre? + %d xats amb membres + tots(es) + Actualitzar l\'adreça + Acceptar la sol·licitud de contacte + Afegir un missatge + No es pot canviar el perfil + xifratge extrem a extrem.]]> + després que la sol·licitud sigui acceptada.]]> + Connectar + el contacte hauria d\'acceptar… + Error en canviar el perfil + Error en obrir el xat + Error en obrir el grup + Error en rebutjar la sol·licitud de contacte + Entrar al grup + Obrir xat + Obrir nou xat + Obrir nou grup + Obrir per acceptar + Obrir per connectar + Obrir per entrar + L\'adreça serà curta i el vostre perfil es compartirà a través d\'ella. + Rebutjar la sol·licitud de contacte + sol·licitud enviada + Enviar sol·licitud de contacte? + Enviar sol·licitud + Enviar sol·licitud sense missatge + Enviat al contacte després de la connexió. + Actualitzar l\'enllaç del grup? + Actualitzar + Actualitzar l\'adreça? + L\'emissor(a) NO serà notificat(da). + Per utilitzar un altre perfil després d\'un intent de connexió, suprimiu el xat i torneu a utilitzar l\'enllaç. + Missatge de benvinguda + El vostre perfil + Xatejar amb l\'administració + Xatejar amb membres abans que s\'uneixin. + Connexió més ràpida! 🚀 + Menys trànsit a les xarxes mòbils. + Enviar missatges a l\'instant en tocar Connectar. + Nou rol de grup: Moderador(a) + Sense sessió d\'encaminament privada + Temps d\'espera d\'encaminament privat + Temps d\'espera del protocol en segon pla + Elimina missatges i bloca membres. + Revisió de membres del grup + Envieu comentaris privats a grups. + Temps d\'espera de la connexió TCP en segon pla + Bio: + Bio massa llarga + Descripció massa llarga + Carregant el perfil… + Descripció breu: + La vostra bio: + Acceptar la sol·licitud de contacte + Connexió empresarial + Grup + Toqueu Connectar per xatejar + Toqueu Connectar per enviar la sol·licitud + Toqueu Unir-vos al grup + El vostre contacte empresarial + El vostre contacte + El vostre grup + El temps de desaparició només està definit per a contactes nous. + 4 idiomes nous + Català, indonesi, romanès i vietnamita: gràcies per les col·laboracions! + Creeu la vostra adreça + Activar els missatges que desapareixen per defecte. + Mantingueu els vostres xats nets + Definir la biografia del perfil i el missatge de benvinguda. + Compartir la vostra adreça + Adreça SimpleX curta + Actualitzar la vostra adreça + Utilitzar el perfil d\'incògnit + Doneu la benvinguda als vostres contactes 👋 + Compartir l\'adreça antiga + Compartir l\'enllaç antic + L\'enllaç serà curt i el perfil del grup es compartirà a través d\'ell. + Actualitzar l\'enllaç del grup + SOL·LICITUDS DE CONTACTE DE GRUPS + Membre eliminat(da); no es pot acceptar la sol·licitud. + connexió sol·licitada del grup %1$s + Aquesta configuració és per al perfil actual + Permetre fitxers i contingut multimèdia només si el vostre contacte ho permet. + Permetre que els contactes enviïn fitxers i contingut multimèdia. + Bot + Tant vos com el vostre contacte podeu enviar fitxers i contingut multimèdia. + Els fitxers i els continguts multimèdia estan prohibits en aquest xat. + Només vos podeu enviar fitxers i contingut multimèdia. + Només el contacte pot enviar fitxers i contingut multimèdia. + Obrir per utilitzar el bot + Prohibir l\'enviament de fitxers i contingut multimèdia. + Toqueu Connectar per utilitzar el bot + Per enviar ordres heu d\'estar connectat(da). + Obrir enllaç net + Obrir enllaç complet + Eliminar el seguiment de l\'enllaç + Opcions obsoletes + Enllaç de servidor SimpleX diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 439b9df18d..4d6aa47fd3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -254,7 +254,7 @@ Hovory na uzamčené obrazovce: Otevřete SimpleX Chat pro přijetí hovoru Povolte volání ze zamčené obrazovky prostřednictvím Nastavení. - Otevřený + Otevřít Zvuk zapnut Reproduktor vypnut Reproduktor zapnut @@ -548,8 +548,8 @@ Smazat pro mě upraveno neautorizované odeslání - jste pozváni do skupiny - připojit jako %s + Jste pozváni do skupiny + Připojit jako %s připojuji… Začněte nový chat Chat s vývojáři @@ -937,7 +937,7 @@ pozorovatel Zpráva bude smazána pro všechny členy. Zpráva bude pro všechny členy označena jako moderovaná. - Nemůžete posílat zprávy! + jste pozorovatel Chyba aktualizace odkazu skupiny Počáteční role Systém @@ -1172,7 +1172,7 @@ Sdílet adresu s kontakty\? Přestat sdílet adresu\? Vytvořit adresu, aby se s vámi lidé mohli spojit. - Uložit nastavení automatického přijímání + Uložit nastavení SimpleX adresy Přestat sdílet Automaticky přijmout Můžete vytvořit později @@ -1363,7 +1363,7 @@ 6 nových jazyků pro rozhraní Aplikace šifruje nové místní soubory (kromě videí) Arabština, bulharština, finština, hebrejština, thajština a ukrajinština - díky uživatelům a Weblate. - propojeno napřímo + požádáno o připojení Otevřít Šifrování uložených souborů a médií Chyba vytváření kontaktu @@ -1374,7 +1374,7 @@ Vytvořit nový profil v desktopové aplikaci. 💻 Změnit inkognito při připojování. - připojení k adresáři skupin (BETA)!\n- doručenky (až 20 členů).\n- rychlejší a stabilnější. - odeslat přímou zprávu + odeslat pro připojení smazaný kontakt Chyba Vytvořit profil @@ -1573,7 +1573,7 @@ Vyhledávání přijímá pozvánky. Zobrazit pomalé API volání odstraněna kontaktní adresa - Vložte člena %1$s + Člen %1$s Vložte odkaz na připojení! Nejnovější historie a vylepšený directory bot. Soukromé poznámky @@ -2282,7 +2282,7 @@ Report: %s Nahlásit další: uvidí pouze skupinový moderátoři. Můžete nastavit název připojení, pro pamatování, s kým byl odkaz sdílen. - Vaše připojení bylo přesunuto na %s, ale došlo k neočekávané chybě při přesměrování na profil. + Vaše připojení bylo přesunuto na %s, ale nastala chyba při přepínání profilu. Pro každý profil použijte různé přihlašovací údaje Neznámé servery Pro ochranu vaší IP adresy, soukromé směrování používá vaše servery SMP k doručování zpráv. @@ -2371,7 +2371,6 @@ Zásady ochrany soukromí a podmínky používání. Soukromé konverzace, skupiny a kontakty nejsou přístupné provozovatelům serverů. Nepodporovaný odkaz k připojení - Používejte krátké odkazy (BETA) Tento odkaz vyžaduje novější verzi aplikace. Prosím aktualizujte aplikaci nebo požádejte kontakt o odeslání kompatibilního odkazu. odkaz SimpleX kanálu Úplný odkaz @@ -2380,4 +2379,94 @@ Vypnut Přednastavené servery Použít TCP port 443 jen pro přednastavené servery. + Chyba přijmutí člena + %d chat(y) + %d chat se členy + %d zpráv + 1 chat se členem + Uložit nastavení vstupného? + Chat s adminy + Nastavit přijímání členů + Přihlášení člena + Schválit členy + Schválit členy před přijetím (zaklepat). + Chat se členy + Žádné chaty se členy + Chat s adminy + Přijmout + Přijmout člena + schválen adminy + Upgradovat adresu + přijat %1$s + Vás přijal + schválení + Odstranit chat se členem? + Odmítnout + odešli jste + Chat se členem + Prosím, počkejte až moderátoři skupiny vaši žádost o připojení ke skupině schválí. + přijali jste tohoto člen + Hlášení odesláno moderátorům + kontakt smazán + kontakt vypnut + kontakt nepřipraven + nesynchronizováno + žádost o přihlášení zamítnuta + Nemůžete posílat zprávy! + Můžete zobrazit své hlášení v Chatu s adminy. + nemůže posílat zprávy + skupina smazána + člen má starou verzi + odstraněn ze skupiny + Nový člen chce připojit do skupiny. + čeká na posouzení + vypnuto + vše + Odstranit chat + Člen se připojí ke skupině, přijmout? + Přijmout jako člena + Přijmout jako pozorovatele + Odmítnout člena? + Chyba odstranění chatu se členem + Použït profil inkognito + Otevřít chat + Otevřít nový chat + Otevřít novou skupinu + Připojit + Připojte se rychleji! 🚀 + POŽADAVKY NA PŘIPOJENÍ ZE SKUPIN + kontakt by měl přijmout… + Vytvořit vaši adresu + Popis příliš dlouhý + Povolení mizících zpráv ve výchozím nastavení. + Chyba změny profilu + Chyba otevření chatu + Chyba otevření skupiny + Chyba odmítnutí žádosti o kontakt + Skupina + Připojit ke skupině + Udržujte chat čistý + Méně provozu na mobilních sítích. + Načítání profilu… + Člen je smazán - nemůže přijmout žádost + Nová skupinová role: Moderátor + Zastaralé možnosti + Soubory a média jsou zakázány v tomto chatu. + Pouze vy můžete odesílat soubory a média. + Pouze vaše kontakty mohou posílat soubory a média. + Otevřít čistý odkaz + Otevřít celý odkaz + Otevřít pro přijetí + Otevřít pro připojení + Otevřít pro připojení + Otevřít pro použítí bota + Vypršel čas soukromého směrování + Odesílání souborů a médií zakázáno. + Vypršel čas protokolu na pozadí + Odmítnout žádost o kontakt + Odebrat sledování odkazů + Odstranit zprávy a blokovat členy. + koncovým šifrováním.]]> + SimpleX relé odkaz + Bez soukromého směrování sezení diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml new file mode 100644 index 0000000000..c4f84d4397 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/da/strings.xml @@ -0,0 +1,718 @@ + + + %1$d file error(s):\n%2$s + %1$d fil(er) henter stadig + %1$d fil(er) kunne ikke hentes. + %1$d fil(er) blev slettet + %1$d fil(er) hentes ikke + %1$d beskeder kunne ikke dekrypteres. + %1$d beskeder modereret af %2$s + %1$d beskeder sprunget over. + %1$d andre filfejl. + %1$d sprungne beskeder + %1$s MEDLEMMER + %1$s beskeder blev ikke videresendt + %1$s ønsker at komme i kontakt med dig via + 1 chat med et medlem + 1 dag + 1 minut + 1 måned + 1 rapport + 1-gangs link + 1 uge + 1 år + 30 sekunder + 5 minutter + 6 nye grænsefladesprog + a + b + Afbryd + Afbryd adresseændring + Afbryd adresseændring? + Om operatører + Om SimpleX + Om SimpleX-adressen + Om SimpleX Chat + over, så: + Farveaccent + Acceptere + Acceptere + Acceptere + Modtage + Acceptere + Acceptere + Accepter som medlem + Accepter som observatør + Accepter betingelser + Accepter forbindelsesanmodning? + Accepter kontaktanmodning + accepterede %1$s + accepted call + Accepterede betingelser + accepteret invitation + accepterede dig + Accepter inkognito + Accepter medlem + Få adgang til serverne via SOCKS proxy på port %d? Proxyen skal startes, før denne indstilling aktiveres. + Anerkendt + Bekræftelsesfejl + Aktive forbindelser + Tilføj adresse til din profil, så dine kontakter kan dele den med andre. Profilopdateringen sendes til dine kontakter. + Tilføj kontakt + Tilføjede medie- og filservere + Tilføjede beskedservere + Tilføj venner + Ekstra farveaccent + Ekstra farveaccent 2 + Yderligere sekundær + Tilføj liste + Tilføj besked + Tilføj forudindstillede servere + Tilføj profil + Adresse + Adresseændringen bliver annulleret. Den gamle modtageradresse bliver brugt. + Adresse eller engangslink? + Adresseindstillinger + Tilføj server + Tilføj servere ved at scanne QR-koder. + Tilføj et kort link + Tilføj teammedlemmer + Tilføj til en anden enhed + Tilføj til liste + Tilføj velkomstbesked + Tilføj dine teammedlemmer til samtalerne. + administrator + administratorer + Administratorer kan blokere et medlem for alle. + Administratorer kan oprette links for at deltage i grupper. + Avancerede netværksindstillinger + Avancerede indstillinger + Avancerede indstillinger + Et par ting mere + accepterer kryptering… + accepterer kryptering for %s… + alle + Alle + Alle appdata slettes. + k + Opret forbindelse via kontaktadresse? + Opret forbindelse via engangslink? + Deltag i gruppe? + Brug den aktuelle profil + Brug ny inkognito -profil + Din profil sendes til den kontakt, du har modtaget dette link fra. + Du opretter forbindelse til alle gruppemedlemmer. + Forbinde + Tilslut inkognito + Alle chats og meddelelser slettes - dette kan ikke fortrydes! + Alle chats fjernes fra listen %s, og listen slettes + Alle farvetilstande + Alle data slettes, når det indtastes. + Alle gruppemedlemmer forbliver forbundet. + alle medlemmer + Alle meddelelser slettes - dette kan ikke fortrydes! + Alle meddelelser slettes - dette kan ikke fortrydes! Meddelelserne slettes KUN for dig. + Alle nye meddelelser fra %s vil være skjult! + Alle nye meddelelser fra disse medlemmer vil være skjult! + Tillade + Tillade + Tillade opkald? + Tillad kun opkald, hvis din kontakt tillader dem. + Tillad kun forsvindende meddelelser, hvis din kontakt tillader dem. + Tillad nedjustering + Tillad kun irreversibel meddelelsesudslettelse, hvis din kontakt giver den mulighed for dig. (24 timer) + Tillad meddelelsesreaktioner. + Tillad kun meddelelsesreaktioner, hvis din kontakt tillader dem. + Tillad at sende direkte beskeder til medlemmerne. + Tillad for irreversibelt at slette sendte beskeder. (24 timer) + Tillad at rapportere meddelelser til moderatorer. + Tillad at sende forsvindende beskeder. + Tillad at sende filer og medier. + Tillad at sende simplex -links. + Tillad at sende stemmemeddelelser. + Tillad stemmemeddelelser? + Tillad kun stemmemeddelelser, hvis din kontakt tillader dem. + Tillad dine kontakter at tilføje meddelelsesreaktioner. + Lad dine kontakter ringe til dig. + Lad dine kontakter irreversibelt slette sendte beskeder. (24 timer) + Lad dine kontakter sende forsvindende beskeder. + Lad dine kontakter sende stemmemeddelelser. + Alle profiler + Alle rapporter vil blive arkiveret for dig. + Alle servere + Alle dine kontakter, samtaler og filer vil blive krypteret og uploadet i bidder til konfigurerede XFTP -relæer. + Alle dine kontakter forbliver forbundet. + Alle dine kontakter forbliver forbundet. Profilopdatering sendes til dine kontakter. + Tilslut dig allerede! + Deltager allerede i gruppen! + altid + Altid + Altid på + Brug altid privat routing. + Brug altid relæ + og %d andre begivenheder + Android Keystore bruges til sikkert at opbevare adgangssæt - det giver Notification Service mulighed for at arbejde. + Android Keystore vil blive brugt til sikkert at gemme adgangssæt, når du genstarter appen eller skifter adgangssætning - det giver mulighed for at modtage meddelelser. + En tom chatprofil med det angivne navn oprettes, og appen åbnes som sædvanligt. + En ny tilfældig profil deles. + En anden grund + Svaropkald + Alle kan være vært for servere. + APP + App løber altid i baggrunden + App Build: %s + App kan kun modtage meddelelser, når den kører, ingen baggrundstjeneste startes + Backup af appdata + Migration af appdata + Åbn chat + Åbn ny chat + Åben gruppe + Åbn ny gruppe + Ugyldigt link + Kontroller at SimpleX-linket er korrekt. + Åbner databasen… + Databasemigrering er i gang.\nDet kan tage et par minutter. + Ugyldig filsti + Du delte en ugyldig filsti. Rapportér problemet til appudviklerne. + Visningen gik ned + forbundet + fejl + forbinder + Du har forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt. + Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt (fejl: %1$s). + Forsøger at oprette forbindelse til den server, der bruges til at modtage beskeder fra denne kontakt. + slettet + markeret som slettet + %d beskeder markeret som slettet + modereret af %s + Kun du og moderatorer ser det + Kun afsender og moderatorer ser det + arkiveret rapport + arkiveret rapport af %s + blokeret + blokeret af administrator + %d beskeder blokeret + %d beskeder blokeret af administrator + afsendelse af filer understøttes endnu ikke + Modtagelse af filer understøttes endnu ikke + du + ukendt beskedformat + ugyldigt beskedformat + LEVENDE + modereret + videresendt + gemt + gemt fra %s + ugyldig chat + ugyldige data + fejl ved visning af besked + fejl ved visning af indhold + Dekrypteringsfejl + Fejl ved genforhandling af kryptering + ende-til-ende-kryptering.]]> + ende-til-ende-kryptering med perfekt fremadrettet hemmeligholdelse, afvisning og gendannelse efter indbrud.]]> + kvanteresistent e2e-kryptering med perfekt fremadrettet hemmeligholdelse, afvisning og gendannelse efter indbrud.]]> + Denne chat er beskyttet af end-to-end-kryptering. + Denne chat er beskyttet af kvanteresistent end-to-end-kryptering. + Private noter + forbindelse %1$d + forbindelse etableret + inviteret til at oprette forbindelse + bedt om at oprette forbindelse + forbinder… + du delte et engangslink + Du delte et engangslink inkognito + via gruppelink + inkognito via gruppelink + via link til kontaktadresse + inkognito via link til kontaktadresse + via engangslink + inkognito via engangslink + SimpleX kontaktadresse + SimpleX engangsinvitation + SimpleX gruppe link + SimpleX-kanallink + via %1$s + SimpleX-links + Beskrivelse + Fuldt link + Via browseren + Åbning af linket i browseren kan reducere forbindelsens privatliv og sikkerhed. Upålidelige SimpleX-links vil være røde. + Spam + Upassende indhold + Overtrædelse af retningslinjer for fællesskabet + Upassende profil + Fejl under lagring af SMP-servere + Fejl ved lagring af XFTP-servere + Sørg for, at SMP-serveradresserne er i korrekt format, linjeseparerede og ikke duplikerede. + Sørg for, at XFTP-serveradresserne er i korrekt format, linjeseparerede og ikke duplikerede. + Fejl ved indlæsning af SMP-servere + Fejl ved indlæsning af XFTP-servere + Fejl ved opdatering af netværkskonfigurationen + Chatten kunne ikke indlæses + Kunne ikke indlæse chats + Opdater appen, og kontakt udviklerne. + Fejl ved oprettelse af profil! + Duplikeret visningsnavn! + Du har allerede en chatprofil med det samme visningsnavn. Vælg et andet navn. + Ugyldigt visningsnavn! + Dette visningsnavn er ugyldigt. Vælg et andet navn. + Fejl ved skift af profil! + Fejl ved lagring af servere + Ingen beskedservere. + Ingen servere til at modtage beskeder. + Ingen servere til routing af private beskeder. + Ingen medie- og filservere. + Ingen servere til at sende filer. + Ingen servere til at modtage filer. + For chatprofil %s: + Fejl i serverkonfigurationen. + Fejl ved accept af betingelser + Spam + Indholdet overtræder brugsbetingelserne + Forbindelsestimeout + Forbindelsesfejl + Tjek venligst din netværksforbindelse med %1$s og prøv igen. + Serveradressen er ikke kompatibel med netværksindstillingerne: %1$s. + Serverversionen er ikke kompatibel med din app: %1$s. + Timeout for privat routing + Fejl ved privat routing + Ingen privat routingsession + Fejl ved forbindelse til videresendelsesserveren %1$s. Prøv venligst senere. + Videresendelsesserveradressen er ikke kompatibel med netværksindstillingerne: %1$s. + Videresendelsesserverversionen er inkompatibel med netværksindstillingerne: %1$s. + Videresendelsesserveren %1$s kunne ikke oprette forbindelse til destinationsserveren %2$s. Prøv senere. + Destinationsserveradressen for %1$s er ikke kompatibel med indstillingerne for videresendelsesserveren %2$s. + Destinationsserverversionen af %1$s er inkompatibel med videresendelsesserveren %2$s. + Prøv senere. + Fejl ved afsendelse af besked + Fejl ved videresendelse af beskeder + Fejl ved oprettelse af besked + Fejl ved oprettelse af rapport + Fejl ved indlæsning af oplysninger + Fejl ved tilføjelse af medlem(mer) + Fejl ved tilmelding til gruppen + Fejl ved accept af medlem + Fejl ved sletning af chat med medlem + Kan ikke modtage fil + Afsenderen annullerede filoverførslen. + Ukendte servere! + Uden Tor eller VPN vil din IP-adresse være synlig for disse XFTP-relæer:\n%1$s. + Fejl ved modtagelse af fil + Fejl ved oprettelse af adresse + Kontakten findes allerede + Du er allerede forbundet til %1$s. + Ugyldigt forbindelseslink + Kontroller venligst, at du har brugt det korrekte link, eller bed din kontaktperson om at sende dig et nyt. + Ikke-understøttet forbindelseslink + Dette link kræver en nyere appversion. Opgrader appen, eller bed din kontaktperson om at sende et kompatibelt link. + Forbindelsesfejl (AUTH) + Medmindre din kontaktperson har slettet forbindelsen, eller dette link allerede er i brug, kan det være en fejl - rapporter det.\nFor at oprette forbindelse skal du bede din kontaktperson om at oprette et nyt forbindelseslink og kontrollere, at du har en stabil netværksforbindelse. + Forbindelse blokeret + Forbindelsen er blokeret af serveroperatøren:\n%1$s. + Ikke-leverede beskeder + Forbindelsen har nået grænsen for antal ikke-leverede beskeder. Din kontaktperson er muligvis offline. + Fejl ved accept af kontaktanmodning + Fejl ved afvisning af kontaktanmodning + Afsenderen har muligvis slettet forbindelsesanmodningen. + Fejl ved sletning af kontakt + Fejl ved sletning af gruppe + Fejl ved sletning af private noter + Fejl ved sletning af kontaktanmodning + Fejl ved sletning af ventende kontaktforbindelse + Fejl ved ændring af adresse + Fejl ved afbrydelse af adresseændring + Fejl ved synkronisering af forbindelse + Testen mislykkedes ved trin %s. + Server kræver tilladelse til at oprette køer, tjek adgangskode + Server kræver tilladelse til at uploade, tjek adgangskode + Muligvis er certifikatets fingeraftryk i serveradressen forkert + Fejl ved angivelse af adresse + Fejl + Forbinde + Afbryde + Opret kø + Sikker kø + Slet kø + Opret fil + Upload fil + Hent fil + Sammenlign fil + Slet fil + Fejl ved sletning af brugerprofil + Der opstod en fejl ved opdatering af brugerens privatliv + Langsom funktion + Funktionens udførelse tager for lang tid: %1$d sekunder: %2$s + Der opstod en fejl ved opdatering af chatlisten. + Fejl ved oprettelse af chatliste + Fejl ved indlæsning af chatlister + Fejl ved åbning af chatten + Fejl ved åbning af gruppe + Fejl ved ændring af profil + Øjeblikkelige notifikationer + Øjeblikkelige notifikationer! + Øjeblikkelige notifikationer er deaktiveret! + løber SimpleX i baggrunden i stedet for at bruge push-notifikationer.]]> + Det kan deaktiveres via indstillinger – notifikationer vises stadig, mens appen løber.]]> + Tillad det i den næste dialogboks for at modtage beskeder med det samme.]]> + Batterioptimering er aktiv og deaktiverer baggrundstjenester og periodiske anmodninger om nye beskeder. Du kan genaktivere dem via indstillinger. + Periodiske meddelelser + Periodiske notifikationer er deaktiveret! + Appen henter nye beskeder med jævne mellemrum – den bruger et par procent af batteriet om dagen. Appen bruger ikke push-notifikationer – data fra din enhed sendes ikke til serverne. + Åbn appindstillinger + Deaktiver notifikationer + SimpleX kan ikke løbe i baggrunden. Du modtager kun notifikationer, når appen kører. + Appens batteriforbrug / Ubegrænset i appindstillingerne.]]> + Ingen baggrundsopkald + Appen kan lukkes i baggrunden efter 1 minut. + Appens batteriforbrug / Ubegrænset i appindstillingerne.]]> + Adgangsudtryk er nødvendig + For at modtage notifikationer, indtast databaseadgangskoden + Kan ikke initialisere databasen + Databasen fungerer ikke korrekt. Tryk for at få mere at vide. + Xiaomi-enheder: Aktiver venligst Autostart i systemindstillingerne for at notifikationer kan fungere.]]> + SimpleX Chat-tjeneste + Modtager beskeder… + Lydopkald + Videoopkald + Afslut opkald + Skjul + SimpleX Chat beskeder + SimpleX Chat opkalder + Notifikationstjeneste + Vis forhåndsvisning + Forhåndsvisning af notifikationer + Løber når appen er åben + Starter periodisk + Tjekker nye beskeder hvert 10. minut i op til 1 minut + Baggrundstjenesten løber altid – der vises meddelelser, så snart beskederne er tilgængelige. + Beskedtekst + Kontaktnavn + Skjult + Vis kontakt og besked + Vis kun kontakt + Skjul kontakt og besked + Kontakt skjult: + ny besked + Ny kontaktanmodning + Forbundet + Fejl ved visning af notifikation. Kontakt udviklerne. + SimpleX Lås + For at beskytte dine oplysninger skal du aktivere SimpleX Lås.\nDu vil blive bedt om at fuldføre godkendelse, før denne funktion aktiveres. + Tænd + SimpleX lås tilstand + Systemgodkendelse + Indtastning af adgangskode + Godkendelse mislykkedes + Du kunne ikke verificeres. Prøv igen. + Ingen app-adgangskode + Indtast adgangskode + Nuværende adgangskode + Skift adgangskode + Godkend + Straks + %d sekunder + %d minutter + Husk eller opbevar den sikkert - der er ingen måde at gendanne en mistet adgangskode på! + SimpleX Lås aktiveret + Du skal godkende, når du starter eller genoptager appen efter 30 sekunder i baggrunden. + Lås op + Log ind med dine legitimationsoplysninger + Aktivér SimpleX Lås + Deaktiver SimpleX Lås + Bekræft dine legitimationsoplysninger + Godkendelse ikke tilgængelig + Enhedsgodkendelse er ikke aktiveret. Du kan aktivere SimpleX Lås via Indstillinger, når du har aktiveret enhedsgodkendelse. + Enhedsgodkendelse er deaktiveret. SimpleX Lås slås fra. + Stop chatten + Åbn chatkonsol + Skift chatprofiler + Åbn migreringsskærmen + SimpleX Lås er ikke aktiveret! + Du kan aktivere SimpleX Lås via Indstillinger. + Fejl ved levering af besked + Advarsel om levering af besked + Denne kontakt har højst sandsynligt slettet forbindelsen med dig. + Ingen besked + Denne besked er blevet slettet eller ikke modtaget endnu. + Rapportér årsag? + Arkivér rapport? + Arkivér %d rapporter? + Arkivér alle rapporter? + Rapporten vil blive arkiveret for dig. + For mig + Til alle moderatorer + Fejl: %1$s + Forkert nøgle eller ukendt forbindelse - sandsynligvis er denne forbindelse slettet. + Kapacitet overskredet - modtageren modtog ikke tidligere sendte beskeder. + Netværksproblemer - beskeden udløb efter mange forsøg på at sende den. + Destinationsserverfejl: %1$s + Videresendelsesserver: %1$s\nFejl: %2$s + Videresendelsesserver: %1$s\nDestinationsserverfejl: %2$s + Serveradressen er ikke kompatibel med netværksindstillingerne. + Serverversionen er inkompatibel med netværksindstillingerne. + Forkert nøgle eller ukendt fil-chunk-adresse - filen er sandsynligvis slettet. + Filen er blokeret af serveroperatøren:\n%1$s. + Filen blev ikke fundet - filen blev sandsynligvis slettet eller annulleret. + Filserverfejl: %1$s + Svar + Dele + Kopi + Gem + Rediger + Info + Søg + Arkivé + Arkivér rapport + Arkiver rapporterne + Slet rapport + Sendt besked + Modtaget besked + Historie + Ingen historik + Som svar til + Gemt + Videresendt + Gemt fra + Videresendt fra + Modtager(e) kan ikke se, hvem denne besked er fra. + Levering + Ingen leveringsoplysninger + Slet + Afsløre + Skjul + Moderé + Anmeld + Vælg + Udvid + Slette besked? + Slet %d beskeder? + Beskeden vil blive slettet - dette kan ikke fortrydes! + Beskeder vil blive slettet - dette kan ikke fortrydes! + Beskeden vil blive markeret til sletning. Modtageren(e) vil kunne se denne besked. + Beskeder vil blive markeret til sletning. Modtageren(e) vil kunne se disse beskeder. + Slet medlemsbesked? + Slet %d beskeder fra medlemmer? + Beskeden vil blive slettet for alle medlemmer. + Beskederne vil blive slettet for alle medlemmer. + Beskeden vil blive markeret som modereret for alle medlemmer. + Beskederne vil blive markeret som modereret for alle medlemmer. + Slet for mig + For alle + Stop fil + Stoppe afsendelse af fil? + Afsendelse af filen vil blive stoppet. + Stoppe modtagelse af fil? + Modtagelsen af filen vil blive stoppet. + Stop + Tilbagekald fil + Tilbagekald fil? + Filen vil blive slettet fra serverne. + Tilbagekald + Videresend + Hent + Liste + Besked videresendt + Ingen direkte forbindelse endnu, beskeden er videresendt af administratoren. + Medlem inaktivt + Beskeden kan blive leveret senere, hvis medlemmet bliver aktivt. + redigeret + sendt + uautoriseret afsendelse + afsendelse mislykkedes + ulæst + Velkommen %1$s! + Velkomst! + Denne tekst er tilgængelig i indstillinger + Chats + Indstillinger + forbinder… + send for at oprette forbindelse + Åben for at deltage + Du er inviteret til gruppen + Deltag som %s + afvist + forbinder… + Tryk for at starte en ny chat + Chat med udviklerne + Du har ingen chats + Indlæser chats… + Ingen filtrerede chats + Ingen chats på listen %s. + Ingen ulæste chats + Ingen chats + Ingen chats fundet + Tryk for at oprette forbindelse + Åbn for at oprette forbindelse + Åben for at acceptere + kontakten skal acceptere… + Forbinde med %1$s? + Søg eller indsæt SimpleX-link + Tryk på Opret SimpleX-adresse i menuen for at oprette den senere. + Ingen chat valgt + Intet er valgt + Valgte %d + Videresend %1$s besked(er)? + Intet at videresende! + Videresende beskeder uden filer? + Beskeder blev slettet, efter du valgte dem. + Hent + Favoritter + Kontakter + Grupper + Virksomheder + Noter + Rapporter + Rapport: %s + %d rapporter + Medlemsrapporter + %d beskeder + %d chats med medlemmer + %d chat(er) + Tryk på Opret forbindelse for at chatte + Tryk på Opret forbindelse for at sende anmodningen + Accepter kontaktanmodning + Dit kontaktbanner + Tryk på Deltag i gruppen + Din gruppe + Gruppe + Forretningsforbindelse + Din forretningskontakt + Del besked… + Del medier… + Del fil… + Videresend besked… + Videresend beskeder… + Kan ikke sende besked + De valgte chatpræferencer forbyder denne besked. + Vedhæft + Kontekstikon + Annuller billedforhåndsvisning + Annuller filforhåndsvisning + For mange billeder! + For mange videoer! + Kun 10 billeder kan sendes ad gangen + Kun 10 videoer kan sendes ad gangen + Afkodningsfejl + Billedet kan ikke afkodes. Prøv et andet billede, eller kontakt udviklerne. + Videoen kan ikke afkodes. Prøv en anden video, eller kontakt udviklerne. + Filer og medier forbudt! + Kun gruppeejere kan aktivere filer og medier. + Send direkte besked for at oprette forbindelse + Videresender %1$s beskeder + Gemmer %1$s beskeder + SimpleX links er ikke tilladt + Filer og medier er ikke tilladt + Talebeskeder er ikke tilladt + Besked + Beskeden er for stor! + Reducer venligst beskedstørrelsen og send den igen. + Reducer venligst beskedstørrelsen, eller fjern mediet, og send igen. + Du kan kopiere og reducere beskedstørrelsen for at sende den. + Rapportér spam: Kun gruppemoderatorer vil se det. + Rapportér medlemsprofil: Kun gruppemoderatorer vil se den. + Rapportér overtrædelse: Kun gruppemoderatorer vil se det. + Rapportér indhold: Kun gruppemoderatorer vil se det. + Rapportér andet: Kun gruppemoderatorer vil se det. + Rapport sendt til moderatorer + Du kan se dine rapporter i Chat med administratorer. + Deltag i gruppen + Forbinde + Send kontaktanmodning? + efter din anmodning er accepteret.]]> + Send anmodning uden besked + Send anmodning + Du kan ikke sende beskeder! + kontakten er ikke klar + anmodningen er sendt + kontakt slettet + ikke synkroniseret + kontakt deaktiveret + du er observatør + Kontakt gruppeadministratoren. + anmodning om tilmelding afvist + gruppen er slettet + fjernet fra gruppen + du forlod + kan ikke sende beskeder + du er observatør + gennemgået af administratorer + medlemmet har en gammel version + Billede + Venter på billede + Bedt om at modtage billedet + Billede sendt + Venter på billede + Billedet modtages, når din kontaktperson har uploadet det. + Billedet modtages, når din kontakt er online. Vent eller tjek senere! + Billede gemt i Galleri + Video + Venter på video + Bedt om at modtage videoen + Video sendt + Venter på video + Videoen modtages, når din kontaktperson har uploadet den. + Video modtages, når din kontakt er online. Vent eller tjek senere! + Fil + Stor fil! + Din kontakt sendte en fil, der er større end den aktuelt understøttede maksimale størrelse (%1$s). + Den maksimalt understøttede filstørrelse er i øjeblikket %1$s. + Venter på fil + Filen modtages, når din kontaktperson har uploadet den. + Filen modtages, når din kontakt er online. Vent eller tjek senere! + Fil gemt + Filen blev ikke fundet + Fejl ved lagring af fil + Indlæser filen + Vent venligst, mens filen indlæses fra den tilknyttede mobil. + Filfejl + Midlertidig filfejl + Åbn med %s + Talebesked + Talebesked (%1$s) + Talebesked… + Notifikationer + Deaktiver automatisk sletning af beskeder? + Ændre automatisk sletning af beskeder? + Beskeder i denne chat vil aldrig blive slettet. + Denne handling kan ikke fortrydes - beskeder sendt og modtaget i denne chat tidligere end valgt vil blive slettet. + Deaktiver sletning af beskeder + Slet chatbeskeder fra din enhed. + forbinde + åbn + besked + opkald + søg + video + Slet kontakt? + Kontakt og alle beskeder vil blive slettet - dette kan ikke fortrydes! + Kontakten vil blive slettet - dette kan ikke fortrydes! + Fortsæt samtalen + Slet kun samtalen + Bekræft sletning af kontakt? + Slet og underret kontakt + Slet uden meddelelse + Slet kontakt + Samtalen er slettet! + Du kan sende beskeder til %1$s fra arkiverede kontakter. + Kontakt slettet! + Du kan stadig se samtalen med %1$s på listen over chats. + Angiv kontaktnavn… + Angiv chatnavn… + Forbundet + Afbrudt + Fejl + Indtil + Ændre modtageradresse? + Modtageradressen vil blive ændret til en anden server. Adresseændringen vil blive gennemført, når afsenderen er online. + Genforhandle kryptering? + Krypteringen fungerer, og den nye krypteringsaftale er ikke påkrævet. Det kan resultere i forbindelsesfejl! + Genforhandle + Rette forbindelse? + Forbindelsen kræver genforhandling af krypteringen. + Lav + Genforhandling af kryptering i gang. + Vis sikkerhedsnummer + Verificier sikkerhedsnummer + Brug inkognito -profil + Send besked + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 6646720c5c..db5e983983 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -69,7 +69,7 @@ Verbindungsfehler (AUTH) Entweder hat Ihr Kontakt die Verbindung gelöscht, oder dieser Link wurde bereits verwendet, es könnte sich um einen Fehler handeln – bitte melden Sie ihn uns. \nBitten Sie Ihren Kontakt darum, einen weiteren Verbindungs-Link zu erzeugen, um sich neu verbinden zu können, und stellen Sie sicher, dass Sie eine stabile Netzwerkverbindung haben. - Fehler beim Akzeptieren der Kontaktanfrage + Fehler beim Annehmen der Kontaktanfrage Der Absender hat möglicherweise die Verbindungsanfrage gelöscht. Fehler beim Löschen des Kontakts Fehler beim Löschen der Gruppe @@ -271,7 +271,7 @@ QR-Code scannen.]]> In mobiler App öffnen“ und dann auf „Verbinden“.]]> - Die Verbindungsanfrage akzeptieren? + Die Verbindungsanfrage annehmen? Wenn Sie ablehnen, wird der Absender NICHT benachrichtigt. Akzeptieren Inkognito akzeptieren @@ -292,10 +292,10 @@ Stummschaltung aufheben Sie haben Ihren Kontakt eingeladen - Sie haben die Verbindung akzeptiert + Sie haben die Verbindung angenommen Ausstehende Verbindung löschen? Der Kontakt, mit dem Sie diesen Link geteilt haben, kann sich NICHT verbinden! - Die von Ihnen akzeptierte Verbindung wird abgebrochen! + Die von Ihnen angenommene Verbindung wird abgebrochen! Ihr Kontakt ist noch nicht verbunden! Ihr Kontakt muss online sein, damit die Verbindung hergestellt werden kann. @@ -326,7 +326,7 @@ Dieser Link ist kein gültiger Verbindungslink! Verbindungsanfrage gesendet! Sie werden mit der Gruppe verbunden, sobald das Endgerät des Gruppen-Hosts online ist. Bitte warten oder schauen Sie später nochmal nach! - Sie werden verbunden, sobald Ihre Verbindungsanfrage akzeptiert wird. Bitte warten oder schauen Sie später nochmal nach! + Sie werden verbunden, sobald Ihre Verbindungsanfrage angenommen wird. Bitte warten oder schauen Sie später nochmal nach! Sie werden verbunden, sobald das Endgerät Ihres Kontakts online ist. Bitte warten oder schauen Sie später nochmal nach! den QR-Code während eines Videoanrufs anzeigen oder einen Einladungslink über einen anderen Kanal mit Ihrem Kontakt teilen.]]> Ihr Chat-Profil wird @@ -727,7 +727,7 @@ Eingeladen Verbindung (erstellt) Verbinde (nach einer Einladung) - Verbindung (akzeptiert) + Verbindung (angenommen) Verbindung (angekündigt) Verbunden Vollständig @@ -1014,7 +1014,7 @@ Moderieren Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet. Sie sind Beobachter - Sie können keine Nachrichten versenden! + Sie sind Beobachter Beobachter Anfängliche Rolle Nachricht des Mitglieds löschen\? @@ -1202,7 +1202,7 @@ Freunde einladen Lassen Sie uns über SimpleX Chat schreiben Profil-Aktualisierung wird an Ihre Kontakte gesendet. - Einstellungen von „automatisch akzeptieren“ speichern + SimpleX-Adress-Einstellungen speichern Einstellungen speichern\? Die Adresse mit Kontakten teilen\? Teilen beenden @@ -1453,8 +1453,8 @@ Erstellen eines neuen Profils in der Desktop-App. 💻 Inkognito beim Verbinden einschalten. - Verbindung mit dem Directory-Service (BETA)!\n- Empfangsbestätigungen (für bis zu 20 Mitglieder).\n- Schneller und stabiler. - Direktnachricht senden - Direkt miteinander verbunden + Zum Verbinden senden + Angefragte Verbindung Erweitern Verbindungsanfrage wiederholen? Gelöschter Kontakt @@ -1465,7 +1465,7 @@ Profil erstellen %s und %s Ihrer Gruppe beitreten? - %1$s.]]> + %1$s.]]> Das ist Ihr eigener Einmal-Link! %d Nachrichten als gelöscht markiert Gruppe besteht bereits! @@ -1641,7 +1641,7 @@ Interner Fehler %s wird eine nicht unterstützte Version verwendet. Bitte stellen Sie sicher, dass beide Geräte die selbe Version nutzen]]> %s ist besetzt]]> - Ehemaliges Mitglied %1$s + Mitglied %1$s Die Ausführung dieser Funktion dauert zu lange: %1$d Sekunden: %2$s Langsame Funktion Langsame API-Aufrufe anzeigen @@ -1661,7 +1661,7 @@ Zum Verbinden den Link einfügen! Mit reduziertem Akkuverbrauch. Aktueller Nachrichtenverlauf und verbesserter Gruppenverzeichnis-Bot. - In der Suchleiste werden nun auch Einladungslinks akzeptiert. + In der Suchleiste werden nun auch Einladungslinks angenommen. Für Alle freigeben Mitglied für Alle freigeben? wurde blockiert @@ -1910,7 +1910,7 @@ Mit reduziertem Akkuverbrauch. Keine Information Debugging-Zustellung - Nachrichten-Warteschlangen-Information + Information Nachrichtenwarteschlange Server-Warteschlangen-Information: %1$s \n \nZuletzt empfangene Nachricht: %2$s @@ -2050,7 +2050,7 @@ Die Weiterleitungs-Server-Version ist nicht kompatibel mit den Netzwerkeinstellungen: %1$s. Die Ziel-Server-Adresse von %1$s ist nicht mit den Einstellungen des Weiterleitungs-Servers %2$s kompatibel. Fehler beim Verbinden zum Weiterleitungs-Server %1$s. Bitte versuchen Sie es später erneut. - Medium verpixeln + Medien verpixeln Weich Stark Mittel @@ -2138,7 +2138,7 @@ Archiv entfernen? Ihre Anmeldeinformationen können unverschlüsselt versendet werden. Verwenden Sie keine Anmeldeinformationen mit einem Proxy. - Ihre Verbindung wurde auf %s verschoben, aber während der Weiterleitung auf das Profil trat ein unerwarteter Fehler auf. + Ihre Verbindung wurde auf %s verschoben, aber während des Profil-Wechsels trat ein Fehler auf. Stellen Sie sicher, dass die Proxy-Konfiguration richtig ist. Fehler beim Speichern des Proxys Passwort @@ -2257,7 +2257,7 @@ Adress- oder Einmal-Link? App-Symbolleiste Verpixeln - nur mit einem Kontakt genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger.]]> + nur mit einem Kontakt genutzt werden - teilen Sie ihn nur persönlich oder über einen beliebigen Messenger.]]> %s.]]> %s.]]> Die Nutzungsbedingungen wurden akzeptiert am: %s @@ -2288,7 +2288,7 @@ Geschäftliche Adresse Geschäftliche Chats Nehmen Sie Team-Mitglieder in Ihre Unterhaltungen auf. - Die App läuft immer im Hintergrund ab + App läuft dauerhaft im Hintergrund In diesem Chat sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. Kein Hintergrund-Service Nachrichten alle 10 Minuten überprüfen @@ -2449,11 +2449,10 @@ Alle neuen Nachrichten dieser Mitglieder werden nicht angezeigt! Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam. Datenschutz- und Nutzungsbedingungen. - Akzeptieren + Annehmen Server-Betreiber konfigurieren Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. Verbindungs-Link wird nicht unterstützt - Kurze Links verwenden (BETA) Verkürzter Link Vollständiger Link SimpleX-Kanal-Link @@ -2462,4 +2461,150 @@ Aus TCP-Port 443 nur für voreingestellte Server verwenden. Voreingestellte Server + %d Chats mit Mitgliedern + %d Chat(s) + Meldung wurde an die Moderatoren gesendet + Sie haben dieses Mitglied angenommen + Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\"). + Überprüfung der Mitglieder + alle + Aus + Als Beobachter übernehmen + Mitglied annehmen + Chats mit Mitgliedern + Chat mit Administratoren + Keine Chats mit Mitgliedern + Ablehnen + hat Sie angenommen + Chat mit einem Mitglied + %d Nachrichten + Mitglied wird der Gruppe beitreten. Annehmen? + Ein neues Mitglied will der Gruppe beitreten. + Überprüfung + Von Administratoren überprüft + Aufnahme von Mitgliedern festlegen + Speichern der Aufnahme-Einstellungen? + Sie können Ihre Meldungen im Chat mit den Administratoren sehen. + Chat mit Administratoren + %1$s angenommen + Als Mitglied übernehmen + Fehler beim Annehmen des Mitglieds + Aufnahme von Mitgliedern + Ausstehende Überprüfung + Chat mit einem Mitglied + Übernehmen + Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können. + Gruppe wurde gelöscht + Beitrittsanfrage abgelehnt + Aus der Gruppe entfernt + Sie haben die Gruppe verlassen + Kontakt deaktiviert + Nicht synchronisiert + Fehler beim Löschen des Chats mit dem Mitglied + Kontakt nicht bereit + Sie können keine Nachrichten senden! + Chat löschen + Chat mit dem Mitglied löschen? + Mitglied ablehnen? + Es können keine Nachrichten gesendet werden + Kontakt gelöscht + Das Mitglied hat eine alte App-Version + Adresse aktualisieren + Kontaktanfrage annehmen + Nachricht hinzufügen + Ende-zu-Ende-Verschlüsselung geschützt.]]> + sobald Ihre Anfrage akzeptiert wurde.]]> + Verbinden + Kontakt sollte annehmen… + Fehler beim Ändern des Profils + Fehler beim Öffnen des Chats + Fehler beim Öffnen der Gruppe + Fehler bei der Ablehnung der Kontaktanfrage + Der Gruppe beitreten + Chat öffnen + Neuen Chat öffnen + Neue Gruppe öffnen + Zum Akzeptieren öffnen + Zum Verbinden öffnen + Zum Beitreten öffnen + Die Adresse wird gekürzt sein, und Ihr Profil wird über die Adresse geteilt. + Kontakt-Anfrage ablehnen + Anfrage wurde gesendet + Kontakt-Anfrage senden? + Anfrage senden + Anfrage ohne Nachricht senden + Wird nach der Verbindung an Ihren Kontakt gesendet. + Gruppen-Link aktualisieren? + Aktualisieren + Adresse aktualisieren? + Der Absender wird NICHT benachrichtigt. + Begrüßungsmeldung + Ihr Profil + Änderung des Profils nicht möglich + Wenn Sie nach dem Verbindungsversuch ein anderes Profil verwenden möchten, löschen Sie den Chat und verwenden Sie den Link erneut. + Chat mit Administratoren + Mit Mitgliedern chatten bevor sie beitreten. + Schneller miteinander verbinden! 🚀 + Weniger Datenverkehr in mobilen Netzen. + Sobald Sie auf Verbinden tippen, erhalten Sie sofort eine Nachricht. + Neue Gruppen-Rolle: Moderator + Keine private Routing-Sitzung + Zeitüberschreitung der privaten Routing-Sitzung + Protokoll Hintergrund-Zeitüberschreitung + Entfernt Nachrichten und blockiert Mitglieder. + Gruppenmitglieder überprüfen + Senden Sie Ihr privates Feedback an Gruppen. + TCP-Verbindung Hintergrund-Zeitüberschreitung + Profil wird geladen… + Bio: + Kurze Beschreibung: + Ihre Biografie: + Biografie zu lang + Beschreibung zu lang + Kontaktanfrage annehmen + Geschäftliche Verbindung + Gruppe + Verbinden tippen, um zu chatten + Verbinden tippen, um die Anfrage zu senden + Tippen, um der Gruppe beizutreten + Ihr geschäftlicher Kontakt + Ihr Kontakt + Ihre Gruppe + 4 neue Sprachen für die Bedienoberfläche + Katalanisch, Indonesisch, Rumänisch und Vietnamesisch - Dank unserer Nutzer! + Ihre Adresse erstellen + Verschwindende Nachrichten sind per Voreinstellung aktiviert. + Ihre Chats übersichtlich halten + Sie können eine Profil-Biografie und eine Begrüßungsmeldung eingeben. + Ihre Adresse teilen + Verkürzte SimpleX-Adresse + Die Zeit bis zum Verschwinden wird nur für neue Kontakte eingestellt. + Ihre Adresse aktualisieren + Inkognito-Profil nutzen + Begrüßen Sie Ihre Kontakte 👋 + Alte Adresse teilen + Alten Link teilen + Der Link wird gekürzt sein, und das Gruppen-Profil wird über den Link geteilt. + Gruppen-Link aktualisieren + KONTAKTANFRAGEN VON GRUPPEN + Mitglied ist gelöscht - Anfrage kann nicht angenommen werden + Angefragte Verbindung von Gruppe %1$s + Diese Einstellung gilt für Ihr aktuelles Profil + Erlauben Sie Dateien und Medien nur dann, wenn es Ihr Kontakt ebenfalls erlaubt. + Erlauben Sie Ihren Kontakten Dateien und Medien zu senden. + Bot + Sowohl Sie, als auch Ihr Kontakt können Dateien und Medien senden. + Veraltete Optionen + In diesem Chat sind Dateien und Medien nicht erlaubt. + Nur Sie können Dateien und Medien senden. + Nur Ihr Kontakt kann Dateien und Medien senden. + Trackingfreien Link öffnen + Vollständigen Link öffnen + Bot für die Nutzung erlaubt + Das Senden von Dateien und Medien ist nicht erlaubt. + Link-Tracking entfernen + Verbinden tippen, um den Bot zu nutzen. + Um Befehle senden zu können, müssen Sie verbunden sein. + SimpleX Relais-Link + Fehler beim Versuch, den Chat mit einem Mitglied als gelesen zu markieren diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index 179c7fec52..2d27bb3592 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -395,4 +395,4 @@ Καλύτερο για τη ζωή της μπαταρίας . Θα λαμβάνετε ειδοποιήσεις μόνο όταν εκτελείται η εφαρμογή (ΧΩΡΙΣ υπηρεσία παρασκηνίου).]]> Beta Καλύτερες κλήσεις - \ No newline at end of file + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 5667c42d2d..fa356715db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -14,16 +14,16 @@ Añadir servidores mediante el escaneo de códigos QR. Añadir servidores predefinidos Todos los miembros del grupo permanecerán conectados. - Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite para tí. (24 horas) - Android Keystore se usará para almacenar de forma segura la frase de contraseña después de cambiarla o reiniciar la aplicación - permitirá recibir notificaciones. - Permites a tus contactos enviar mensajes temporales - Permites a tus contactos enviar mensajes de voz. + Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite. (24 horas) + Android Keystore se usará para almacenar la frase de contraseña de forma segura después de cambiarla o reiniciar la aplicación - permitirá recibir notificaciones. + Permites que tus contactos envien mensajes temporales. + Permites que tus contactos envien mensajes de voz. siempre La aplicación sólo puede recibir notificaciones cuando se está ejecutando. No se iniciará ningún servicio en segundo plano. ICONO DE LA APLICACIÓN La optimización de la batería está activa, desactivando el servicio en segundo plano y las solicitudes periódicas de nuevos mensajes. Puedes volver a activarlos en Configuración. El servicio está siempre en funcionamiento en segundo plano. Las notificaciones se muestran en cuanto haya mensajes nuevos. - Se puede desactivar en Configuración – las notificaciones se seguirán mostrando mientras la app esté en funcionamiento.]]> + Se puede desactivar en la configuración. En ese caso las notificaciones se seguirán mostrando mientras la aplicación esté en funcionamiento.]]> Siempre activo Permitir y después: @@ -44,7 +44,7 @@ Aceptar llamada (sin cifrar) llamada - Llamadas y videollamadas + Llamadas y Videollamadas Audio desactivado Audio activado ID de mensaje erróneo @@ -52,10 +52,10 @@ Se eliminarán todos los chats y mensajes. ¡No puede deshacerse! Aceptar Se permiten mensajes temporales. - Android Keystore se usará para almacenar de forma segura la frase de contraseña - permite que el servicio de notificación funcione. + Android Keystore se usará para almacenar la frase de contraseña de forma segura - permite que el servicio de notificaciones funcione. Añadir perfil Color - Permites a tus contactos eliminar irreversiblemente los mensajes enviados. (24 horas) + Permites que tus contactos eliminan irreversiblemente los mensajes enviados. (24 horas) Se permiten los mensajes de voz pero sólo si tu contacto también los permite. Se permiten mensajes directos entre miembros. Se permite la eliminación irreversible de mensajes. (24 horas) @@ -72,7 +72,7 @@ Añadir a otro dispositivo Versión de la aplicación: v%s Solicita recibir la imagen - Recuerda: Si la pierdes NO podrás recuperar o cambiar la frase de contraseña.]]> + Recuerda: Si se pierde NO podrás recuperar o cambiar la frase de contraseña.]]> Tanto tú como tu contacto podéis enviar mensajes de voz. ¡Consume más energía! La aplicación está siempre en segundo plano y las notificaciones se muestran de inmediato.]]> Tanto tú como tu contacto podéis eliminar los mensajes enviados de forma irreversible. (24 horas) @@ -108,7 +108,7 @@ Llamada con cifrado de extremo a extremo cifrado de extremo a extremo mensaje duplicado - Herramientas para desarrolladores + Herramientas de desarrollo Eliminar los archivos de todos los perfiles Activar ¡Base de datos cifrada! @@ -223,7 +223,7 @@ %ds eliminado ¿Conectar mediante dirección de contacto? - ¿Unirte al grupo? + ¿Te unes al grupo? ¿Conectar mediante enlace de invitación? Conectar conectado @@ -304,7 +304,7 @@ Imagen guardada en la Galería El archivo se recibirá cuando el contacto esté en línea, por favor espera o revisa más tarde. Enlace de invitación de un uso - Pegar el enlace recibido + Pega el enlace recibido Error al guardar perfil de grupo Salir sin guardar Archivo guardado @@ -356,7 +356,7 @@ Base de datos cifrada Error al expulsar miembro Los miembros pueden enviar mensajes de voz. - en modo incógnito mediante enlace de dirección del contacto + en modo incógnito mediante dirección de contacto ¡Error al crear perfil! No se pudo cargar el chat Fallo en la carga de chats @@ -438,13 +438,13 @@ Para verificar el cifrado de extremo a extremo con tu contacto, compara (o escanea) el código en ambos dispositivos. La base de datos no está cifrada. Escribe una contraseña para protegerla. Asegúrate de que las direcciones del servidor SMP tienen el formato correcto, están separadas por líneas y no están duplicadas. - Notificación instantánea + Notificaciones instantáneas Configuración avanzada Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes. Cómo afecta a la batería Instantánea - Unirte - Unirte en modo incógnito + Unirme + Unirme en modo incógnito indirecta (%1$s) Claro Activado @@ -463,7 +463,7 @@ ¡Invitación caducada! ha salido Mensajes en vivo - Notificación instantánea + Notificaciones instantáneas Servicio El mensaje se marcará para eliminar. El destinatario o destinatarios podrán revelar este mensaje. ¡Mensaje en vivo! @@ -495,7 +495,7 @@ Error en la entrega del mensaje Lo más probable es que este contacto haya eliminado la conexión contigo. Moderar - unirte como %s + Unirme como %s Sólo se pueden enviar 10 imágenes al mismo tiempo ¡Archivo grande! Silenciar @@ -518,7 +518,7 @@ OK (sólo almacenado por miembros del grupo) Ayuda sintaxis markdown - Servidores y Red + Servidores y Redes Se usarán hosts .onion si están disponibles. cursiva Llamada audio entrante @@ -530,7 +530,7 @@ Sin archivos recibidos o enviados Mensajes Contraseña nueva… - ¿Unirte al grupo? + ¿Te unes al grupo? Entrando al grupo Error en Keystore Invitar miembros @@ -715,7 +715,7 @@ envío no autorizado Escribe un nombre para el contacto Error desconocido - El rol cambiará a %s. Todos serán notificados. + El rol cambiará a %s. Se notificará en el grupo. La seguridad de SimpleX Chat ha sido auditada por Trail of Bits. Los mensajes enviados se eliminarán una vez transcurrido el tiempo establecido. Mensajes de chat SimpleX @@ -738,7 +738,7 @@ Esta acción es irreversible. Los mensajes enviados y recibidos anteriores a la selección serán eliminados. Podría tardar varios minutos. Esta configuración se aplica a los mensajes del perfil actual ¡Esta cadena no es un enlace de conexión! - SimpleX se ejecuta en segundo plano en lugar de usar notificaciones push.]]> + SimpleX se ejecuta en segundo plano.]]> Configuración Altavoz desactivado Inciar chat nuevo @@ -771,8 +771,8 @@ Sin identificadores de usuario. Este grupo ya no existe. Establecer 1 día - ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate. - ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate. + ¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate. + ¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate. Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC. Aislamiento de transporte (para compartir con tu contacto) @@ -855,7 +855,7 @@ Ya tienes un perfil con este nombre mostrado. Por favor, selecciona otro nombre. Abrir en aplicación móvil.]]> ponerte en contacto con los desarrolladores de SimpleX Chat para consultas y para recibir actualizaciones.]]> - ¡No puedes enviar mensajes! + eres observador Puedes usar la sintaxis markdown para dar formato a tus mensajes: Debes usar la versión más reciente de tu base de datos ÚNICAMENTE en un dispositivo, de lo contrario podrías dejar de recibir mensajes de algunos contactos. El contacto debe estar en línea para completar la conexión. @@ -864,14 +864,14 @@ \nEsta acción es irreversible. Tu perfil, contactos, mensajes y archivos actuales se perderán. Tu perfil aleatorio Te conectarás cuando tu solicitud se acepte, por favor espera o revisa más tarde. - Te conectarás cuando el dispositivo de tu contacto esté en línea, por favor espera o revisa más tarde. + Te conectarás cuando el dispositivo del contacto esté en línea, por favor espera o revisa más tarde. Se te pedirá autenticarte cuando inicies la aplicación o sigas usándola tras 30 segundos en segundo plano. Estás intentando invitar a un contacto con el que compartes un perfil incógnito a un grupo en el que usas tu perfil principal Mediante navegador mediante %1$s Servicio SimpleX Chat ¡Bienvenido %1$s! - has sido invitado al grupo + Has sido invitado al grupo Esperando archivo Esperando imagen Mensaje de voz (%1$s ) @@ -880,7 +880,7 @@ Has sido invitado a un grupo. Únete para conectar con sus miembros. has expulsado a %1$s Tú: %1$s - Puedes compartir un enlace o código QR para que cualquiera pueda unirse al grupo. Si decides eliminarlo más tarde, los miembros del grupo se mantendrán. + Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. Mis preferencias Con mensaje de bienvenida opcional. @@ -914,17 +914,17 @@ Permites SimpleX Tu perfil se enviará al contacto del que has recibido este enlace. - Te conectarás con todos los miembros del grupo. + Conectarás con todos los miembros del grupo. tu Estás conectado al servidor usado para recibir mensajes de este contacto. - mediante enlace de dirección de contacto + mediante dirección de contacto mediante enlace de grupo enlace de un solo uso has compartido enlace de un solo uso en módo incógnito No tienes chats El contacto ha enviado un archivo mayor al máximo admitido (%1$s ). %1$d mensaje(s) omitido(s) - Dejarás de recibir mensajes de este grupo. El historial del chat se conservará. + Dejarás de recibir mensajes del grupo. El historial del chat se conservará. Mostrar código de seguridad Para poder enviar mensajes de voz antes debes permitir que tu contacto pueda enviarlos. ¡Mensajes de voz no permitidos! @@ -937,7 +937,7 @@ Tu servidor Dirección de tu servidor Tu perfil actual - Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. + Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. Sistema Añadir mensaje de bienvenida Llamadas y videollamadas @@ -965,7 +965,7 @@ Ahora con soporte bluetooth y otras mejoras. ¡Guarda un mensaje para ser mostrado a los miembros nuevos! Interfaz en chino y español - ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate. + ¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate. Error al actualizar privacidad de usuario Confirmar contraseña Reducción consumo de batería @@ -996,8 +996,8 @@ El archivo se recibirá cuando el contacto termine de subirlo. La imagen se recibirá cuando el contacto termine de subirla. Mostrar opciones para desarrolladores - Ocultar: - Mostrar: + Oculta: + Muestra: Eliminar perfil Contraseña del perfil Mostrar perfil oculto @@ -1046,7 +1046,7 @@ %d minutos Introduce código Inmediatamente - Por favor, recuerda y guarda el código de acceso en un lugar seguro. ¡No hay forma de recuperar un código perdido! + Por favor, recuerda y guarda el código de acceso en un lugar seguro. ¡No hay manera de recuperar un código perdido! ¡Bloqueo SimpleX no activado! Puedes activar el Bloqueo SimpleX a través de Configuración. Confirma código @@ -1081,7 +1081,7 @@ ¿Dejar de recibir el archivo\? ¿Dejar de enviar el archivo\? Se permiten las llamadas pero sólo si tu contacto también las permite. - Permites que tus contactos puedan llamarte. + Permites que tus contactos te llamen. Llamadas y videollamadas Las llamadas y videollamadas no están permitidas. " @@ -1093,7 +1093,7 @@ Código de acceso de la aplicación Interfaz en polaco Úsalo en lugar de la autenticación del sistema. - ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate. + ¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate. Vídeos y archivos de hasta 1Gb ¡Rápido y sin necesidad de esperar a que el remitente esté en línea! Cambiar perfil @@ -1110,7 +1110,7 @@ Dirección SimpleX Cuando alguien solicite conectarse podrás aceptar o rechazar su solicitud. Compartir dirección - Introduce mensaje de bienvenida… + Deja un mensaje de bienvenida… SimpleX Color adicional Secundario adicional @@ -1124,7 +1124,7 @@ Continuar Tema oscuro Personalizar tema - Introduce mensaje de bienvenida… (opcional) + Deja un mensaje de bienvenida… (opcional) Crea una dirección para que otras personas puedan conectar contigo. No crear dirección SimpleX ¡Hola! @@ -1136,7 +1136,7 @@ Asegúrate de que el archivo tiene la sintaxis YAML correcta. Exporta el tema para tener un ejemplo de la estructura del archivo de tema. La actualización del perfil se enviará a tus contactos. Mensaje recibido - Guardar configuración de auto aceptar + Guardar configuración de dirección SimpleX Puedes compartir tu dirección como enlace o código QR para que cualquiera pueda conectarse contigo. ¿Guardar configuración\? Secundario @@ -1211,7 +1211,7 @@ Registro actualiz 30 segundos 5 minutos - Permitir que tus contactos añadan reacciones a los mensajes. + Permites que tus contactos añadan reacciones a los mensajes. Eliminado Se permiten las reacciones a los mensajes pero sólo si tu contacto también las permite. Temas personalizados @@ -1219,7 +1219,7 @@ - mensajes de voz de hasta 5 minutos. \n- tiempo personalizado para mensajes temporales. \n- historial de edición. - ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate. + ¡Agradecimiento a los colaboradores! Puedes contribuir a través de Weblate. Mensajes mejorados Al introducirlo todos los datos son eliminados. Personalizar y compartir temas de color. @@ -1229,10 +1229,10 @@ Interfaz en japonés y portugués sin texto Han ocurrido algunos errores no críticos durante la importación: - ¿Cerrar\? + ¿Salir de SimpleX? APLICACIÓN Reiniciar - Cerrar + Salir Las notificaciones dejarán de funcionar hasta que vuelvas a iniciar la aplicación Desactivado Error al cancelar cambio de dirección @@ -1379,8 +1379,8 @@ - conexión al servicio de directorio (BETA)! \n- confirmaciones de entrega (hasta 20 miembros). \n- mayor rapidez y estabilidad. - Enviar mensaje directo - conectado directamente + envia para conectar + conexión solicitada Expandir Error de renegociación de cifrado contacto eliminado @@ -1424,7 +1424,7 @@ Grupo abierto Conexión finalizada (este dispositivo v%s)]]> - ¡Los mensajes de %s serán mostrados! + ¡Los mensajes nuevos de %s serán mostrados! Nombre de este dispositivo… Error Conectar con ordenador @@ -1520,8 +1520,8 @@ El código QR escaneado no es un enlace de SimpleX. El texto pegado no es un enlace de SimpleX. Permitir acceso a la cámara - Podrás ver el enlace de invitación en detalles de conexión. - ¿Guardar invitación no usada? + Puedes ver el enlace de invitación de nuevo en los detalles de la conexión. + ¿Guardar enlace no usado? Comparte este enlace de un solo uso Crear grupo: crea un grupo nuevo.]]> Historial visible @@ -1534,7 +1534,7 @@ Añadir contacto Pulsa para escanear Guardar - Pulsa para pegar el enlace + Pulsa aquí para pegar el enlace Buscar o pegar enlace SimpleX Con uso reducido de batería. bloqueado por administrador @@ -1552,7 +1552,7 @@ ¿Bloqear miembro para todos? Creado: %s Bloquear para todos - ¿Desbloquear el miembro para todos? + ¿Desbloquear al miembro para todos? Desbloquear para todos bloqueado bloqueado por administrador @@ -1577,7 +1577,7 @@ El ordenador tiene un código de invitación incorrecto El ordenador ha sido desconectado estado desconocido - Migración de la base de datos en curso. \nPodría tardar varios minutos. + Migrando base de datos.\nPuede tardar varios minutos. El ordenador tiene una versión sin soporte. Por favor, asegúrate de usar la misma versión en ambos dispositivos el contacto %1$s ha cambiado a %2$s perfil actualizado @@ -1609,7 +1609,7 @@ Por favor, informa a los desarrolladores: \n%s Reiniciar chat - Miembro pasado %1$s + Miembro %1$s el miembro %1$s ha cambiado a %2$s dirección de contacto eliminada ha eliminado la imagen del perfil @@ -1971,7 +1971,7 @@ Errores de confirmación Conectando con el contacto, por favor espera o revisa más tarde. Estado de tu conexión y servidores. - Conecta más rápido con tus amigos + Conéctate más rápido con tus amigos Controla tu red Protege tu dirección IP y tus conexiones. ¿Permitir llamadas? @@ -2036,7 +2036,7 @@ Debes permitir que tus contacto te llamen para poder llamarles. Error al conectar con el servidor de reenvío %1$s. Por favor, inténtalo más tarde. La dirección del servidor de reenvío es incompatible con la configuración de red: %1$s. - El servidor de reenvío %1$s no ha podido conectarse al servidor de destino %2$s. Por favor, intentalo más tarde. + El servidor de reenvío %1$s no ha podido conectarse al servidor de destino %2$s. Por favor, inténtalo más tarde. La versión del servidor de reenvío es incompatible con la configuración de red: %1$s. Ningún contacto filtrado Difumina para mayor privacidad @@ -2072,7 +2072,7 @@ Los mensajes han sido eliminados después de seleccionarlos. ¡Nada para reenviar! Guardando %1$s mensajes - No uses credenciales con proxy. + No se usan credenciales con proxy. Error guardando proxy Contraseña Autenticación proxy @@ -2087,7 +2087,7 @@ Error al cambiar perfil Selecciona perfil de chat Perfil a compartir - Tu conexión ha sido trasladada a %s pero ha ocurrido un error inesperado al redirigirte al perfil. + Tu conexión ha sido trasladada a %s pero ha ocurrido un error al cambiar de perfil. Sonido silenciado Error al iniciar WebView. Asegúrate de tener WebView instalado y que sea compatible con la arquitectura amr64.\nError: %s Forma del mensaje @@ -2120,9 +2120,9 @@ Compartir enlace de un uso con un amigo Comparte tu dirección SimpleX en redes sociales. Ajustes de dirección - Crear enlace de un uso + Crear enlace de un solo uso Para redes sociales - ¿Dirección SimpleX o enlace de un uso? + ¿Dirección SimpleX o enlace de un solo uso? Operadores de servidores Operadores de red Las condiciones de los operadores habilitados serán aceptadas después de 30 días. @@ -2143,7 +2143,7 @@ El operador del servidor ha cambiado. El protocolo del servidor ha cambiado. Barras de herramientas - Difuminar + Difuminado Navegación en el chat mejorada Descentralización de la red - El chat abre en el primer mensaje no leído.\n- Desplazamiento hasta los mensajes citados. @@ -2222,8 +2222,8 @@ El chat será eliminado para tí. ¡No puede deshacerse! Sólo los propietarios del chat pueden cambiar las preferencias. El miembro será eliminado del chat. ¡No puede deshacerse! - El rol cambiará a %s. Todos serán notificados. - Dejarás de recibir mensajes de este chat. El historial del chat se conserva. + El rol cambiará a %s. Se notificará en el chat. + Dejarás de recibir mensajes del chat. El historial del chat se conserva. Cómo ayuda a la privacidad Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién. Tu perfil de chat será enviado a los miembros de chat @@ -2359,7 +2359,7 @@ rechazado rechazado ¿Expulsar miembros? - ¡Los mensajes de estos miembros serán mostrados! + ¡Los mensajes nuevos de estos miembros serán mostrados! ¿Desbloquear los miembros para todos? ¡Todos los mensajes nuevos de estos miembros estarán ocultos! ¿Bloquear miembros para todos? @@ -2382,9 +2382,154 @@ Enlace corto Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible. Enlace de conexión no compatible - Usar enlaces cortos (BETA) Usar puerto TCP 443 solo en servidores predefinidos. Todos los servidores Servidores predefinidos No + Admisión miembro + Aceptar como observador + Error al aceptar el miembro + ¿Guardar configuración? + Por favor, espera a que tu solicitud sea revisada por los moderadores del grupo. + has aceptado al miembro + pendiente de revisión + por revisar + Chat con administradores + Chat con miembro + en revisión por los administradores + %1$s aceptado + desactivado + Admisión de miembros + el miembro usa una versión antigua + Sin chats + Error al eliminar el chat con el miembro + %d chats con miembros + %d mensajes + un chat con miembro + %d chat(s) + Informe enviado a los moderadores + no se pueden enviar mensajes + contacto eliminado + contacto desactivado + en espera de ser aceptado + el grupo ha sido eliminado + no sincronizado + expulsado del grupo + petición para unirse rechazada + ¡No puedes enviar mensajes! + Puedes ver tus informes en Chat con administradores + has salido + te ha aceptado + Un miembro nuevo desea unirse al grupo. + todos + Chat con miembros + Chat con administradores + Eliminar chat + ¿Eliminar chat con miembro? + Rechazar + ¿Rechazar al miembro? + Revisar miembros + Revisa a los miembros antes de admitirles en el grupo. + Aceptar + Aceptar miembro + Aceptar como miembro + El miembro se unirá al grupo, ¿aceptas al miembro? + Actualizar dirección + Aceptar petición de contacto + Añadir mensaje + después de que tu petición sea aceptada.]]> + Chatea con los administradores + Chatea con el miembro antes de unirse. + Conectar + ¡Conéctate más rápido! 🚀 + el contacto debe aceptarte… + Error al cambiar el perfil + Error al abrir el chat + Error al abrir el grupo + Error al rechazar la solicitud del contacto + Unirme al grupo + Menos tráfico en redes móviles. + Tras pulsar Contactar, mensajea ya. + Nuevo rol de grupo: Moderador + Ninguna sesión con enrutamiento privado + Abrir chat + Abrir chat nuevo + Abrir grupo nuevo + Abrir para aceptar + Abre para conectar + Abre para unirte + Timeout enrutamiento privado + La dirección pasará a ser corta y tu perfil será compartido mediante la dirección. + Timeout protocolo en segundo plano + Rechazar solicitud del contacto + Elimina mensajes y bloquea miembros. + petición enviada + Revisa los miembros del grupo + ¿Enviar solicitud de contacto? + Enviar solicitud + Enviar solicitud sin mensaje + Envía tu comentario privado a los grupos. + Enviadp a tu contacto tras la conexión. + ¿Actualizar enlace de grupo? + Actualizar + ¿Actualizar la dirección? + Timeout conexión TCP sp + El remitente NO será notificado. + Para usar otro perfil tras el intento de conexión, elimina el chat y usa el enlace de nuevo. + Mensaje de bienvenida + Tu perfil + cifrado de extremo a extremo.]]> + No se puede cambiar el perfil + Biografía: + Cargando perfil. . . + Descripción corta: + Tu biografía: + Biografía demasiado larga + Descripción demasiado larga + Aceptar solicitud de conexión + Conexión empresarial + Grupo + Pulsa Conectar para chatear + Pulsa Conectar para enviar solicitud + Pulsa Unirme al grupo + Mensajes temporales activados sólo para los contactos nuevos. + Usar perfil incógnito + Mi contacto empresarial + Mi contacto + Mi grupo + 4 idiomas nuevos + Catalán, Indonesio, Rumano y Vietnamita - gracias a los colaboradores! + Crea tu dirección + Activa por defecto los mensajes temporales + Mantén los chats limpios + Añade mensaje de bienvenida y biografía del perfil + Comparte tu dirección + Direcciones SimpleX cortas + Actualiza tu dirección + Da la bienvenida a tus contactos 👋 + Compartir dirección antigua + Compartir enlace antiguo + El enlace será corto y el perfil del grupo se compartirá mediante el enlace. + Actualizar enlace de grupo + SOLICITUDES DE CONTACTO EN GRUPOS + conexión solicitada desde el grupo %1$s + Esta configuración se aplica al perfil actual + Miembro eliminado, no puede aceptar solicitudes + Se permiten archivos y multimedia pero sólo si tu contacto también los permite. + Permes que tus contactos envíen archivos y multimedia. + Bot + Tanto tú como tu contacto podéis enviar archivos y multimedia. + Los archivos y multimedia no están permitidos en este chat. + Sólo tú puedes enviar archivos y multimedia. + Sólo tu contacto puede enviar archivos y multimedia. + Abre para usar el bot + Archivos y multimedia no permitidos. + Pulsa Conectar para usar el bot + Para enviar comandos debes estar conectado. + Opciones obsoletas + Abrir enlace limpio + Limpiar enlaces de seguimiento + Abrir enlace completo + Enlace de servidor SimpleX + Error al marcar el chat con miembro como leído diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index b8e99587f7..cbb4849067 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -11,14 +11,14 @@ لغو تغییر نشانی تغییر نشانی را لغو می‌کنید؟ درباره سیمپل‌اکس(SimpleX) - به وسیله نشانی مخاطب متصل می‌شوید؟ - به وسیله لینک یک بار مصرف متصل می‌شوید؟ - از نمایه ناشناس جدید استفاده کن - در حال گشودن پایگاه داده… - نمایه شما به مخاطبی که این لینک را از او دریافت کردید، فرستاده خواهد شد. - متصل شدن به صورت ناشناس - شما یک مسیر نامعتبر پرونده به اشتراک گذاشتید. موضوع را به توسعه‌دهندگان برنامه گزارش دهید. - هنوز از دریافت پرونده پشتیبانی نمی‌شود + با نشانی مخاطب ارتباط برقرار شود؟ + با لینک یک بار مصرف ارتباط برقرار شود؟ + استفاده از پروفایل ناشناس جدید + در حال باز کردن پایگاه داده… + پروفایل شما به مخاطبی که این لینک را از او دریافت کردید، فرستاده خواهد شد. + اتصال ناشناس + شما یک مسیر نامعتبر فایل به اشتراک گذاشتید. موضوع را به توسعه‌دهندگان برنامه گزارش دهید. + هنوز از دریافت فایل پشتیبانی نمی‌شود قالب پیام نامعتبر حذف شده خطا در نمایش پیام @@ -30,10 +30,10 @@ در حال اتصال k به گروه می‌پیوندید؟ - از نمایه کنونی استفاده کن + استفاده از پروفایل کنونی به تمام اعضای گروه متصل خواهید شد. متصل شدن - مسیر نامعتبر پرونده + مسیر نامعتبر فایل برنامه از کار افتاد در حال تلاش برای اتصال به سرور مورد استفاده برای دریافت پیام‌ها از این مخاطب (خطا: %1$s). حذف شد @@ -41,11 +41,11 @@ توسط %s حذف شد مسدود %d پیام مسدود شده - هنوز از ارسال پرونده پشتیبانی نمی‌شود + هنوز از ارسال فایل پشتیبانی نمی‌شود شما قالب پیام ناشناخته زنده - گپ نامعتبر + چت نامعتبر داده نامعتبر خطا در نمایش محتوا خطا در رمزگشایی @@ -68,7 +68,7 @@ لینک‌های SimpleX خطا در ذخیره کردن سرورهای SMP خطا در ذخیره کردن سرورهای XFTP - خطا در مذاکره مجدد رمزگذاری + خطای تجدید مذاکره رمزنگاری اتصال %1$d اتصال برقرار شد شما لینک یک بار مصرف به اشتراک گذاشتید @@ -78,39 +78,39 @@ لینک گروه SimpleX به وسیله %1$s به وسیله مرورگر - نشانی مخاطب SimpleX + آدرس مخاطب SimpleX دعوت یک بار مصرف SimpleX در حال اتصال… - باز کردن لینک در مرورگر ممکن است حریم خصوصی و امنیت اتصال را کاهش دهد. لینک‌های SimpleX ناموثق قرمز خواهند بود. + باز کردن لینک در مرورگر ممکن است حریم خصوصی و امنیت اتصال را کاهش دهد. لینک‌های SimpleX غیر قابل اعتماد به رنگ قرمز خواهند بود. خطا در بارگیری سرورهای XFTP - خطا در ایجاد نمایه! - خطا در تعویض نمایه! + خطا در ایجاد پروفایل! + خطا در تعویض پروفایل! توقف اتصال خطا در اتصال خطا در ارسال پیام خطا در ایجاد پیام خطا در بارگیری جزئیات خطا در پیوستن به گروه - فرستنده انتقال پرونده را لغو کرد. + فرستنده انتقال فایل را لغو کرد. شما از قبل به %1$s متصل هستید. خطا در اتصال (تصدیق) خطا در بارگیری سرورهای SMP - عدم موفقیت در بارگیری گپ‌ها + عدم موفقیت در بارگیری چت‌ها لطفا برنامه را به‌روزرسانی کنید و با توسعه‌دهندگان تماس بگیرید. نام نمایشی همسان! نام نمایشی نامعتبر است. لطفا نام دیگری انتخاب کنید. خطا در افزودن اعضا - امکان دریافت پرونده وجود ندارد - لطفا اتصال خود را با %1$s بررسی کنید و دوباره امتحان کنید. - خطا در دریافت پرونده + امکان دریافت فایل وجود ندارد + لطفا اتصال شبکه خود را با %1$s بررسی کنید و دوباره امتحان کنید. + خطا در دریافت فایل مخاطب از قبل وجود دارد لینک اتصال نامعتبر لطفا بررسی کنید که از لینک صحیح استفاده کردید یا از مخاطبتان بخواهید لینک دیگری برایتان بفرستد. مطمئن شوید قالب نشانی‌های سرور SMP صحیح است، در خط‌های جدا نوشته شده و تکرار نشده‌اند. خطا در به‌روزرسانی پیکربندی شبکه - عدم موفقیت در بارگیری گپ + عدم موفقیت در بارگیری چت نام نمایشی نامعتبر! - شما یک نمایه گپ با نام نمایشی یکسان دارید، لطفا نام دیگری انتخاب کنید. + شما یک پروفایل چت با نام نمایشی یکسان دارید، لطفا نام دیگری انتخاب کنید. خطا در ایجاد نشانی مطمئن شوید قالب نشانی‌های سرور XFTP صحیح است، در خط‌های جدا نوشته شده و تکرار نشده‌اند. خطا در پذیرش درخواست مخاطب @@ -122,11 +122,11 @@ سرور برای بارگذازی به اجازه نیاز دارد، گذرواژه را بررسی کنید احتمال دارد اثر انگشت گواهینامه در نشانی سرور نادرست باشد ایجاد صف - بارگذاری پرونده - بارگیری پرونده - مقایسه پرونده - حذف پرونده - خطا در حذف نمایه کاربر + بارگذاری فایل + بارگیری فایل + مقایسه فایل + حذف فایل + خطا در حذف پروفایل کاربر خطا در به‌روزرسانی حریم خصوصی کاربر خطا در حذف مخاطب خطا در حذف گروه @@ -137,9 +137,9 @@ خطا اتصال قطع اتصال - ایمن‌سازی صف + صف امن حذف صف - ایجاد پرونده + ایجاد فایل سرور برای ایجاد صف داده‌ها به اجازه نیاز دارد، گذرواژه را بررسی کنید مگر اینکه مخاطبتان اتصال را حذف کرده یا این لینک قبلا استفاده شده باشد، ممکن است این یک اشکال باشد - لطفا آن را گزارش دهید. \nبرای متصل شدن، لطفا از مخاطبتان بخواهید لینک اتصال دیگری ایجاد کند و بررسی کنید که اتصال شبکه باثباتی دارید. @@ -154,29 +154,29 @@ به وسیله تنظیمات می‌تواند غیرفعال شود – اعلان‌ها تا زمانی که برنامه در حال اجراست، همچنان نمایش داده می‌شوند.]]> جابه‌جایی پایگاه داده در حال جریان است. \nممکن است دقایقی زمان ببرد. - به SimpleX اجازه دهید در پس‌زمینه اجرا شود. در غیر این صورت، اعلان‌ها غیرفعال خواهند شد.]]> - سرویس پس‌زمنیه SimpleX دارد – سرویس، هر روز درصد معدودی از باتری را استفاده می‌کند.]]> + اجازه دهید تا اعلان‌ها را فوری دریافت کنید.]]> + SimpleX در پس‌زمینه اجرا می‌شود و به جای استفاده از پوش نوتیفیکیشن، کار می‌کند.]]> ذخیره شده ذخیره شده از %s ذخیره شده از فرستاده شده - رمزگذاری سرتاسر با محرمانگی پیشرو، مردودسازی و بازیابی ورود غیرمجاز محافظت شده‌اند.]]> + رمزنگاری انتها به انتها با محرمانگی پیشرو، مردودسازی و بازیابی ورود غیرمجاز محافظت شده‌اند.]]> بهینه‌سازی باتری فعال است، سرویس پس‌زمینه و درخواست‌های متناوب برای پیام‌های جدید خاموش می‌شوند. می‌توانید آن‌ها را از طریق تنظیمات باز فعال کنید. اعلان‌های متناوب اعلان‌های متناوب غیرفعالند! - رمزگذاری سرتاسر مقاوم در برابر کوانتوم با محرمانگی پیشرو، مردودسازی و بازیابی ورود غیرمجاز محافظت شده‌اند.]]> - این گپ به وسیله رمزگذاری سرتاسر محافظت شده است. - این گپ به وسیله رمزگذاری سرتاسر مقاوم در برابر کوانتوم محافظت شده است. + رمزنگاری انتها به انتها مقاوم در برابر کوانتوم با محرمانگی پیشرو، مردودسازی و بازیابی ورود غیرمجاز محافظت شده‌اند.]]> + این چت به وسیله رمزنگاری انتها به انتها محافظت شده است. + این چت به وسیله رمزنگاری انتها به انتها مقاوم در برابر کوانتوم محافظت شده است. غیرفعال کردن اعلان‌ها SimpleX نمی‌تواند در پس‌زمینه اجرا شود. فقط وقتی برنامه در حال اجراست اعلان‌ها را دریافت خواهید کرد. - برنامه پیام‌های جدید را به طور متناوب دریافت می‌کند - درصد معدودی از باتری در روز استفاده می‌کند. برنامه از اعلان‌های رانشی استفاده نمی‌کند - داده‌ای از دستگاه شما به سرورها فرستاده نمی‌شود. + برنامه پیام‌های جدید را به طور متناوب دریافت می‌کند - درصد کمی از باتری در روز استفاده می‌کند. برنامه از پوش نوتیفیکیشن استفاده نمی‌کند - داده‌ای از دستگاه شما به سرورها فرستاده نمی‌شود. اجازه دادن باز کردن تنظیمات برنامه - نمایش پیش‌نما + نمایش پیش‌نمایش وقتی برنامه باز است، اجرا می‌شود هر ۱۰ دقیقه به مدت ۱ دقیقه پیام‌های جدید را بررسی می‌کند فقط نمایش مخاطب - گشودن کنسول گپ + باز کردن کنسول چت پنهان کردن پیام‌های SimpleX Chat تماس‌های SimpleX Chat @@ -185,7 +185,7 @@ پنهان پنهان کردن مخاطب و پیام مخاطب پنهان: - گشودن نمایه‌های گپ + تغییر پروفایل‌های چت سرویس پس‌زمینه همیشه در حال اجراست - اعلان‌ها به محض موجود شدن پیام‌ها به نمایش درمی‌آیند. قفل SimpleX پایان تماس @@ -198,8 +198,8 @@ پیام جدید نمایش مخاطب و پیام درخواست مخاطب جدید - توقف گپ - گشودن صفحه جابه‌جایی + توقف چت + بازکردن صفحه جابه‌جایی می‌توانید قفل SimpleX را از طریق تنظیمات روشن کنید. خطا در نمایش اعلان، با توسعه‌دهندگان تماس بگیرید. قفل SimpleX فعال نیست! @@ -207,18 +207,18 @@ ارسال شده نماد زمینه لغو پیش‌نمایش تصویر - لغو پیش‌نمایش پرونده + لغو پیش‌نمایش فایل مصرف باتری برنامه / نامحدود را در تنظیمات برنامه انتخاب کنید.]]> برنامه ممکن است بعد از ۱ دقیقه در پس‌زمینه بسته شود. مقداردهی اولیه پایگاه داده ممکن نیست عبارت عبور الزامی است برای دریافت اعلان‌ها، لطفا، عبارت عبور پایگاه داده را وارد کنید - پایگاه داده به درستی کار نمی‌کند. برای آگاهی بیشتر لمس کنید + پایگاه داده به درستی کار نمی‌کند. برای آگاهی بیشتر ضربه بزنید سرویس SimpleX Chat در حال دریافت پیام‌ها… روشن کردن عدم موفقیت تصدیق - گشودن قفل + باز کردن قفل اطلاعات ورودتان را تایید کنید پاسخ تاریخچه @@ -227,11 +227,11 @@ بسط دادن پیام برای تمام اعضا به عنوان حذف شده علامت‌گذاری خواهد شد. توقف - دریافت پرونده متوقف خواهد شد. + دریافت فایل متوقف خواهد شد. خوش آمدید! خطا در کدبرداری ارسال پیام مستقیم برای اتصال - لطفا، تا زمانی که پرونده در حال بارگیری از موبایل متصل است، منتظر باشید. + لطفا تا زمانی که فایل در حال بارگیری از موبایل متصل است، منتظر باشید. حذف مخاطب مشاهده کد امنیتی تایید کد امنیتی @@ -246,10 +246,10 @@ کپی پیام به عنوان حذف شده علامت‌گذاری خواهد شد. گیرنده‌ها قادر خواهند بود این پیام را آشکار کنند. ارسال غیرموفق - برای اتصال لمس کنید + برای اتصال ضربه بزنید امکان کدبرداری ویدئو وجود ندارد. لطفا، ویدئوی دیگری را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. تصویر - پرونده پیدا نشد + فایل پیدا نشد لمس دکمه ایجاد گروه: برای ایجاد یک گروه جدید.]]> بدون تماس‌های پس‌زمینه @@ -257,23 +257,23 @@ تصدیق ویرایش بدون اطلاعات تحویل - لغو پرونده + ابطال فایل در حال اتصال… - شما هیچ گپی ندارید - بارگذاری گپ‌ها… + شما هیچ چتی ندارید + بارگذاری چت‌ها… ویدئو وقتی دریافت خواهد شد که مخاطبتان بارگذاری آن را تکمیل کند. - بیشترین اندازه پشتیبانی شده فعلی پرونده %1$s است. - خطا در ذخیره‌سازی پرونده - در حال بارگیری پرونده + بیشترین اندازه پشتیبانی شده فعلی فایل %1$s است. + خطا در ذخیره‌سازی فایل + در حال بارگیری فایل حذف و مخاطب را باخبر کن متصل - مذاکره مجدد رمزگذاری؟ + مذاکره مجدد رمزنگاری؟ شما نیاز دارید به مخاطبتان اجازه ارسال پیام‌های صوتی دهید تا بتوانید آن‌ها را ارسال کنید. ارسال پیام زنده پیام ناپدید شونده زمان سفارشی به حافظه کپی شد - شروع گپ جدید + شروع چت جدید از گالری تماس تصویری برای محافظت از اطلاعاتتان، قفل SimpleX را روشن کنید. @@ -299,48 +299,48 @@ خوانده نشده خوش آمدید، %1$s! این متن در تنظیمات دردسترس است - گپ با توسعه‌دهندگان - گپ پالایش شده‌ای نیست + چت با توسعه‌دهندگان + چت فیلتر شده‌ای نیست به %1$s متصل شوید؟ - گپی انتخاب نشده + چتی انتخاب نشده فرستادن پیام… تعداد ویدئوی بیش از اندازه! فقط ۱۰ ویدئو در هر زمان می‌توان ارسال کرد - شما نمی‌توانید پیامی بفرستید! + شما ناظر هستید لطفا با مدیر گروه تماس بگیرید. - فقط صاحبان گروه می‌توانند پرونده‌ها و رسانه را فعال کنند. + فقط صاحبان گروه می‌توانند فایل‌ها و رسانه را فعال کنند. دریافت تصویر درخواست شده در انتظار تصویر تصویر در گالری ذخیره شد ویدئو ویدئو وقتی دریافت خواهد شد که مخاطبتان آنلاین شود، لطفا صبر کنید یا بعدا بررسی کنید. - پرونده - پرونده حجیم! - در انتظار پرونده - پرونده وقتی دریافت خواهد شد که مخاطبتان بارگذاری آن را تکمیل کند. + فایل + فایل حجیم! + در انتظار فایل + فایل وقتی دریافت خواهد شد که مخاطبتان بارگذاری آن را تکمیل کند. پیام صوتی (%1$s) اعلان‌ها مخاطب و تمام پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! پیام‌های صوتی مجازند؟ پیام‌های صوتی ممنوع هستند! اسکن کد QR.]]> - اشتراک‌گذاری پرونده… + اشتراک‌گذاری فایل… تعداد تصویر بیش از اندازه! - برای اسکن لمس کنید - انتخاب پرونده - برای شروع گپ جدید + برای اسکن ضربه بزنید + انتخاب فایل + برای شروع چت جدید تحویل پیام عضو حذف شود؟ برای همه - ارسال پرونده متوقف خواهد شد. - دریافت پرونده متوقف شود؟ - پرونده از سرورها حذف خواهد شد. - ارسال پیام مستقیم + ارسال فایل متوقف خواهد شد. + دریافت فایل متوقف شود؟ + فایل از سرورها حذف خواهد شد. + برای اتصال ارسال کنید اشتراک‌گذاری رسانه… فقط ۱۰ تصویر در هر زمان می‌توان ارسال کرد تماس صوتی - لطفا آن را به خاطر داشته باشید یا به طور امن ذخیره کنید - راهی برای بازیابی کلمه عبور وجود ندارد! - قفل SimpleX روشن است + لطفا آن را به خاطر داشته باشید یا به طور امن ذخیره کنید - راهی برای بازیابی رمز عبور گم‌شده وجود ندارد! + قفل SimpleX روشن شد وقتی برنامه را شروع می‌کنید یا بعد از ۳۰ ثانیه در پس‌زمینه آن از سر می‌گیرید، نیاز به تصدیق خواهید داشت. جستجو غیرفعال کردن قفل SimpleX @@ -362,16 +362,16 @@ بدون تاریخچه حذف پیام حذف شود؟ - پرونده لغو شود؟ - توقف پرونده - ارسال پرونده متوقف شود؟ - لغو + فایل ابطال شود؟ + توقف فایل + ارسال فایل متوقف شود؟ + ابطال در حال اتصال… - گپ‌ها + چت‌ها شما به گروه دعوت شده‌اید پیوستن به عنوان %s جستجو یا الصاق لینک SimpleX - برای شروع گپ جدید لمس کنید + برای شروع چت جدید ضربه بزنید اشتراک‌گذاری پیام… در انتظار تصویر پیام صوتی… @@ -385,17 +385,17 @@ تایید بازنشاندن اسکن کد QR - (اسکن یا الصاق از حافظه) + (اسکن یا الصاق از کلیپ بورد) (تنها ذخیره شده توسط اعضای گروه) فعال کردن دسترسی دوربین اجازه داده نشد! دوربین سپاس برای نصب SimpleX Chat! - گشودن در موبایل را لمس کنید، سپس اتصال را در برنامه لمس کنید.]]> + باز کردن در موبایل را لمس کنید، سپس اتصال را در برنامه لمس کنید.]]> درخواست اتصال پذیرفته شود؟ پیام‌های صوتی مجاز نیستند لینک‌های SimpleX مجاز نیستند - پرونده‌ها و رسانه مجاز نیست + فایل و رسانه مجاز نیست در انتظار ویدئو دریافت ویدئو درخواست شده ویدئو ارسال شد @@ -412,22 +412,22 @@ افزودن مخاطب: برای ایجاد لینک دعوت جدید، یا اتصال از طریق لینکی که دریافت کردید.]]> اتصال از طریق لینک تصویر وقتی دریافت خواهد شد که مخاطبتان آنلاین شود، لطفا صبر کنید یا بعدا بررسی کنید! - نوع قفل SimpleX - تصدیق سیستم + حالت قفل SimpleX + احراز هویت سیستم تغییر کد عبور در پاسخ به فوری تصدیق دستگاه فعال نیست. زمانی که تصدیق دستگاه را فعال کنید می‌توانید قفل SimpleX را از طریق تنظیمات روشن کنید. اجازه دادن ضمیمه - امکان کدبرداری تصویر وجود ندارد. لطفا، تصویر دیگری را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. - پرونده‌ها و رسانه ممنوع است! + تصویر قابل رمزگشایی نیست. لطفاً یک تصویر دیگر را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. + فایل و رسانه ممنوع است! تصویر ارسال شد در انتظار ویدئو - مخاطبتان پرونده‌ای ارسال کرد که از بیشترین اندازه پشتیبانی شده (%1$s) بزرگتر است. - پرونده وقتی دریافت خواهد شد که مخاطبتان آنلاین شود، لطفا صبر کنید یا بعدا بررسی کنید. - پرونده ذخیره شد - رمزگذاری در حال کار است و نیازی به توافق رمزگذاری جدید نیست. ممکن است باعث بروز خطاهای اتصال شود! + مخاطبتان فایلی ارسال کرد که از بیشترین اندازه پشتیبانی شده (%1$s) بزرگتر است. + فایل وقتی دریافت خواهد شد که مخاطبتان آنلاین شود، لطفا صبر کنید یا بعدا بررسی کنید. + فایل ذخیره شد + رمزنگاری در حال کار است و توافق جدید رمزنگاری لازم نیست. این ممکن است منجر به خطاهای اتصال شود! لطفا از مخاطبتان بخواهید ارسال پیام‌های صوتی را فعال کند. ارسال ویدئو @@ -436,9 +436,9 @@ خطا آدرس دریافت به سرور متفاوتی تغییر خواهد یافت. تغییر نشانی وقتی تکمیل خواهد شد که فرستنده آنلاین شود. تایید شما ممکن نیست؛ لطفا دوباره امتحان کنید. - پرونده + فایل شما ناظر هستید - گپ پاک شود؟ + چت پاک شود؟ تمام پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! حذف پذیرش ناشناس @@ -454,47 +454,47 @@ مخاطبی که این لینک را با او به اشتراک گذاشتید قادر به اتصال نخواهد بود! اگر نپذیرید، فرستنده باخبر نخواهد شد. تمام پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! پیام‌ها فقط برای شما حذف خواهند شد. - پاک‌سازی گپ + پاک‌سازی چت اتصال را پذیرفتید مخاطب هنوز متصل نشده است! شما مخاطبی را دعوت کردید اتصال در حال انتظار حذف شود؟ - اتصالی که پذیرفتید لغو خواهد شد! + اتصالی که شما قبول کردید، لغو خواهد شد! بی‌صدا لغو بی‌صدا علامت‌گذاری به عنوان خوانده شده خلع برگزیده تیم SimpleX - گپ جدید + چت جدید روش استفاده راهنمای مارکداون لینک نامعتبر! اشتراک‌گذاری لینک یک بار مصرف یا کد QR را اسکن کنید تلاش مجدد - عبارت عبور و صدور پایگاه داده + عبارت عبور و اکسپورت پایگاه داده افزودن سرورهای از پیش تنظیم شده افزودن به دستگاه دیگر حذف سرور چگونه از سرورهای خود استفاده کنید تصویر پیش‌نمایش لینک - نمایه‌های گپ شما + پروفایل‌های چت شما سرورهای SMP SimpleX Chat را برای ترمینال نصب کنید - در GitHub ستاره بزنید + در GitHub ستاره بدهید همکاری کنید کد QR - یک نمایه تصادفی جدید به اشتراک گذاشته خواهد شد. + یک پروفایل تصادفی جدید به اشتراک گذاشته خواهد شد. اتصال از طریق لینک لینک دعوت یک‌بارمصرف %s تایید شده است - ایجاد نمایه گپ - کنسول گپ - آزمایش سرور + ایجاد پروفایل چت + کنسول چت + سرور آزمایشی وارد کردن دستی سرور سرورهای ICE (یکی در هر خط) خطا در ذخیره کردن سرورهای ICE - جایگزین تصویر نمایه + جایگزین تصویر پروفایل دکمه بستن لغو پیش‌نمایش لینک تنظیمات @@ -505,7 +505,7 @@ درخواست اتصال ارسال شد! وقتی دستگاه میزبان گروه آنلاین شد، به گروه متصل خواهید شد، لطفا صبر کنید یا بعدا بررسی کنید! لینکی که دریافت کردید را الصاق کنید تا به مخاطبتان متصل شوید… - نمایه شما %1$s به اشتراک گذاشته خواهد شد. + پروفایل شما %1$s به اشتراک گذاشته خواهد شد. برای اتصال، مخاطبتان می‌تواند کد QR را اسکن یا از لینک در برنامه استفاده کند. اگر نمی‌توانید ملاقات حضوری داشته باشید، کد QR را در یک تماس تصویری نمایش دهید، یا لینک را به اشتراک بگذارید. می‌توانید نشانی خود را به صورت لینک یا کد QR به اشتراک بگذارید - هر کسی می‌تواند به شما متصل شود. @@ -525,7 +525,7 @@ کد امنیتی را از برنامه مخاطبتان اسکن کنید. علامت‌گذاری به عنوان تایید شده %s تایید نشده است - برای تایید رمزگذاری سرتاسر، روی دستگاه‌های خود، کد را با مخاطبتان مقایسه(یا اسکن) کنید. + برای تایید رمزنگاری انتها به انتها، روی دستگاه‌های خود، کد را با مخاطبتان مقایسه(یا اسکن) کنید. سرورهای XFTP شما روش استفاده در حال استفاده از سرورهای SimpleX Chat. @@ -540,10 +540,10 @@ به ما ایمیل بفرستید قفل SimpleX افزودن سرور - آزمایش سرورها + سرورهای آزمایشی ذخیره سرورها عدم موفقیت آزمایش سرور! - عدم موفقیت آزمایش چند سرور: + برخی از سرورها در تست ناموفق بودند: اسکن کد QR سرور سرورهای SMP شما سرورهای XFTP @@ -552,14 +552,14 @@ سرورهای ICE شما اتصال الصاق - گشودن در برنامه موبایل کلیک کنید.]]> + باز کردن در برنامه موبایل کلیک کنید.]]> افزودن مخاطب نشانی‌ SimpleX پاک‌سازی تایید نشانی‌ سرور از پیش تنظیم شده کد امنیتی به‌کارگیری از سرور - سرورها برای اتصال‌های جدید نمایه گپ فعلی شما + سرورها برای اتصال‌های جدید پروفایل چت فعلی شما سرورها ذخیره شوند؟ به برنامه امتیاز بدهید نشانی‌ سرور شما @@ -572,18 +572,17 @@ این رشته متن، یک لینک اتصال نیست! کدی که اسکن کردید یک کد QR لینک SimpleX نیست. اسکن کد - تصویر نمایه + تصویر پروفایل بیشتر نمایش کد QR کد QR نامعتبر وقتی درخواست اتصال شما پذیرفته شد، متصل خواهید شد، لطفا صبر کنید یا بعدا بررسی کنید! کد QR را در تماس تصویری نمایش دهید، یا لینک را به اشتراک بگذارید.]]> - نمایه گپ شما ارسال خواهد شد -\nبه مخاطبتان + پروفایل چت شما ارسال خواهد شد \nبه مخاطبتان اطلاعات بیشتر اگر بعدا نشانی‌ خود را حذف کنید، مخاطبان خود را از دست نخواهید داد. این لینک دعوت یک‌بارمصرف را به اشتراک بگذارید - برای الصاق لینک لمس کنید + برای الصاق لینک ضربه بزنید تنظیمات شما میزبان‌های Onion برای اتصال الزامی خواهد بود. \nلطفا توجه داشته باشید: شما بدون نشانی‌ onion. قادر نخواهید بود به سرورها متصل شوید. @@ -603,9 +602,9 @@ مخاطبانتان متصل باقی خواهند ماند. اشتراک‌گذاری لینک دسترسی به سرورها از طریق پروکسی SOCKS روی پورت %d؟ پروکسی باید قبل از فعال کردن این گزینه، راه اندازی شده باشد. - برای هر نمایه گپی که در برنامه دارید استفاده خواهد شد.]]> + برای هر پروفایل چتی که در برنامه دارید استفاده خواهد شد.]]> نمایش خطاهای داخلی - نمایش تماس‌های کند API + نمایش API call های کند خیر استفاده از میزبان‌های onion. را روی «خیر» تنظیم کنید اگر پروکسی SOCKS از آنها پشتیبانی نمی‌کند.]]> سفارشی کردن تم @@ -623,17 +622,17 @@ وقتی موجود بود از میزبان‌های Onion استفاده نخواهد شد. انزوای ترابری - نمایه گپ + پروفایل چت اتصال ساختار برنامه: %s - تنظیمات شبکه + تنظیمات پیشرفته نمایش: - لطفا توجه داشته باشید: واسطه‌های پیام و پرونده از طریق پروکسی SOCKS متصل می‌شوند. تماس‌ها و ارسال پیش‌نمایش‌های لینک از اتصال مستقیم استفاده می‌کنند.]]> + لطفا توجه داشته باشید: واسطه‌های پیام و فایل از طریق پروکسی SOCKS متصل می‌شوند. تماس‌ها و ارسال پیش‌نمایش‌های لینک از اتصال مستقیم استفاده می‌کنند.]]> گزینه‌های توسعه‌دهنده پنهان کردن: بسته شود؟ تمام مخاطبانتان متصل باقی خواهند ماند. - تمام مخاطبانتان متصل باقی خواهند ماند. به‌روزرسانی نمایه به مخاطبانتان ارسال خواهد شد. + تمام مخاطبانتان متصل باقی خواهند ماند. به‌روزرسانی پروفایل به مخاطبانتان ارسال خواهد شد. اگر تایید کنید، سرورهای پیام‌رسانی خواهند توانست نشانی‌ IP، و فراهم‌کننده شما را ببینند - و این که به چه سرورهایی متصل می‌شوید. مطمئن شوید قالب نشانی‌های سرور WebRTC ICE صحیح است، در خط‌های جدا نوشته شده و تکرار نشده‌اند. برای هر مخاطب و عضو گروه استفاده خواهد شد. \nلطفا توجه داشته باشید: اگر اتصال‌های زیادی داشته باشید، مصرف باتری و ترافیک شما می‌تواند به شکل قابل توجه بالاتر باشد و بعضی اتصال‌ها ممکن است با موفقیت انجام نشوند.]]> @@ -641,41 +640,41 @@ ویرایش تصویر ایجاد نشانی‌ SimpleX اشتراک‌گذاری با مخاطبان - به‌روزرسانی نمایه به مخاطبانتان ارسال خواهد شد. + به‌روزرسانی پروفایل به مخاطبانتان ارسال خواهد شد. پذیرفتن خودکار ورود پیام خوشامدگویی…(اختیاری) تنظیمات ذخیره شوند؟ - ذخیره تنظیمات پذیرفتن خودکار + ذخیره تنظیمات آدرس SimpleX حذف نشانی سلام! \nبه وسیله SimpleX Chat به من متصل شوید: %s نشانی ایجاد نشود ادامه - نمایه فعلی شما + پروفایل فعلی شما نام کامل: - ذخیره و مخاطبان مطلع شوند - بیایید در SimpleX Chat گفتگو کنیم + ذخیره کردن و اطلاع به مخاطبان + بیایید در SimpleX Chat چت کنیم تنظیمات ذخیره شوند؟ - پنهان کردن نمایه + پنهان کردن پروفایل کلمه عبور برای نمایش - ذخیره کلمه عبور نمایه + ذخیره کلمه عبور پروفایل اشتراک‌گذاری نشانی متوقف شود؟ توقف اشتراک‌گذاری می‌توانید بعدا آن را ایجاد کنید می‌توانید آن را از طریق تنظیمات برای مخاطبان SimpleX خود قابل رویت کنید. - نام نمایه: + نام پروفایل: حذف تصویر - ذخیره و اعضای گروه مطلع شوند - ذخیره و مخاطب مطلع شود + ذخیره کردن و اطلاع به اعضای گروه + ذخیره کردن و اطلاع به مخاطب خروج بدون ذخیره کردن - برای آشکار کردن نمایه پنهان خود، یک کلمه عبور کامل در فیلد جستجو در صفحه نمایه‌های گپتان وارد کنید. + برای آشکار کردن پروفایل پنهان خود، یک کلمه عبور کامل در فیلد جستجو در صفحه پروفایل‌های چت خود وارد کنید. دعوت از دوستان تایید کلمه عبور - کلمه عبور نمایه پنهان - به نمایه خود نشانی اضافه کنید، تا مخاطبانتان بتوانند آن را با اشخاص دیگر به اشتراک بگذارند. به‌روزرسانی نمایه به مخاطبانتان ارسال خواهد شد. + کلمه عبور پروفایل پنهان + به پروفایل خود نشانی اضافه کنید، تا مخاطبانتان بتوانند آن را با اشخاص دیگر به اشتراک بگذارند. به‌روزرسانی پروفایل به مخاطبانتان ارسال خواهد شد. نشانی با مخاطبان به اشتراک گذاشته شود؟ یک نشانی ایجاد کنید تا اشخاص بتوانند به شما متصل شوند. - نمایه شما روی دستگاهتان ذخیره شده و فقط با مخاطبانتان به اشتراک گذاشته می‌شود. سرورهای SimpleX قادر به دیدن نمایه شما نیستند. + پروفایل شما روی دستگاهتان ذخیره شده و فقط با مخاطبانتان به اشتراک گذاشته می‌شود. سرورهای SimpleX قادر به دیدن پروفایل شما نیستند. خطا در ذخیره کردن کلمه عبور کاربر تماس‌های صوتی و تصویری تماس‌های شما @@ -692,7 +691,7 @@ چرخش دوربین پیش‌نویس پیام وقتی برنامه در حال اجراست - تماس صوتی رمزگذاری سرتاسر شده + تماس صوتی با رمزنگاری انتها به انتها پذیرفتن حریم خصوصی شما حالت قفل @@ -704,42 +703,42 @@ فعال کردن (نگه‌داشتن مقدارهای جایگزین شده گروه) غیرفعال برای همه گروه‌ها کمک - گپ‌ها - اجرای گپ - گپ در حال اجراست + چت‌ها + اجرای چت + چت در حال اجراست توقف - حذف تمام پرونده‌ها + حذف تمام فایل‌ها اصلاح نام به %s؟ - بعدا از طریق تنظیمات قابل تغییر است. - تماس پذیرفته نشده + چگونه بر باتری تأثیر می‌گذارد + تماس رد شده هش پیام ناصحیح هش پیام قبلی متفاوت است. شناسه پیام ناصحیح رسیدها فعال شوند؟ شما - پیام‌ها و پرونده‌ها + پیام‌ها و فایل‌ها تماس‌ها حالت ناشناس عبارت عبور پایگاه داده - صدور پایگاه داده - نمایه گپ حذف شود؟ + اکسپورت پایگاه داده + پروفایل چت حذف شود؟ نام خود را وارد کنید: ایجاد روش استفاده از مارکداون می‌توانید از مارکداون برای آرایش پیام‌ها استفاده کنید: - تماس بی‌پاسخ + تماس رد شده تماس پذیرفته نامتمرکز - نمایه خود را ایجاد کنید + پروفایل خود را ایجاد کنید SimpleX چگونه کار می‌کند مخزن GitHub ما.]]> - استفاده از گپ + استفاده از چت بهترین گزینه برای باتری. شما اعلان‌ها را فقط وقتی دریافت می‌کنید که برنامه در حال اجراست (بدون سرویس پس‌زمینه).]]> تماس‌ها روی صفحه قفل: پذیرفتن سرور واسط از نشانی IP شما محافظت می‌کند، اما سرور می‌تواند مدت تماس را مشاهده کند. - گشودن - رمزگذاری سرتاسر شده + باز کردن + رمزنگاری شده به صورت انتها به انتها قطع تماس ویدئو خاموش ویدئو روشن @@ -759,7 +758,7 @@ نام نمایشی جدید: اگر کد عبور خودتخریبی خود را زمان باز کردن برنامه وارد کنید: تمام اطلاعات برنامه حذف می‌شود. - این تنظیمات برای نمایه فعلی شما هستند + این تنظیمات برای پروفایل فعلی شما هستند ارسال رسید برای %d مخاطب فعال است غیرفعال برای همه فعال برای همه گروه‌ها @@ -767,13 +766,13 @@ توقف برنامه تم‌ها حذف پایگاه داده - پایگاه داده گپ وارد شد - %d پرونده با اندازه کل %s - رمزگذاری سرتاسر دو لایه را ذخیره می‌کنند.]]> + پایگاه داده چت ایمپورت شد + %d فایل با اندازه کل %s + فقط دستگاه‌های کلاینت پروفایل‌های کاربری، مخاطبان، گروه‌ها و پیام‌ها را ذخیره می‌کنند. نادیده گرفتن بلوتوث وارد کردن پایگاه داده - اشخاص فقط از طریق لینک‌هایی که به اشتراک می‌گذارید می‌توانند به شما متصل شوند. + شما تصمیم می‌گیرید که چه کسی می‌تواند متصل شود. تماس از پیش پایان یافته! هش پیام ناصحیح پذیرفتن خودکار تصاویر @@ -788,29 +787,29 @@ استفاده از کامپیوتر آرشیو پایگاه داده جدید آرشیو پایگاه داده قدیمی - خطا در شروع گپ - ایمن در برابر اسپم و سو استفاده + خطا در شروع چت + مصونیت در برابر هرزنامه چگونه کار می‌کند تماس تصویری گوشی بلندگو هدفون‌ها - نسل بعدی پیام‌رسانی خصوصی + آینده پیام‌رسانی پایان یافت خطا در باز کردن مرورگر - جهت صدور عبارت عبور تعیین کنید + عبارت عبور برای اکسپورت را تعیین کنید محافظت از صفحه برنامه شروع مجدد ویژگی‌های آزمایشی باز کردن پوشه پایگاه داده - گپ متوقف شده است - خطا در حذف پایگاه داده گپ - خطا در وارد کردن پایگاه داده گپ - شما گپ خود را کنترل می‌کنید! - نمایه، مخاطبان و پیام‌های تحویل داده شده شما روی دستگاهتان ذخیره می‌شوند. - نمایه فقط با مخاطبانتان به اشتراک گذاشته می‌شود. + چت متوقف شده است + خطا در حذف پایگاه داده چت + خطا در ایمپورت کردن پایگاه داده چت + شما چت خود را کنترل می‌کنید! + پروفایل، مخاطبان و پیام‌های تحویل داده شده شما روی دستگاهتان ذخیره می‌شوند. + پروفایل فقط با مخاطبانتان به اشتراک گذاشته می‌شود. نام نمایشی نمی‌تواند شامل نویسه‌های فاصله باشد. - ایجاد نمایه + ایجاد پروفایل نام نامعتبر! برجسته در انتظار تایید… @@ -837,24 +836,24 @@ آن‌ها در تنظیمات مخاطب و گروه قابل جایگزینی هستند. گروه‌های کوچک (حداکثر ۲۰) تنظیمات - اولین بن‌سازه بدون هیچ شناسه کاربری - با طرح‌ریزی خصوصی + هیچ شناسه کاربری وجود ندارد اعلان‌های خصوصی عبارت عبور تصادفی در تنظیمات به صورت متن آشکار ذخیره می‌شود. \nمی‌توانید بعدا آن را تغییر دهید. تماس تصویری رسیده تماس صوتی رسیده - تماس تصویری رمزگذاری سرتاسر شده - تماس صوتی (رمزگذاری سرتاسر نشده) - بدون رمزگذاری سرتاسر - مخاطب رمزگذاری سرتاسر دارد + تماس تصویری با رمزنگاری انتها به انتها + تماس صوتی (بدون رمزنگاری انتها به انتها) + بدون رمزنگاری انتها به انتها + مخاطب رمزنگاری انتها به انتها دارد این اتفاق وقتی می‌افتد که شما یا اتصالتان از پشتیبان پایگاه داده قدیمی استفاده کرده باشید. - مذاکره مجدد رمزگذاری ناموفق بود. - رمزگذاری پرونده‌های محلی + تجدید مذاکره رمزنگاری ناموفق بود. + رمزنگاری فایل‌های محلی پشتیبان‌گیری اطلاعات برنامه قفل بعد از تایید کد عبور کد عبور تغییر نکرد! - ایجاد نمایه + ایجاد پروفایل مورب ما هیچکدام از مخاطبان و پیام‌های(وقتی تحویل داده شدند) شما را روی سرورها ذخیره نمی‌کنیم. رنگی @@ -867,7 +866,7 @@ در حال راه‌اندازی… متصل در حال برقراری تماس… - تماس تصویری (رمزگذاری سرتاسر نشده) + تماس تصویری (بدون رمزنگاری انتها به انتها) رد کردن تعیین کد عبور رسیدها برای گروه‌ها غیرفعال شوند؟ @@ -879,45 +878,45 @@ آزمایشی اتصال شبکه آیکون برنامه - پایگاه داده گپ + پایگاه داده چت بن‌سازه پیام‌رسانی و کاربردی که از حریم خصوصی و امنیت شما محافظت می‌کند. گزینه خوب برای باتری. سرویس پس‌زمینه هر ۱۰ دقیقه پیام‌ها را بررسی می‌کند. ممکن است تماس‌ها یا پیام‌های ضروری را از دست دهید.]]> - پیام‌ها از قلم افتادند + پیام‌های نادیده گرفته شده مرورگر وب پیش‌فرض برای تماس‌ها لازم است. لطفا مرورگر پیش‌فرض را در سیستم تنظیم کنید، و اطلاعات بیشتر را با توسعه‌دهندگان به اشتراک بگذارید. سرور واسط فقط در زمان نیاز مورد استفاده قرار می‌گیرد. طرف دیگری قادر به مشاهده نشانی IP شما خواهد بود. نمایش غیرفعال - گشودن SimpleX Chat برای پذیرفتن تماس + باز کردن SimpleX Chat برای پذیرفتن تماس تماس‌ها از صفحه قفل را از طریق تنظیمات فعال کنید. - مخاطب رمزگذاری سرتاسر ندارد + مخاطب رمزنگاری انتها به انتها ندارد سرورهای WebRTC ICE شناسه پیام بعدی نادرست است (کمتر یا برابر است با قبلی). \nبروز این اتفاق می‌تواند به دلیل وجود اشکال نرم‌افزاری یا مورد حمله قرار گرفتن اتصال باشد. لطفا آن را به توسعه‌دهندگان گزارش دهید. پیام همسان - گپ متوقف شود؟ - پایگاه داده با استفاده از یک عبارت عبور تصادفی رمزگذاری شده، لطفا پیش از صدور آن را تغییر دهید. - خطا در متوقف کردن گپ - خطا در صدور پایگاه داده گپ - پایگاه داده گپ وارد شود؟ + چت متوقف شود؟ + پایگاه داده با استفاده از یک عبارت عبور تصادفی رمزنگاری شده، لطفا پیش از اکسپورت آن را تغییر دهید. + خطا در متوقف کردن چت + خطا در اکسپورت پایگاه داده چت + پایگاه داده چت ایمپورت شود؟ وارد کردن - به منظور استفاده از پایگاه داده گپ وارد شده، برنامه را شروع مجدد کنید. - چند خطای غیر مهلک هنگام وارد کردن رخ داد - برای اطلاعات بیشتر می‌توانید کنسول گپ را ببینید. - پایگاه داده گپ حذف شد - به منظور ایجاد نمایه گپ جدید، برنامه را شروع مجدد کنید. - پرونده‌ها و رسانه - حذف پرونده‌ها برای تمام نمایه‌های گپ - پرونده‌ها و رسانه حذف شوند؟ - هیچ پرونده دریافتی یا ارسالی وجود ندارد + به منظور استفاده از پایگاه داده چت ایمپورت شده، برنامه را شروع مجدد کنید. + چند خطای غیر مهلک هنگام ایمپورت رخ داد: + پایگاه داده چت حذف شد + به منظور ایجاد پروفایل چت جدید، برنامه را شروع مجدد کنید. + فایل‌ها و رسانه + حذف فایل‌ها برای تمام پروفایل‌های چت + فایل‌ها و رسانه حذف شوند؟ + هیچ فایل دریافتی یا ارسالی وجود ندارد هرگز %s ثانیه اجرای اتصال خصوصی جابه‌جایی از دستگاهی دیگر - یک نمایه گپ خالی با نام فراهم شده ایجاد می‌شود، و برنامه به طور معمول باز می‌شود. + یک پروفایل چت خالی با نام فراهم شده ایجاد می‌شود، و برنامه به طور معمول باز می‌شود. اعطای اجازه‌ها در تنظیمات این مجوز را در تنظیمات اندروید پیدا و به صورت دستی آن را اعطا کنید. باز کردن تنظیمات - پروتکل و کد متن‌باز - هر کسی می‌تواند سرورها را راه‌اندازی کند. + هر کسی می‌تواند سرویس‌دهنده میزبانی کند. رسیدها غیرفعال شوند؟ فعال کردن (نگه‌داشتن مقدارهای جایگزین شده) اعطای اجازه‌ها @@ -925,55 +924,54 @@ دوربین دوربین و میکروفون اعطای اجازه‌ها برای برقراری تماس‌ها - حریم خصوصی باز تعریف شده - برای حفاظت از حریم خصوصی، به جای شناسه‌های کاربری مورد استفاده در بن‌سازه‌های دیگر، SimpleX شناسه‌هایی برای صفوف پیام دارد، جدا برای هر کدام از مخاطبان شما. + تعریف مجدد حریم خصوصی + برای حفظ حریم خصوصی شما، SimpleX از شناسه‌های جداگانه برای هر یک از مخاطبان شما استفاده می‌کند. از باتری بیشتر استفاده می‌کند! سرویس پس‌زمینه همیشه در حال اجراست - اعلان‌ها به محض موجود شدن، نمایش داده می‌شوند.]]> وقتی می‌تواند اتفاق بیفتد که: \n۱. پیام‌ها در کلاینت فرستنده بعد از ۲ روز یا روی سرور بعد از ۳۰ روز منقضی شده باشند. \n۲. رمزگشایی پیام ناموفق بود، چون شما یا مخاطبتان از پشتیبان پایگاه داده قدیمی استفاده استفاده کردید. \n۳. اتصال مورد حمله قرار گرفته باشد. - تصاویر نمایه - به منظور صدور، ورود و حذف پایگاه داده گپ، گپ را متوقف کنید. هنگامی که گپ متوقف شده است، شما قادر به دریافت و ارسال پیام نخواهید بود. - پایگاه داده گپ فعلی شما حذف و توسط پایگاه داده وارد شده جایگزین خواهد شد. -\nاین عمل قابل برگشت نیست - نمایه، مخاطبان، پیام‌ها و پرونده‌های شما به صورت غیر قابل بازگشت از بین خواهند رفت. - پایگاه داده گپ شما - این عمل قابل برگشت نیست - نمایه، مخاطبان، پیام‌ها و پرونده‌های شما به صورت غیر قابل بازگشت از بین خواهند رفت. + تصاویر پروفایل + چت را متوقف کنید تا بتوانید پایگاه داده چت را اکسپورت، ایمپورت یا حذف کنید. در حالی که چت متوقف است، نمی‌توانید پیام‌ها را ارسال یا دریافت کنید. + پایگاه داده چت فعلی شما حذف و توسط پایگاه داده ایمپورت شده جایگزین خواهد شد. \nاین عمل قابل برگشت نیست - پروفایل، مخاطبان، پیام‌ها و فایل‌های شما به صورت غیر قابل بازگشت از بین خواهند رفت. + پایگاه داده چت شما + این عمل قابل برگشت نیست - پروفایل، مخاطبان، پیام‌ها و فایل‌های شما به صورت غیر قابل بازگشت از بین خواهند رفت. ارسال رسید برای %d مخاطب غیرفعال است - این عمل قابل برگشت نیست - تمام پرونده‌ها و رسانه دریافتی حذف خواهند شد. عکس‌های با کیفیت پایین باقی خواهند ماند. - شما باید از تازه‌ترین نسخه پایگاه داده گپ خود روی فقط یک دستگاه استفاده کنید، در غیر این صورت ممکن است از بعضی از مخاطب‌ها ‌دیگر پیامی دریافت نکنید. + این عمل قابل برگشت نیست - تمام فایل‌ها و رسانه دریافتی حذف خواهند شد. عکس‌های با کیفیت پایین باقی خواهند ماند. + شما باید از تازه‌ترین نسخه پایگاه داده چت خود روی فقط یک دستگاه استفاده کنید، در غیر این صورت ممکن است از بعضی از مخاطب‌ها ‌دیگر پیامی دریافت نکنید. پیام‌ها این عمل قابل برگشت نیست - پیام‌های ارسالی و دریافتی قدیمی‌تر از زمان انتخابی حذف خواهند شد. این کار ممکن است چندین دقیقه زمان ببرد. خطا در تغییر تنظیمات ذخیره عبارت عبور در تنظیمات حذف پیام‌ها ذخیره عبارت عبور در مخزن کلید - این تنظیمات بر پیام‌های موجود در نمایه گپ فعلی شما اعمال می‌شود + این تنظیمات بر پیام‌های موجود در پروفایل چت فعلی شما اعمال می‌شود حذف خودکار پیام فعال شود؟ برگرداندن - ارتقا و گشودن گپ + ارتقا و باز کردن چت دعوت به گروه %1$s به گروه می‌پیوندید؟ ترک - دریافت پیام‌ها از این گروه برای شما متوقف خواهد شد. تاریخچه گپ حفظ خواهد شد. + دریافت پیام‌ها از این گروه برای شما متوقف خواهد شد. تاریخچه چت حفظ خواهد شد. دعوت اعضا %d رویداد گروه %s، %s و %d عضو دیگر متصل شدند %s و %s %s، %s و %d عضو و %d رویداد دیگر - گشودن + باز کردن کد امنیتی تغییر پیدا کرد وضعیت ناشناخته سازنده عبارت عبور جدید… - پایگاه داده رمزگذاری و عبارت عبور در تنظیمات ذخیره خواهد شد. - عبارت عبور رمزگذاری پایگاه داده به‌روز و در تنظیمات ذخیره خواهد شد. + پایگاه داده رمزنگاری و عبارت عبور در تنظیمات ذخیره خواهد شد. + عبارت عبور رمزنگاری پایگاه داده به‌روز و در تنظیمات ذخیره خواهد شد. امکان دسترسی مخزن کلید برای ذخیره کلمه عبور پایگاه داده وجود ندارد خطای پایگاه داده ناشناخته: %s تایید جابه‌جایی نامعتبر شما به این گروه پیوستید شما ترک کردید - نمایه گروه به‌روز شد + پروفایل گروه به‌روز شد عضو مدیر صاحب @@ -982,41 +980,41 @@ عبارت عبور از تنظیمات پاک شود؟ لطفا توجه داشته باشید: اگر عبارت عبور را از دست بدهید، قادر نخواهید بود آن را بازیابی کنید یا تغییر دهید.]]> عبارت عبور پایگاه داده اشتباه - پرونده: %s - عبارت عبور پایگاه داده برای گشودن گپ الزامی است. + فایل: %s + عبارت عبور پایگاه داده برای باز کردن چت الزامی است. خطای ناشناخته - گشودن گپ + باز کردن چت تلاش برای تغییر عبارت عبور پایگاه داده کامل نشد. ارتقای پایگاه داده تنزل پایگاه داده تایید ارتقای پایگاه داده گروه را ترک می‌کنید؟ امکان دعوت مخاطبان وجود ندارد! - در حال استفاده از نمایه ناشناس برای این گروه هستید - برای جلوگیری از اشتراک‌گذاری نمایه اصلی شما، دعوت مخاطبان مجاز نیست + در حال استفاده از پروفایل ناشناس برای این گروه هستید - برای جلوگیری از اشتراک‌گذاری پروفایل اصلی شما، دعوت مخاطبان مجاز نیست مخاطب حذف شد متصل شد ترک کرد %1$s حذف شد - شما حذف شدید - توافق رمزگذاری + شما را حذف کرد + رمزنگاری مورد توافق قرار گرفت ناظر عضو پیشین %1$s بسط دادن انتخاب نقش - عبارت عبور رمزگذاری پایگاه داده به‌روز خواهد شد. - مستقیما متصل شد + عبارت عبور رمزنگاری پایگاه داده به‌روز خواهد شد. + درخواست اتصال کرد %s متصل شد پیوستن به گروه به‌روزرسانی تایید عبارت عبور جدید… - پایگاه داده رمزگذاری شود؟ + پایگاه داده رمزنگاری شود؟ نسخه پایگاه داده ناسازگار - نمایه گروه به‌روز شد + پروفایل گروه به‌روز شده شما نشانی را برای %s تغییر دادید شما نشانی را تغییر دادید - مذاکره مجدد رمزگذاری مجاز است + تجدید مذاکره رمزنگاری مجاز است گروه حذف شد در حال اتصال (دعوت معرفی) - رمزگذاری سرتاسر استاندارد + رمزنگاری استاندارد انتها به انتها در حال اتصال (پذیرفته شد) در حال اتصال (اعلام شد) متصل شد @@ -1035,48 +1033,48 @@ از %1$s دعوت شده نقش %s به %s تغییر کرد گروه حذف شد - مذاکره مجدد رمزگذاری الزامی است - رمزگذاری برای %s بی‌عیب است - مذاکره مجدد رمزگذاری برای %s مجاز است - رمزگذاری سرتاسر مقاوم در برابر کوانتوم + تجدید مذاکره رمزنگاری الزامی است + رمزنگاری برای %s مورد تایید است + تجدید مذاکره رمزنگاری برای %s مجاز است + رمزنگاری انتها به انتهای مقاوم در برابر کوانتوم مخاطبی برای افزودن وجود ندارد - رد شدن از دعوت اعضا + نادیده گرفتن دعوت از اعضا انتخاب مخاطبان - پایگاه داده رمزگذاری شده! + پایگاه داده رمزنگاری شده! عبارت عبور فعلی… عضو %1$s به %2$s تغییر کرد - رمزگذاری + رمزنگاری به‌روزرسانی عبارت عبور پایگاه داده تعیین عبارت عبور از مخزن کلید اندروید برای ذخیره امن عبارت عبور استفاده می‌شود - به سرویس اعلان اجازه عمل می‌دهد. باید هر بار که برنامه شروع می‌شود عبارت عبور را وارد کنید - در دستگاه ذخیره نمی‌شود. - پایگاه داده رمزگذاری شده + پایگاه داده رمزنگاری شده مخاطب بررسی شد پاک کردن نمایش کنسول در پنجره جدید - می‌توانید گپ را از طریق تنظیمات برنامه / پایگاه داده یا با شروع مجدد برنامه شروع کنید. - گپ شروع شود؟ + می‌توانید چت را از طریق تنظیمات برنامه / پایگاه داده یا با شروع مجدد برنامه شروع کنید. + چت شروع شود؟ هشدار: ممکن است بعضی از اطلاعات را از دست بدهید! - گپ متوقف شده است - خطا در رمزگذاری پایگاه داده + چت متوقف شده است + خطا در رمزنگاری پایگاه داده عبارت عبور از مخزن کلید پاک شود؟ اعلان‌ها فقط تا زمان توقف برنامه تحویل داده خواهند شد! پاک کردن تعیین عبارت عبور پایگاه داده - لطفا عبارت عبور فعلی درست را وارد کنید. - پایگاه داده گپ شما رمزگذاری نشده است - برای محافظت از آن عبارت عبور تعیین کنید. + لطفا عبارت عبور صحیح فعلی را وارد کنید. + پایگاه داده چت شما رمزنگاری نشده است - برای محافظت از آن عبارت عبور تعیین کنید. عبارت عبور به صورت متن آشکار در تنظیمات ذخیره شده است. بعد از تغییر عبارت عبور یا شروع مجدد برنامه، عبارت عبور به صورت متن آشکار در تنظیمات ذخیره خواهد شد. عبارت عبور پایگاه داده تغییر داده شود؟ - پایگاه داده رمزگذاری خواهد شد. - لطفا عبارت عبور را به صورت امن ذخیره کنید، اگر آن را از دست دهید، قادر نخواهید بود به گپ دسترسی پیدا کنید. + پایگاه داده رمزنگاری خواهد شد. + لطفا عبارت عبور را به صورت امن ذخیره کنید، اگر آن را از دست دهید، قادر نخواهید بود به چت دسترسی پیدا کنید. خطا در پایگاه داده خطا در Keychain عبارت عبور پایگاه داده با آنچه در مخزن کلید ذخیره شده متفاوت است. خطا: %s عبارت عبور اشتباه! ورود عبارت عبور… - ذخیره عبارت عبور و گشودن گپ + ذخیره عبارت عبور و باز کردن چت برگرداندن پشتیبان پایگاه داده پشتیبان پایگاه داده برگردانده شود؟ لطفا بعد از برگرداندن پشتیبان پایگاه داده، کلمه عبور قبلی را وارد کنید. این عمل قابل برگشت نیست. @@ -1088,8 +1086,8 @@ پیوستن به صورت ناشناس این گروه دیگر وجود ندارد. شما دعوت گروه ارسال کردید - برای پیوستن لمس کنید - برای پیوستن به صورت ناشناس لمس کنید + برای پیوستن ضربه بزنید + برای پیوستن به صورت ناشناس ضربه بزنید %s مسدود شد مسدود سازی %s لغو شد نقش شما به %s تغییر کرد @@ -1101,28 +1099,28 @@ در حال تغییر نشانی… در حال تغییر نشانی برای %s… در حال تغییر نشانی… - رمزگذاری بی‌عیب است - در حال توافق رمزگذاری برای %s… - توافق رمزگذاری برای %s + رمزنگاری مورد تایید است + در حال توافق رمزنگاری برای %s… + رمزنگاری برای %s مورد توافق قرار گرفت نویسنده دعوت شد در حال اتصال (معرفی شد) نقش آغازین مخاطبی انتخاب نشده - در حال توافق رمزگذاری… + در حال توافق رمزنگاری… ترک کرد مخاطب %1$s به %2$s تغییر کرد نشانی مخاطب حذف شد تعیین نشانی مخاطب جدید - پایگاه داده با استفاده از عبارت عبور تصادفی رمزگذاری شده، می‌توانید آن را تغییر دهید. + پایگاه داده با استفاده از عبارت عبور تصادفی رمزنگاری شده، می‌توانید آن را تغییر دهید. بعد از شروع مجدد برنامه یا تغییر عبارت عبور، از مخزن کلید اندروید برای ذخیره امن عبارت عبور استفاده خواهد شد - اجازه دریافت اعلان‌ها را خواهد داد. - پایگاه داده رمزگذاری و عبارت عبور در مخزن کلید ذخیره خواهد شد. - عبارت عبور رمزگذاری پایگاه داده به‌روز و در مخزن کلید ذخیره خواهد شد. + پایگاه داده رمزنگاری و عبارت عبور در مخزن کلید ذخیره خواهد شد. + عبارت عبور رمزنگاری پایگاه داده به‌روز و در مخزن کلید ذخیره خواهد شد. عبارت عبور درست را وارد کنید. لطفا عبارت عبور را به صورت امن ذخیره کنید، اگر آن را از دست دهید، قادر به تغییرش نخواهید بود. - عبارت عبور در مخزن کلید پیدا نشد، لطفا به صورت دستی آن را وارد کنید. دلیل این اتفاق ممکن است برگرداندن اطلاعات برنامه با استفاده از یک ابزار پشتیبان‌گیری باشد. اگر این طور نیست، لطفا با توسعه دهندگان تماس بگیرید. - تنزل و گشودن گپ - گپ متوقف شده است. اگر از پیش از این پایگاه داده روی دستگاه دیگری استفاده می‌کردید، بهتر است قبل از شروع گپ، آن را برگردانید. + عبارت عبور در کی‌استور پیدا نشد، لطفاً آن را به‌صورت دستی وارد کنید. این ممکن است در صورتی اتفاق افتاده باشد که داده‌های برنامه را با استفاده از ابزار پشتیبان‌گیری بازیابی کرده‌اید. اگر این مورد نیست، لطفاً با توسعه‌دهندگان تماس بگیرید. + تنزل و باز کردن چت + چت متوقف شده است. اگر از پیش از این پایگاه داده روی دستگاه دیگری استفاده می‌کردید، بهتر است قبل از شروع چت، آن را برگردانید. به این گروه پیوستید. در حال اتصال به عضوی از گروه که از شما دعوت کرد. دعوت منقضی شد! دعوت گروه منقضی شد @@ -1130,30 +1128,30 @@ شما نقش %s را به %s تغییر دادید شما %s را مسدود کردید شما %1$s را حذف کردید - عکس نمایه حذف شد - تعیین عکس نمایه جدید - نمایه به‌روز شد - مذاکره مجدد رمزگذاری برای %s الزامی است + عکس پروفایل حذف شد + تعیین عکس پروفایل جدید + پروفایل به‌روز شد + تجدید مذاکره رمزنگاری برای %s الزامی است در حال ارسال از طریق اتصال اصلاح شود؟ اصلاح توسط عضو گروه پشتیبانی نمی‌شود - نمایه گپ شما به اعضای گروه ارسال خواهد شد + پروفایل چت شما به اعضای گروه ارسال خواهد شد ایجاد لینک خطا در به‌روزرسانی لینک گروه - زمان توقف پروتکل + وقفه پروتکل می‌توانید این نشانی را با مخاطبان خود به اشتراک بگذارید تا به آن‌ها اجازه دهید به %s متصل شوند. غیرفعال رسیدها غیرفعال هستند برای کنسول حذف به‌روزرسانی تنظیمات کلاینت را دوباره به سرورها متصل خواهد کرد. - نمایه گپ حذف شود؟ - خصوصی کردن نمایه! - می‌توانید نمایه کاربر را پنهان یا بی‌صدا کنید - برای نمایش منو لمس کنید و‍ نگه دارید. - لغو پنهان‌سازی نمایه - لغو پنهان‌سازی نمایه گپ - کلمه عبور نمایه - نمایه تصادفی شما + پروفایل چت حذف شود؟ + خصوصی کردن پروفایل! + می‌توانید پروفایل کاربر را پنهان یا بی‌صدا کنید - برای نمایش منو لمس کنید و‍ نگه دارید. + لغو پنهان‌سازی پروفایل + لغو پنهان‌سازی پروفایل چت + کلمه عبور پروفایل + پروفایل تصادفی شما ابتدایی پیام ارسالی خیر @@ -1168,7 +1166,7 @@ می‌توانید یک لینک یا کد QR به اشتراک بگذارید - هر کسی می‌تواند به گروه بپیوندد. اگر بعدا گروه را حذف کنید، اعضای گروه را از دست نخواهید داد. خطا در ایجاد لینک گروه خطا در ارسال دعوت - اشتراک‌گذاری نشانی + اشتراک‌گذاری آدرس این گروه بیش از %1$d عضو دارد، رسیدهای تحویل ارسال نمی‌شوند. نام محلی شناسه پایگاه داده @@ -1192,10 +1190,10 @@ اتصال مستقیم؟ درخواست اتصال به این عضو گروه ارسال خواهد شد. پیام خوشامدگویی ذخیره شود؟ - مذاکره مجدد رمزگذاری - ذخیره نمایه گروه + تجدید مذاکره رمزنگاری + ذخیره پروفایل گروه به‌روزرسانی - شما هنوز تماس‌ها و اعلان‌های نمایه‌های بی‌صدا را وقتی فعال هستند دریافت می‌کنید. + شما هنوز تماس‌ها و اعلان‌های پروفایل‌های بی‌صدا را وقتی فعال هستند دریافت می‌کنید. ناشناس سیستم وارد کردن تم @@ -1218,20 +1216,20 @@ روشن خطا در وارد کردن تم مخاطب اجازه می‌دهد - شما در حال دعوت از مخاطبی که با او نمایه ناشناسی به اشتراک گذاشته‌اید به گروهی هستید که در آن از نمایه اصلی خود استفاده می‌کنید + شما در حال دعوت از مخاطبی که با او پروفایل ناشناسی به اشتراک گذاشته‌اید به گروهی هستید که در آن از پروفایل اصلی خود استفاده می‌کنید حذف گروه افزودن پیام خوشامدگویی وضعیت شبکه - نمایه گروه روی دستگاه‌های اعضا ذخیره می‌شود، نه روی سرورها. - اتصال‌های نمایه و سرور + پروفایل گروه روی دستگاه‌های اعضا ذخیره می‌شود، نه روی سرورها. + اتصال‌های پروفایل و سرور (فعلی) عضو عضو از گروه حذف خواهد شد - این عمل قابل برگشت نیست! مسدود برای همه اصلاح توسط مخاطب پشتیبانی نمی‌شود ثانیه - تمام گپ‌ها و پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! - فقط اطلاعات نمایه محلی + تمام چت‌ها و پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! + فقط اطلاعات پروفایل محلی دعوت اعضا گروه حذف شود؟ گروه برای تمام اعضا حذف خواهد شد - این عمل قابل برگشت نیست! @@ -1242,27 +1240,27 @@ توسط مدیر حذف شد در ایجاد شد در: %s عضو مسدود شود؟ - ویرایش نمایه گروه + ویرایش پروفایل گروه حذف لینک خطا در ایجاد مخاطب عضو %s در %s %s (فعلی) نقش - ذخیره و به‌روزرسانی نمایه گروه - وقتی نمایه ناشناسی را با کسی به اشتراک می‌گذارید، این نمایه برای گروه‌هایی که شما را به آن‌ها دعوت می‌کند استفاده خواهد شد. + ذخیره و به‌روزرسانی پروفایل گروه + وقتی پروفایل ناشناسی را با کسی به اشتراک می‌گذارید، این پروفایل برای گروه‌هایی که شما را به آن‌ها دعوت می‌کند استفاده خواهد شد. نام گروه را وارد کنید: ایجاد گروه - خطا در ذخیره نمایه گروه + خطا در ذخیره پروفایل گروه بازنشاندن به پیش‌فرض‌ها - صدور تم + اکسپورت تم خطا در حذف عضو خطا در تغییر نقش - ثانوی + ثانویه ارسال شد در: %s %s: %s پیام ذخیره شده تغییر نقش - حذف نمایه + حذف پروفایل شما: %1$s تمام اعضای گروه متصل باقی خواهند ماند. تنها صاحبان گروه می‌توانند تنظیمات گروه را تغییر دهند. @@ -1274,8 +1272,8 @@ لغو مسدودسازی عضو لغو مسدودسازی برای همه پیام‌های %s نشان داده خواهند شد! - نقش به «%s» تغییر داده خواهد شد. تمام افراد گروه مطلع خواهند شد. - نقش به «%s» تغییر داده خواهد شد. عضو یک دعوت جدید دریافت خواهد کرد. + نقش به %s تغییر خواهد کرد. همه در گروه مطلع خواهند شد. + نقش به %s تغییر خواهد کرد. عضو یک دعوت‌نامه جدید دریافت خواهد کرد. خطا در مسدودسازی عضو برای همه گروه اتصال @@ -1292,28 +1290,28 @@ ایجاد گروه محرمانه تماما نامتمرکز - قابل مشاهده فقط توسط اعضا. نام کامل گروه: - زمان توقف پروتکل در کیلوبایت - دریافت همزمان - زمان توقف اتصال TCP - وقفه پینگ - شمار پینگ + وقفه پروتکل به کیلوبایت + همزمانی دریافت + وقفه زمانی اتصال TCP + فاصله زمانی بین هر پینگ + تعداد پینگ فعال کردن زنده نگه‌داشتن TCP ذخیره تنظیمات شبکه به‌روزرسانی شود؟ - افزودن نمایه + افزودن پروفایل لغو پنهان‌سازی لغو بی‌صدا کلمه عبور را در جستجو وارد کنید - برای فعال‌سازی نمایه لمس کنید. + برای فعال‌سازی پروفای ضربه بزنید. دوباره نمایش داده نشود بی‌صدا هنگام غیرفعال بودن! - حذف نمایه گپ - حالت ناشناس از حریم خصوصی شما با استفاده از یک نمایه تصادفی جدید برای هر مخاطب محافظت می‌کند. - اجازه می‌دهد اتصال‌های بی‌نام زیادی داشته باشید بدون اطلاعات مشترک بین آن‌ها در تنها یک نمایه گپ. + حذف پروفایل چت + حالت ناشناس از حریم خصوصی شما با استفاده از یک پروفایل تصادفی جدید برای هر مخاطب محافظت می‌کند. + اجازه می‌دهد اتصال‌های بی‌نام زیادی داشته باشید بدون اطلاعات مشترک بین آن‌ها در تنها یک پروفایل چت. تاریک SimpleX تم تاریک - مطمئن شوید پرونده دارای ترکیب YAML صحیح است. برای داشتن یک نمونه از ساختار پرونده تم، تم را صادر کنید. + مطمئن شوید فایل دارای ترکیب YAML صحیح است. برای داشتن یک نمونه از ساختار فایل تم، تم را اکسپورت کنید. ثانوی اضافی ابتدایی اضافی پس‌زمینه @@ -1330,33 +1328,33 @@ فقط شما می‌توانید پیام‌های صوتی ارسال کنید. هر دوی شما و مخاطبتان می‌توانید واکنش‌های پیام اضافه کنید. فقط شما می‌توانید واکنش‌های پیام اضافه کنید. - ارسال پیام‌های ناپدید شونده را منع می‌کنید. + ممنوع کردن ارسال پیام‌های ناپدید شونده. اجازه ارسال پیام‌های مستقیم را به اعضا می‌دهید. - اجازه ارسال پرونده‌ها و رسانه را می‌دهید. - اعضای گروه می‌توانند پیام‌های ارسالی را به صورت غیرقابل برگشت حذف کنند. (۲۴ ساعت) + اجازه ارسال فایل‌ها و رسانه را می‌دهید. + اعضا می‌توانند پیام‌های ارسالی را به صورت غیرقابل برگشت حذف کنند. (۲۴ ساعت) حذف بعد از تنظیمات گفت‌و‌گو - پرونده‌ها و رسانه + فایل‌ها و رسانه تماس‌های صوتی/تصویری " \nموجود در نسخه 5.1" به مخاطبان خود اجازه ارسال پیام‌های صوتی می‌دهید. فقط زمانی اجازه حذف پیام‌ها به صورت غیرقابل برگشت را می‌دهید که مخاطب شما این اجازه را به شما بدهد. (۲۴ ساعت) - پیام‌های ناپدید شونده در این گروه ممنوع هستند. + پیام‌های ناپدید شونده ممنوع است. تعیین تنظیمات گروه واکنش‌های پیام - حذف پیام به صورت غیرقابل برگشت در این گپ ممنوع است. + حذف پیام به صورت غیرقابل برگشت در این چت ممنوع است. %d هفته به مخاطبان خود اجازه ارسال پیام‌های ناپدید شونده دهید. فقط وقتی پیام‌های صوتی را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز می‌داند. - واکنش‌های پیام در این گپ ممنوع هستند. - ارسال پیام‌های صوتی را منع می‌کنید. - اعضای گروه می‌توانند پیام‌های صوتی ارسال کنند. + واکنش‌های پیام در این چت ممنوع هستند. + ممنوع کردن ارسال پیام‌های صوتی. + اعضا می‌توانند پیام‌های صوتی ارسال کنند. پذیرفتن هر دوی شما و مخاطبتان می‌توانید پیام‌های ناپدید شونده ارسال کنید. فقط شما می‌توانید پیام‌های ناپدید شونده ارسال کنید. - حذف پیام به صورت غیرقابل برگشت را منع می‌کنید. - واکنش‌های پیام‌ها را منع می‌کنید. + ممنوع کردن حذف غیرقابل بازگشت پیام. + ممنوع کردن واکنش به پیام‌ها. ارسال ۱۰۰ پیام آخر به اعضای جدید. تا ۱۰۰ پیام آخر به اعضای جدید ارسال خواهد شد. %d ماه @@ -1364,12 +1362,12 @@ %d دقیقه فقط وقتی تماس‌ها را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز می‌داند. منع تماس‌های صوتی/تصویری. - ارسال پرونده‌ها و رسانه را منع می‌کنید. + ممنوع کردن ارسال فایل‌ها و رسانه‌ها. اجازه ارسال لینک‌های SimpleX را می‌دهید. تاریخچه به اعضای جدید ارسال نمی‌شود. - اعضای گروه می‌توانند واکنش‌های پیام اضافه کنند. - اعضای گروه می‌توانند لینک‌های SimpleX ارسال کنند. - لینک‌های SimpleX در این گروه ممنوع هستند. + اعضا می‌توانند به پیام‌ها واکنش نشان دهند. + اعضا می‌توانند لینک‌های SimpleX را ارسال کنند. + لینک‌های SimpleX ممنوع هستند. %d ثانیه %d دقیقه %d ماه @@ -1379,9 +1377,9 @@ مطالعه بیشتر هر دوی شما و مخاطبتان می‌توانید پیام‌های صوتی ارسال کنید. فقط وقتی واکنش‌های پیام را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز می‌داند. - منع واکنش‌های پیام. + منع واکنش‌ها به پیام. فقط زمانی پیام‌های ناپدید شونده را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز بداند. - ارسال پیام‌های ناپدید شونده را منع می‌کنید. + ممنوع کردن ارسال پیام‌های ناپدید شونده. فقط مخاطبتان می‌تواند پیام‌های صوتی ارسال کند. %d روز مخاطبان می‌توانند پیام‌ها را برای حذف علامت بگذارند؛ شما قادر به مشاهده آن‌ها خواهید بود. @@ -1397,11 +1395,11 @@ فعال برای شما فعال برای مخاطب خاموش - دریافتی، ممنوع + دریافت شد، ممنوع شد تعیین ۱ روز - منع ارسال پیام‌ها صوتی. + ممنوع کردن ارسال پیام‌ها صوتی. به مخاطبان خود اجازه تماس با شما را می‌دهید. - پیام‌های ناپدید شونده در این گپ ممنوع هستند. + پیام‌های ناپدید شونده در این چت ممنوع هستند. هر دوی شما و مخاطبتان می‌توانید پیام‌ها را به صورتی غیرقابل برگشت حذف کنید. (۲۴ ساعت) هر دوی شما و مخاطبتان می‌توانید تماس برقرار کنید. فقط شما می‌توانید تماس برقرار کنید. @@ -1410,16 +1408,16 @@ اجازه ارسال پیام‌های ناپدید شونده می‌دهید. اجازه ارسال پیام‌های صوتی را می‌دهید. اجازه واکنش‌های پیام را می‌دهید. - ارسال لینک‌های SimpleX را منع می‌کنید + ممنوع کردن ارسال لینک‌های SimpleX عدم ارسال تاریخچه به اعضای جدید. - اعضای گروه می‌توانند پیام‌های ناپدید شونده ارسال کنند. - اعضای گروه می‌توانند پیام‌های مستقیم ارسال کنند. + اعضا می‌توانند پیام‌های ناپدید شونده ارسال کنند. + اعضا می‌توانند پیام‌های مستقیم ارسال کنند. پیام‌های مستقیم بین اعضا در این گروه ممنوع هستند. - حذف غیرقابل برگشت در این گروه ممنوع است. - پیام‌های صوتی در این گروه ممنوع هستند. - واکنش‌های پیام در این گروه ممنوع هستند. - اعضای گروه می‌توانند پرونده‌ها و رسانه ارسال کنند. - پرونده‌ها و رسانه در این گروه ممنوع هستند. + حذف پیام‌های غیرقابل بازگشت ممنوع است. + پیام‌های صوتی ممنوع است. + واکنش به پیام‌ها ممنوع است. + اعضا می‌توانند فایل‌ها و رسانه‌ها را ارسال کنند. + فایل‌ها و رسانه‌ها ممنوع هستند. %d ثانیه %d ساعت %d ساعت @@ -1434,10 +1432,10 @@ تمام اعضا فعال برای چی جدید است - پیام‌های صوتی در این گپ ممنوع هستند. + پیام‌های صوتی در این چت ممنوع هستند. فقط مخاطبتان می‌تواند واکنش‌های پیام اضافه کند. اجازه حذف پیام‌های ارسالی به صورت غیرقابل برگشت را می‌دهید. (۲۴ ساعت) - ارسال پیام‌های مستقیم به اعضا را منع می‌کنید. + ممنوع کردن ارسال پیام‌های مستقیم به اعضا. ظرفیت از محدودیت فراتر رفت - گیرنده پیام‌های ارسالی پیشین را دریافت نکرد. خطای سرور مقصد: %1$s خطا: %1$s @@ -1492,15 +1490,15 @@ عبارت عبور را وارد کنید خطا در حذف پایگاه داده کدهای امنیتی را با مخاطبان خود مقایسه کنید. - برنامه پرونده‌های جدید محلی (به جز ویدئوها) را رمزگذاری می‌کند. - رمزگذاری پرونده‌ها و رسانه ذخیره شده + برنامه فایل‌های جدید محلی (به جز ویدئوها) را رمزنگاری می‌کند. + رمزنگاری فایل‌ها و رسانه ذخیره شده به وسیله پروتکل امن مقاوم در برابر کوانتوم. مسدودسازی اعضای گروه خطا %s قطع شد، به دلیل: %s]]> کامپیوتر رابط چینی و اسپانیایی - به وسیله نمایه گپ (پیش‌فرض) یا به وسیله اتصال (آزمایشی). + به وسیله پروفایل چت (پیش‌فرض) یا به وسیله اتصال (آزمایشی). در حین اتصال به کامپیوتر، مهلت زمان اتصال تمام شد. روز‍ فعال کردن @@ -1521,9 +1519,9 @@ اتصال به صورت خودکار قابل کشف از طریق شبکه محلی سلولی - ایجاد نمایه جدید در برنامه کامپیوتر. 💻 + ایجاد پروفایل جدید در برنامه کامپیوتر. 💻 نشانی کامپیوتر - الصاق نشانی کامپیوتر + الصاق نشانی دسکتاپ یافتن از طریق شبکه محلی تمام اطلاعات وقتی وارد می‌شوند پاک می‌شوند. سفارشی کردن و اشتراک‌گذاری تم‌های رنگ. @@ -1532,24 +1530,24 @@ \n- رسیدهای تحویل (تا ۲۰ دقیقه). \n- سریع‌تر و پایداری بیشتر. گروه‌های بهتر - فعال کردن در گپ های مستقیم (آزمایشی)! + فعال کردن در چت‌های مستقیم (آزمایشی)! اتصال با کامپیوتر قطع شود؟ به زودی! بارگیری موفق نبود - گپ جابه‌جا شد! + چت منتقل شد! آرشیو و بارگذاری تایید بارگذاری اتصال اینترنت خود را بررسی و دوباره امتحان کنید برنامه کامپیوتر جدید وصل کردن برنامه‌های موبایل و کامپیوتر! 🔗 یافتن و پیوستن به گروه‌ها - ایجاد یک گروه با استفاده از یک نمایه تصادفی. + ایجاد یک گروه با استفاده از یک پروفایل تصادفی. جابه‌جایی اطلاعات برنامه استفاده از کامپیوتر را در برنامه موبایل باز و کد QR را اسکن کنید.]]> در حال بارگیری آرشیو - خطا در صدور پایگاه داده گپ + خطا در اکسپورت پایگاه داده چت خطا در ذخیره تنظیمات - تمام مخاطبان، مکالمات و پرونده‌های شما به صورت امن، رمزگذاری و به صورت بسته‌های داده به واسطه‌های XFTP تنظیم شده، بارگذاری خواهند شد. + تمام مخاطبان، مکالمات و فایل‌های شما به صورت امن، رمزنگاری و به صورت بسته‌های داده به واسطه‌های XFTP تنظیم شده، بارگذاری خواهند شد. موبایل متصل شد مدیران می‌توانند لینک‌ها را برای پیوستن به گروه‌ها ایجاد کنند. قطع اتصال موبایل‌ها @@ -1582,14 +1580,14 @@ نمایش وضعیت پیام برای محافظت از نشانی IP شما، مسیریابی خصوصی از سرورهای SMP شما به منظور تحویل پیام‌ها استفاده می‌کند. مسیریابی پیام خصوصی - مسیریابی خصوصی + مسیردهی خصوصی تنظیمات سرور بهبودیافته با پیام خوشامدگویی اختیاری. مخاطبان شما می‌توانند اجازه حذف کامل پیام را بدهند. - چندین نمایه گپ - نام‌های پرونده خصوصی - پالایش گپ‌های خوانده نشده و برگزیده. - اصلاح رمزگذاری بعد از برگرداندن پشتیبان‌ها. + چندین پروفایل چت + نام‌های فایل خصوصی + فیلتر کردن چت‌های خوانده‌نشده و موردعلاقه. + اصلاح رمزنگاری بعد از برگرداندن پشتیبان‌ها. مدیریت گروه رابط کاربری ژاپنی و پرتقالی ناپدید کردن یک پیام @@ -1601,8 +1599,8 @@ \n- ویرایش تاریخچه. پیوستن سریع‌تر و پیام‌های قابل اطمینان تر. باز فرستادن و ذخیره پیام‌ها - مربع، دایره، و هر چیزی در این بین. - در گپ‌های مستقیم فعال خواهد شد! + مربع، دایره، یا هر چیزی در این بین. + در چت‌های مستقیم فعال خواهد شد! ارسال رسیدهای تحویل برای تمام مخاطبان فعال خواهد شد. می‌توانید بعدا از طریق تنظیمات آن را فعال کنید نام این دستگاه @@ -1612,7 +1610,7 @@ در انتظار متصل شدن موبایل: %s مشغول است]]> تصادفی - تجدید + تازه‌سازی %s نسخه پشتیبانی نشده دارد. لطفا، اطمینان حاصل کنید که از نسخه یکسان روی هر دو دستگاه استفاده می‌کنید]]> %s قطع شد]]> این ویژگی هنوز پشتیبانی نمی‌شود. انتشار بعدی را امتحان کنید. @@ -1620,22 +1618,22 @@ گروه از قبل وجود دارد! شما هم اکنون در حال پیوستن به گروه از طریق این لینک هستید. در حال وارد کردن آرشیو - یا لینک آرشیو را الصاق کنید + یا لینک آرشیو را پیست کنید الصاق لینک آرشیو می‌توانید دوباره امتحان کنید. - پرونده حذف شد یا لینک نامعتبر است + فایل حذف شد یا لینک نامعتبر است جابه‌جایی دستگاه در حال آماده‌سازی بارگذاری نهایی‌سازی جابه‌جایی - یا لینک پرونده را به صورت امن به اشتراک بگذارید - شروع گپ + یا لینک فایل را به صورت امن به اشتراک بگذارید + شروع چت اترنت باسیم بهبودهای بیشتر به زودی! سازگار نیست! تایید اتصال‌ها %s غیرفعال است]]> شما از پیش اتصال به وسیله این نشانی را درخواست کرده‌اید! - شروع مجدد گپ + شروع مجدد چت حتی وقتی در مکالمه غیرفعال باشند. خیر موبایل متصلی وجود ندارد @@ -1646,21 +1644,21 @@ محفوظ نگه داشتن پیش‌نویس پیام آخر، به همراه ضمیمه‌ها. درخواست اتصال تکرار شود؟ از موبایل اسکن کنید - ارسال رسیدهای تحویل برای تمام مخاطبان در تمام نمایه‌های گپ قابل مشاهده، فعال خواهد شد. + ارسال رسیدهای تحویل برای تمام مخاطبان در تمام پروفایل‌های چت قابل مشاهده، فعال خواهد شد. پیام‌ها مستقیما ارسال شود وقتی نشانی IP محافظت می‌شود و سرور مقصد شما از مسیریابی خصوصی پشتیبانی نمی‌کند. پیام‌ها مستقیما ارسال شود وقتی سرور مقصد شما از مسیریابی خصوصی پشتیبانی نمی‌کند. - شکل دادن به تصاویر نمایه + شکل دادن به تصاویر پروفایل با سپاس از کاربران - از طریق Weblate همکاری کنید! با سپاس از کاربران - از طریق Weblate همکاری کنید! این لینک یک‌بارمصرف خودتان است! این نشانی‌ SimpleX خودتان است! - واسطه‌های ناشناخته + سرورهای ناشناخته محافظت نشده تایید اتصال تایید عبارت عبور پایگاه داده وقتی IP پنهان است شما هم اکنون در حال اتصال از طریق لینک یک‌بارمصرف هستید! - نمایه‌های گپ پنهان + پروفایل‌های چت پنهان (این دستگاه v%s)]]> متصل کردن یک موبایل پیش‌نویس پیام @@ -1668,7 +1666,7 @@ پیام‌های صوتی پیام‌های ارسال شده بعد زمان تعیین شده حذف خواهند شد. رابط فرانسوی - به وسیله یک کلمه عبور از نمایه‌های گپ خود محافظت کنید! + به وسیله یک کلمه عبور از پروفایل‌های چت خود محافظت کنید! بهبودهای بیشتر به زودی! با سپاس از کاربران - از طریق Weblate همکاری کنید! رسیدهای تحویل پیام! @@ -1683,7 +1681,7 @@ \n- و بیشتر! کد عبور خودتخریبی با سپاس از کاربران - از طریق Weblate همکاری کنید! - ویدئوها و پرونده‌ها تا ۱ گیگابایت + ویدئوها و فایل‌ها تا ۱ گیگابایت تایید عبارت عبور لینک نامعتبر هنگام برقراری تماس‌های صوتی و تصویری. @@ -1697,20 +1695,18 @@ ماه انتخاب دستگاه موبایل جدید - در حال متوقف کردن گپ + در حال متوقف کردن چت بدون اتصال شبکه - دیگر + سایر تایید امنیت اتصال در حال آماده‌سازی بارگیری جابه‌جایی کامل شد نباید از یک پایگاه داده روی دو دستگاه استفاده کنید.]]> پیام خوشامدگویی گروه - - مطلع کردن اختیاری مخاطبان حذف شده. -\n- نام‌های نمایه شامل فاصله. -\n- و بیشتر! + - مطلع کردن اختیاری مخاطبان حذف شده. \n- نام‌های پروفایل شامل فاصله. \n- و بیشتر! رابط کاربری مجارستانی و ترکی جابه‌جایی به دستگاه دیگر از طریق کد QR. - گشودن گروه + باز کردن گروه WiFi تماس‌های تصویر در تصویر استفاده از برنامه در حین مکالمه. @@ -1720,7 +1716,7 @@ پنهان کردن صفحه برنامه در برنامه‌های اخیر. پیام‌های زنده گیرنده‌ها به‌روزرسانی‌ها را هم‌زمان با تایپ کردن شما مشاهده می‌کنند. - برای محافظت از منطقه زمانی، پرونده‌های تصویر/صدا از UTC استفاده می‌کنند. + برای محافظت از منطقه زمانی، فایل‌های تصویر/صدا از UTC استفاده می‌کنند. سریع و بدون منتظر ماندن تا زمانی که فرستنده آنلاین شود. کاهش بیشتر استفاده باتری تعیین پیام نمایش داده شده به اعضای جدید! @@ -1731,11 +1727,11 @@ تیک دومی که ما نداشتیم! ✅ برای پنهان کردن پیام‌های ناخواسته. یادداشت‌های خصوصی - با پرونده‌ها و رسانه رمزگذاری شده. + با فایل‌ها و رسانه رمزنگاری شده. تحویل پیام بهبود یافته ساعت می‌توانید بعدا از طریق تنظیمات حریم خصوصی و امنیت برنامه آن‌ها را فعال کنید. - گشودن پورت در فایروال + باز کردن پورت در فایروال %s مفقود است]]> برای اجازه دادن به برنامه موبایل به کامپیوتر متصل شوید، این پورت را در فایروال خود باز کنید، اگر فعال است خطای داخلی @@ -1743,9 +1739,9 @@ %1$s هستید.]]> تکرار بارگیری وارد کردن ناموفق بود - تکرار وارد کردن + تکرار ایمپورت نهایی‌سازی جابه‌جایی در دستگاه دیگر. - برای ادامه دادن، گپ باید متوقف شود. + برای ادامه دادن، چت باید متوقف شود. تکرار بارگذاری %s بارگذاری شد بارگذاری ناموفق بود @@ -1757,34 +1753,34 @@ حالت ناشناس ساده‌شده تغییر حالت ناشناس هنگام اتصال. پیوستن به مکالمات گروه - جهت اتصال لینک را الصاق کنید - تاریخچه اخیر و روبات فهرست راهنمای بهبودیافته. - نوار جستجو لینک‌های دعوت قبول می‌کند. + جهت اتصال لینک را الصاق کنید! + تاریخچه اخیر و روبات فهرست راهنمای بهبود یافته. + نوار جستجو لینک‌های دعوت را قبول می‌کند. با استفاده باتری کاهش یافته. ثانیه - رمزگذاری مقاوم در برابر کوانتوم + رمزنگاری مقاوم در برابر کوانتوم گروه‌های امن‌تر تنها یک دستگاه در هر زمان می‌تواند مورد استفاده قرار گیرد درخواست پیوستن تکرار شود؟ به گروه خود می‌پیوندید؟ %s بارگیری شد - پرونده صادر شده وجود ندارد + فایل اکسپورت شده وجود ندارد جابه‌جایی به دستگاه دیگر - هشدار: شروع گپ روی چندین دستگاه پشتیبانی نمی‌شود و باعث عدم موفقیت در تحویل پیام خواهد شد + هشدار: شروع چت روی چندین دستگاه پشتیبانی نمی‌شود و باعث عدم موفقیت در تحویل پیام خواهد شد نام دستگاه با کلاینت موبایل متصل شده به اشتراک گذاشته خواهد شد. - پیدا کردن سریع‌تر گپ‌ها + پیدا کردن سریع‌تر چت‌ها لینک‌های گروه حذف غیرقابل برگشت پیام - امنیت SimpleX Chat به وسیله Tails of Bits مورد سنجش قرار گرفت. + امنیت SimpleX Chat به وسیله Tails of Bits مورد سنجش قرار گرفته است. انزوای ترابری کد نشست %1$s هستید.]]> موبایل‌های متصل سرورهای ناشناخته! حفاظت از نشانی IP - برنامه از شما خواهد خواست تا بارگیری‌ها از سرورهای پرونده ناشناخته را تایید کنید (به جز .onion یا وقتی پروکسی SOCKS فعال است). - پرونده‌ها - بدون تور یا VPN، نشانی IP شما برای سرورهای پرونده قابل رویت خواهد بود. + این اپلیکیشن از شما می‌خواهد که دانلودها از سرورهای فایل ناشناخته را تأیید کنید (به جز .onion یا زمانی که پروکسی SOCKS فعال باشد). + فایل‌ها + بدون Tor یا VPN، نشانی IP شما برای سرورهای فایل قابل رویت خواهد بود. بدون تور یا VPN، نشانی IP شما برای این واسطه‌های XFTP قابل رویت خواهد بود: \n%1$s. هیچ @@ -1794,11 +1790,11 @@ اشکال‌زدایی تحویل اطلاعات صف پیام تم برنامه - تایید پرونده‌ها از سرورهای ناشناخته. + تایید فایل‌ها از سرورهای ناشناخته. اطلاعات صف سرور: %1$s \n \nآخرین پیام دریافتی: %2$s - نمایش فهرست گپ در پنجره جدید + نمایش فهرست چت در پنجره جدید حالت تاریک سیاه حالت رنگ @@ -1809,9 +1805,9 @@ سیستم خطا در مقداردهی اولیه WebView. سیستم خود را به نسخه جدید به روز کنید. لطفا با توسعه‌دهنگان تماس بگیرید. \nخطا: 9%s - رنگ‌های گپ - تم گپ - تم نمایه + رنگ‌های چت + تم چت + تم پروفایل پس‌زمینه کاغذدیواری ابتدایی اضافی ۲ تنظیمات پیشرفته @@ -1832,23 +1828,704 @@ اعمال بر حالت روشن مسیریابی پیام خصوصی 🚀 - ظاهر گپ‌های خود را متمایز کنید! - تم‌های جدید گپ + ظاهر چت‌های خود را متمایز کنید! + تم‌های جدید چت از نشانی IP خود در برابر واسطه‌های پیام‌رسانی انتخاب شده توسط مخاطبانتان محافظت کنید. \nدر تنظیمات «شبکه و سرورها» فعال کنید. - دریافت امن پرونده‌ها - پرونده یافت نشد - احتمالا حذف یا لغو شده. - کلید اشتباه یا نشانی پرونده ناشناخته - به احتمال زیاد پرونده حذف شده است. - خطای پرونده - خطای پرونده موقت - وضعیت پرونده + دریافت امن فایل‌ها + فایل یافت نشد - احتمالا حذف یا لغو شده. + کلید اشتباه یا نشانی پرونده ناشناخته - به احتمال زیاد فایل حذف شده است. + خطای فایل + خطای فایل موقت + وضعیت فایل وضعیت پیام - وضعیت پرونده: %s + وضعیت فایل: %s وضعیت پیام: %s خطای کپی - لطفا بررسی کنید که تلفن همراه و کامپیوتر به شبکه محلی یکسانی متصل هستند، و فایروال کامپیوتر شما اجازه اتصال را میدهد. -\nلطفا هر مشکل دیگری را با توسعه‌دهندگان به اشتراک بگذارید. + لطفا بررسی کنید که تلفن همراه و کامپیوتر به شبکه محلی یکسانی متصل هستند، و فایروال کامپیوتر شما اجازه اتصال را می‌دهد. \nلطفا هر مشکل دیگری را با توسعه‌دهندگان به اشتراک بگذارید. این لینک توسط موبایل دیگری استفاده شده است، لطفا لینک جدیدی در کامپیوتر بسازید. - خطای سرور پرونده:%1$s - %1$d خطای پرونده:\n%2$s + خطای سرور فایل:%1$s + %1$d خطای فایل:\n%2$s + %1$d فایل هنوز در حال بارگیری هستند + %1$d فایل پاک شدند. + %1$d فایل بارگیری نشدند. + %1$d خطای دیگر در فایل. + %1$s پیام باز ارسال نشدند. + چت با یک عضو + یک گزارش + یک سال + a + b + درباره عملگرها + پذیرفتن + پذیرفتن + پذیرفتن به عنوان عضو + پذیرفتن به عنوان ناظر + پذیرفتن شرایط + %1$s پذیرفته شد + دعوت پذیرفته شد + شما پذیرفته شدید + پذیرفتن عضو + تأیید شد + خطاهای تأیید + ارتباطات فعال + سرویس‌دهنده‌های رسانه و فایل اضافه شدند + سرویس‌دهنده‌های پیام اضافه شدند + %1$d فایل هنوز در حال بارگیری هستند. + شرایط پذیرفته شد + افزودن دوستان + افزودن فهرست + نشانی یا پیوند یک‌بار مصرف؟ + تنظیمات نشانی + به‌روزرسانی آدرس + اضافه کردن اعضای تیم + افزودن به فهرست + اعضای تیم خود را به چت‌ها اضافه کنید. + همه + همه + همه چت‌ها از فهرست %s پاک خواهند شد و فهرست حذف می‌شود. + همه پیام‌های جدید از این اعضا پنهان خواهند شد! + اجازه تماس‌ها؟ + اجازه گزارش پیام‌ها به ناظران. + همه پروفایل‌ها + همه گزارش‌ها برای شما بایگانی خواهند شد. + همه سرویس‌دهنده‌ها + دلیلی دیگر + برنامه همیشه در پس‌زمینه اجرا می‌شود + نشست برنامه + نوارهای ابزار برنامه + به‌روزرسانی برنامه بارگیری شد + بایگانی + همه گزارش‌ها بایگانی شوند؟ + بایگانی کردن تماس‌ها برای چت کردن در آینده. + مخاطبین بایگانی شده + گزارش بایگانی شده + گزارش بایگانی شده توسط %s + بایگانی %d گزارش؟ + بایگانی گزارش + گزارش بایگانی شود؟ + پیوند نامعتبر + لطفاً بررسی کنید که لینک سیمپلکس درست است. + فقط شما و ناظران آن را می‌بینید + فقط فرستنده و ناظران آن را می‌بینند + درخواست اتصال داد + پیوند کانال SimpleX + هرزنامه + محتوای نامناسب + نقض دستورالعمل‌های انجمن + پروفایل نامناسب + خطا در ذخیره سرویس‌دهنده‌ها + بدون سرویس‌دهنده پیام. + هیچ سرویس‌دهنده‌ای برای دریافت پیام وجود ندارد. + هیچ سرویس‌دهنده‌ای برای مسیردهی پیام خصوصی وجود ندارد. + بدون سرویس‌دهنده رسانه و فایل. + هیچ سرویس‌دهنده‌ای برای ارسال فایل وجود ندارد. + هیچ سرویس‌دهنده‌ای برای دریافت فایل وجود ندارد. + برای پروفایل چت %s: + خطاها در پیکربندی سرویس‌دهنده‌ها. + خطا در پذیرفتن شرایط + هرزنامه + محتوا شرایط استفاده را نقض می‌کند + نشانی سرویس‌دهنده با تنظیمات شبکه سازگار نیست: %1$s. + نسخه سرویس‌دهنده با برنامه شما سازگار نیست: %1$s. + خطای مسیردهی خصوصی + خطا در اتصال به سرویس‌دهنده فورواردینگ %1$s. لطفاً بعداً تلاش کنید. + نشانی سرویس‌دهنده فورواردینگ با تنظیمات شبکه سازگار نیست: %1$s. + نسخه سرویس‌دهنده فورواردینگ با تنظیمات شبکه سازگار نیست: %1$s. + سرویس‌دهنده فورواردینگ %1$s نتوانست به سرویس‌دهنده مقصد %2$s متصل شود. لطفاً بعداً تلاش کنید. + نشانی سرویس‌دهنده مقصد %1$s با تنظیمات سرویس‌دهنده فورواردینگ %2$s سازگار نیست. + نسخه سرویس‌دهنده مقصد %1$s با سرویس‌دهنده فورواردینگ %2$s سازگار نیست. + لطفاً بعداً تلاش کنید. + خطا در فوروارد کردن پیام‌ها + خطا در ایجاد گزارش + خطا در پذیرفتن عضو + خطا در پاک کردن چت با عضو + پیوند اتصال پشتیبانی‌نشده + این پیوند به نسخه جدیدتری از برنامه نیاز دارد. لطفاً برنامه را به‌روزرسانی کنید یا از مخاطب خود بخواهید پیوند سازگاری بفرستد. + اتصال مسدود شد + اتصال توسط گرداننده سرویس‌دهنده مسدود شده است:\n%1$s. + پیام‌های نرسیده + ارتباط به حد نصاب پیام‌های نرسیده رسید، ممکن است مخاطب شما آفلاین باشد. + خطا در به‌روزرسانی فهرست چت + افزودن پیام + امکان تماس وجود ندارد! + خطا در ارسال پیام + تماس با مخاطب امکان پذیر نیست + تماس با عضو گروه ممکن نیست + تغییر پروفایل امکان پذیر نیست + ارسال پیام به عضو گروه امکان پذیر نیست + ارسال پیام امکان پذیر نیست + بایگانی گزارش ها + تماس های با کیفیت تر + عملکرد بهتر گروه ها + امنیت و حریم خصوصی قوی تر + تغییر لیست + تغییر ترتیب + چت + این چت از قبل وجود دارد! + چت با اعضا + پیام برای همه اعضا حذف خواهد شد - این عمل قابل بازگشت نیست! + بخش های حذف شده + بخش های دانلود شده + برای مجوز میکروفون، روی آیکون اطلاعات کنار نوار آدرس کلیک کنید + تکمیل شد + تایید حذف مخاطب؟ + اتصال + اتصال + متصل + اتصال سریع تر! 🚀 + درحال اتصال + درحال اتصال به مخاطب، لطفا منتظر بمانید یا بعداً بررسی کنید! + وضعیت اتصال و سرور ها + اتصال آماده نیست + مخاطب حذف شد. + مخاطبین + باز کردن چت + باز کردن چت جدید + باز کردن گروه جدید + به منوی تنظیمات سافاری / وب‌سایت‌ها / و سپس میکروفن رفته و گزینه \"اجازه دادن برای localhost\" را انتخاب کنید. + باز کردن تنظیمات سرور + باز کردن لینک + باز کردن لینک‌ها از لیست چت‌ها + باز کردن برای پذیرش + باز کردن برای اتصال + باز کردن برای عضویت + آیا لینک وب باز شود؟ + باز کردن با %s + اپراتور + سرور اپراتور + سازماندهی چت‌ها به صورت لیست‌ها + یا ایمپورت کردن فایل آرشیو + یا به‌طور خصوصی به اشتراک بگذارید + سایر + سایر خطاها + سایر سرورهای SMP + سایر سرورهای XFTP + عبارت عبور در کی‌استور قابل خواندن نیست، لطفاً آن را به‌صورت دستی وارد کنید. این ممکن است پس از یک به‌روزرسانی سیستم که با برنامه سازگار نیست، اتفاق افتاده باشد. اگر این مورد نیست، لطفاً با توسعه‌دهندگان تماس بگیرید. + عبارت عبور در مخزن کلید قابل خواندن نیست. این ممکن است پس از به‌روزرسانی سیستم که با برنامه سازگار نیست، اتفاق افتاده باشد. اگر این طور نیست، لطفا با توسعه دهندگان تماس بگیرید. + کلمه عبور + الصاق لینک + در حال انتظار + در حال انتظار + در انتظار تأیید + در انتظار بررسی + پخش کردن از فهرست چت. + لطفاً از مخاطب خود بخواهید که تماس‌ها را فعال کند. + لطفاً اندازه پیام را کاهش دهید و دوباره ارسال کنید. + لطفاً اندازه پیام را کاهش دهید یا فایل رسانه‌ای را حذف کنید و دوباره ارسال کنید. + لطفا برنامه را شروع مجدد کنید. + لطفاً منتظر بمانید تا مدیران گروه درخواست شما برای پیوستن به گروه را بررسی کنند. + سرورهای از پیش تنظیم شده + سرورهای از پیش تنظیم شده + سرورهای متصل قبلی + حریم خصوصی برای مشتریان شما. + سیاست حریم خصوصی و شرایط استفاده. + چت‌های خصوصی، گروه‌ها و مخاطبین شما برای اپراتورهای سرور قابل دسترسی نیستند. + نام‌های فایل‌های رسانه‌ای خصوصی. + وقفه در مسیردهی خصوصی + آدرس کوتاه خواهد بود و پروفایل شما از طریق آدرس به اشتراک گذاشته خواهد شد. + ممنوع کردن گزارش پیام‌ها به مدیران. + وقفه پس‌زمینه پروتکل + پروکسی شده + سرورهای پروکسی شده + احراز هویت پروکسی + نوار ابزارهای قابل دسترسی در برنامه + نوار ابزار چت قابل دسترس + نوار ابزار چت قابل دسترس + پیام‌های دریافتی + مجموع دریافتی + خطاهای دریافت + اتصال مجدد + اتصال مجدد به تمام سرورهای متصل برای مجبور کردن به تحویل پیام. این کار از ترافیک اضافی استفاده می‌کند. + اتصال مجدد به تمام سرورها + اتصال مجدد به سرور؟ + اتصال مجدد به سرورها؟ + اتصال مجدد به سرور برای مجبور کردن به تحویل پیام. این کار از ترافیک اضافی استفاده می‌کند. + رد کردن + رد کردن درخواست مخاطب + رد شده + رد شده + رد کردن عضو؟ + بعداً یادآوری کن + موبایل‌های از راه دور + حذف کردن آرشیو؟ + از گروه حذف شد + اعضا حذف شوند؟ + پیام‌ها را حذف می‌کند و اعضا را مسدود می‌کند. + گزارش + گزارش محتوا: فقط مدیران گروه آن را خواهند دید. + گزارش دادن پیام‌ها در این گروه ممنوع است. + گزارش دادن پروفایل عضو: فقط مدیران گروه آن را خواهند دید. + گزارش سایر موارد: فقط مدیران گروه آن را خواهند دید. + دلیل گزارش؟ + گزارش: %s + گزارش‌ها + گزارش‌های ارسالی به مدیران + گزارش هرزنامه: فقط مدیران گروه آن را خواهند دید. + گزارش تخلف: فقط مدیران گروه آن را خواهند دید. + درخواست ارسال شد + درخواست عضویت رد شد + بازنشاندن + تنظیم مجدد تمام راهنماها + تنظیم مجدد تمام آمار + تمام آمار مجدد تنظیم شود؟ + بررسی + بررسی شرایط + بررسی شده توسط مدیران + بررسی اعضای گروه + بعداً بررسی شود + بررسی اعضا + بررسی اعضا قبل از پذیرش (کوبیدن). + آیا تنظیمات پذیرش ذخیره شود؟ + ذخیره و برقراری مجدد اتصال + ذخیره فهرست + در حال ذخیره پیام‌های %1$s + اسکن / الصاق لینک + جستجو + امن شده + انتخاب + انتخاب پروفایل چت + تنظیمات برگزیده چت، این پیام را ممنوع می‌کند. + %d انتخاب شده + اپراتورهای شبکه را برای استفاده انتخاب کنید. + تنظیم پذیرش اعضا + تنظیم انقضای پیام در چت‌ها. + تنظیمات + اشتراک‌گذاری لینک یک بار مصرف با یک دوست + اشتراک‌گذاری آدرس به صورت عمومی + آیا لینک گروه به‌روزرسانی شود؟ + اشتراک‌گذاری پروفایل + به‌روزرسانی + آیا آدرس به‌روزرسانی شود؟ + اشتراک‌گذاری آدرس SimpleX در شبکه‌های اجتماعی + توضیح کوتاه: + لینک کوتاه + نمایش اطلاعات برای + نمایش درصد + آدرس SimpleX و لینک‌های یکبارمصرف برای اشتراک توسط هرگونه پیامرسانی امن می‌باشند. + آدرس SimpleX یا لینک یکبار مصرف؟ + SimpleX Chat و Flux به توافق رسیده‌اند که سرورهای تحت کنترل Flux را به برنامه اضافه کنند. + پروتکل‌های SimpleX توسط Trail of Bits بررسی شده‌اند. + اندازه + نادیده گرفتن این نسخه + سرور SMP + پروکسی SOCKS + نرم + برخی از فایل(ها) اکسپورت نشدند + صدا خاموش شد + %s سرور + پایدار + در حال شروع از %s. + در حال شروع از %s.\nتمام داده‌ها به صورت خصوصی بر روی دستگاه شما نگهداری می‌شوند. + آمار + خط‌خورده + قوی + مشترک شد + خطاهای اشتراک + اشتراک‌ها نادیده گرفته شدند + در حین تماس، صدا و ویدیو را جابجا کنید. + پروفایل چت را برای دعوت‌های یک‌بار مصرف تغییر دهید. + حالت سیستم + انتها + برای چت، روی \"اتصال\" ضربه بزنید. + برای ارسال درخواست، روی \"اتصال\" ضربه بزنید. + برای ایجاد آدرس SimpleX در آینده، در منو روی \"ایجاد آدرس SimpleX\" ضربه بزنید. + روی \"پیوستن به گروه\" ضربه بزنید. + اتصال TCP + وقفه زمانی اتصال TCP در پس‌زمینه + پورت TCP برای پیام‌رسانی + این اپلیکیشن با استفاده از اپراتورهای مختلف در هر مکالمه، از حریم خصوصی شما محافظت می‌کند. + پیام‌ها برای تمام اعضا حذف خواهند شد. + پیام‌ها برای تمام اعضا به عنوان مدیریت‌شده علامت‌گذاری خواهند شد. + پذیرفتن درخواست تماس + پذیرفتن درخواست تماس + پرسش + تلاش‌ها + بتا + بهبود تاریخ پیام‌ها + امنیت بهتر ✅ + تجربه کاربری بهتر + بیوگرافی: + بیو خیلی بزرگ است + مسدود کردن اعضا برای همه؟ + تار کردن + تار کردن برای حفظ بهتر حریم خصوصی. + تار کردن رسانه + آدرس کسب و کار + چت‌های تجاری + ارتباط تجاری + کسب و کارها + با استفاده از SimpleX Chat شما موافقت می‌کنید که:\n- فقط محتوای قانونی را در گروه‌های عمومی ارسال کنید.\n- به سایر کاربران احترام بگذارید - از ارسال هرزنامه خودداری کنید. + تماس + با یک مخاطب استفاده شود - آن را به صورت حضوری یا از طریق هر پیام‌رسانی به اشتراک بگذارید.]]> + به صورت رمزنگاری انتها به انتهاو با امنیت پساکوانتومی در پیام‌های مستقیم ارسال می‌شوند.]]> + %s.]]> + %s.]]> + رمزنگاری انتها به انتها محافظت می‌شوند.]]> + %s اعمال خواهد شد.]]> + %s اعمال خواهد شد.]]> + %s اعمال خواهد شد.]]> + %s اعمال خواهد شد.]]> + پایگاه داده چت اکسپورت شد + چت برای شما حذف خواهد شد - این عمل غیرقابل بازگشت است! + چت با مدیران + چت با مدیران + چت با مدیران + چت با عضو + چت با اعضا قبل از پیوستن. + بررسی به‌روزرسانی‌ها + بررسی به‌روزرسانی‌ها + بررسی پیام‌ها هر ۱۰ دقیقه + بخش‌ها بارگذاری شدند + شرایط در %s پذیرفته شد. + شرایط استفاده + شرایط برای اپراتورهای فعال پس از ۳۰ روز پذیرفته خواهد شد. + شرایط در: %s پذیرفته خواهد شد. + شرایط به‌طور خودکار برای اپراتورهای فعال در: %s پذیرفته خواهد شد. + سرورهای SMP پیکربندی‌شده + سرورهای XFTP پیکربندی‌شده + پیکربندی اپراتورهای سرور + سرورهای متصل شده + اتصال نیاز به تجدید مذاکره رمزنگاری دارد. + اتصالات + امنیت اتصال + به دوستان خود سریع‌تر متصل شوید. + مخاطب حذف شد. + مخاطب حذف شد! + مخاطب غیر فعال شد + مخاطب آماده نیست + مخاطب باید قبول کند… + مخاطب حذف خواهد شد - این عمل غیرقابل بازگشت است! + ادامه + ادامه + کنترل شبکه خود را در دست بگیرید + گفتگو حذف شد! + گوشه + ایجاد + ایجاد لینک یک‌بار مصرف + ایجاد شد + ایجاد لیست + متن شرایط فعلی بارگذاری نشد، می‌توانید شرایط را از طریق این لینک بررسی کنید: + پروفایل فعلی + شکل پیام قابل تنظیم. + %d چت(ها) + %d چت با اعضا + خطاهای رمزگشایی + پیش‌فرض (%s) + حذف + حذف چت + حذف چت + چت حذف شود؟ + پیام‌های چت را از دستگاه خود حذف کنید. + حذف پروفایل چت برای + آیا می‌خواهید چت با عضو را حذف کنید؟ + حذف شد + آیا می‌خواهید %d پیام از اعضا را حذف کنید؟ + حذف لیست؟ + حذف پیام‌ها بعد از + حذف یا مدیریت حداکثر ۲۰۰ پیام. + حذف گزارش + حذف حداکثر ۲۰ پیام به‌طور همزمان. + حذف بدون اطلاع‌رسانی + حذف خطاها + توضیحات خیلی بزرگ است + آمار دقیق + جزئیات + پیام‌های مستقیم بین اعضا ممنوع است. + پیام‌های مستقیم بین اعضا در این چت ممنوع است. + غیر فعال + آیا می‌خواهید حذف خودکار پیام‌ها را غیرفعال کنید؟ + غیر فعال شد + غیر فعال شد + غیرفعال کردن حذف پیام‌ها + %d پیام + از اعتبارنامه‌ها با پروکسی استفاده نشود. + پیام‌های مهم را از دست ندهید. + دانلود + دانلود شده + دانلود فایل‌ها + دانلود خطاها + در حال دانلود به‌روزرسانی، اپلیکیشن را نبندید. + دانلود نسخه‌های جدید از گیت‌هاب. + دانلود %s (%s) + %d گزارش + اعضا از چت حذف خواهند شد - این عمل غیرقابل بازگشت است! + اعضا از گروه حذف خواهند شد - این عمل غیرقابل بازگشت است! + عضو از چت حذف خواهد شد - این عمل غیرقابل بازگشت است! + عضو به گروه ملحق خواهد شد، آیا می‌خواهید عضو را بپذیرید؟ + منشن اعضا👋 + پیام + پیام + پیام فوروارد شد. + پیام بلافاصله پس از ضربه زدن بر روی \"اتصال\" ارسال می‌شود. + پیام خیلی بزرگ است! + اگر عضو فعال شود، پیام ممکن است بعداً ارسال گردد. + دریافت پیام + سرورهای پیام + پیام‌های این اعضا نمایش داده خواهد شد! + شکل پیام + پیام‌ها در این چت هرگز حذف نخواهند شد. + پیام‌ها دریافت شدند + پیام‌ها ارسال شدند + پیام‌ها پس از انتخاب حذف شدند. + پیام‌ها حذف خواهند شد - این عمل غیرقابل بازگشت است! + پیام‌ها برای حذف علامت‌گذاری خواهند شد. دریافت‌کننده(ها) قادر خواهند بود این پیام‌ها را آشکار کنند. + مدیر + مدیران + بی‌صدا کردن همه + تمرکززدایی شبکه + اپراتور شبکه + اپراتورهای شبکه + تجربه چت جدید 🎉 + نقش جدید گروه: مدیر + گزینه‌های رسانه‌ای جدید + عضو جدید می‌خواهد به گروه ملحق شود. + پیام جدید + سرور جدید + اعتبارنامه‌های جدید SOCKS هر بار که اپلیکیشن را راه‌اندازی کنید، استفاده خواهند شد. + اعتبارنامه‌های جدید SOCKS برای هر سرور استفاده خواهند شد. + خیر + هیچ سرویس پس‌زمینه‌ای وجود ندارد + هیچ چتی وجود ندارد + هیچ چتی پیدا نشد + هیچ چتی در لیست %s وجود ندارد. + هیچ چتی با اعضا وجود ندارد + هنوز هیچ ارتباط مستقیمی وجود ندارد، پیام توسط مدیر فوروارد شده است. + هیچ مخاطب فیلتر شده‌ای وجود ندارد + هیچ اطلاعاتی وجود ندارد، سعی کنید دوباره بارگذاری کنید + هیچ پیامی وجود ندارد + هیچ جلسه مسیریابی خصوصی وجود ندارد + یادداشت‌ها + هیچ چیزی انتخاب نشده است + هیچ چیزی برای فوروارد کردن وجود ندارد! + اطلاع‌رسانی‌ها و باتری + همگام‌سازی نشده است + هیچ چت خوانده‌نشده‌ای وجود ندارد + خاموش + خاموش + خاموش + فقط مالکین چت می‌توانند تنظیمات را تغییر دهند. + فقط گفتگو حذف شود + باز کردن + باز کردن تغییرات + - باز کردن چت برای اولین پیام خوانده نشده.\n- پرش به پیام‌های نقل‌قول شده. + شرایط باز کردن + باز کردن مکان فایل + دستگاه‌های شیائومی: لطفاً در تنظیمات سیستم، گزینه شروع خودکار را فعال کنید تا اعلان‌ها کار کنند.]]> + %s.]]> + %s.]]> + %s، شرایط استفاده را بپذیرید.]]> + %1$s متصل هستید.]]> + پس از پذیرش درخواست‌تان قادر به ارسال پیام خواهید بود.]]> + تغییر حذف خودکار پیام؟ + پایگاه داده چت + تکراری‌ها + تغییر + Flux را در تنظیمات شبکه و سرورها فعال کنید تا حریم خصوصی فراداده بهتر شود. + فعال کردن لاگ‌ها + مذاکره مجدد رمزنگاری در حال انجام است. + خطا + خطا در افزودن سرور + خطا در تغییر پروفایل + خطا در ایجاد لیست چت + خطا در راه‌اندازی WebView. لطفاً اطمینان حاصل کنید که WebView نصب شده و معماری پشتیبانی شده آن arm64 است. \nخطا: %s + خطا در بارگذاری لیست‌های چت + خطا در باز کردن چت + خطا در باز کردن گروه + خطا در خواندن عبارت عبور پایگاه داده + خطا در اتصال مجدد به سرور + خطا در اتصال مجدد به سرورها + خطا در رد درخواست مخاطب + خطا در بازنشانی آمار + خطاها + خطا در ذخیره‌سازی پایگاه داده + خطا در ذخیره‌سازی پروکسی + خطا در ذخیره‌سازی تنظیمات + خطا در تغییر پروفایل + خطا در به‌روزرسانی سرور + منقضی شده + حذف سریع‌تر گروه‌ها. + ارسال سریع‌تر پیام‌ها. + علاقه‌مندی‌ها + فایل توسط اپراتور سرور مسدود شده است: \n%1$s. + فایل‌ها + اصلاح کردن + اتصال اصلاح شود؟ + اندازه فونت + برای همه مدیران + برای بهبود حریم خصوصی فراداده. + به عنوان مثال، اگر مخاطب شما پیام‌ها را از طریق یک سرور چت SimpleX دریافت کند، برنامه شما آن‌ها را از طریق یک سرور Flux تحویل خواهد داد. + برای من + برای مسیریابی خصوصی + برای رسانه‌های اجتماعی + آیا می‌خواهید %1$s پیام را ارسال کنید؟ + در حال ارسال %1$s پیام + در حال ارسال پیام‌ها… + آیا می‌خواهید پیام‌ها را بدون فایل ارسال کنید؟ + حداکثر می‌توانید ۲۰ پیام را به‌طور همزمان ارسال کنید. + لینک کامل + زمانی که به شما اشاره شد، مطلع شوید. + گروه + گروه حذف شد + گروه‌ها + به مدیران در مدیریت گروه‌هایشان کمک کنید. + چگونه به حفظ حریم خصوصی کمک می‌کند + بهبود ناوبری چت + غیرفعال + اندازه فونت را افزایش دهید. + با موفقیت نصب شد + نصب به‌روزرسانی + دعوت + دعوت + دعوت به چت + آدرس IP و اتصالات شما را محافظت می‌کند. + پیوستن به گروه + گفتگو را حفظ کنید + ترک کردن چت + آیا می‌خواهید چت را ترک کنید؟ + ترافیک کمتری در شبکه‌های موبایل + فهرست + نام فهرست... + نام فهرست و ایموجی باید برای تمام فهرست‌ها متفاوت باشد. + در حال فراخوانی پروفایل… + اطمینان حاصل کنید که پیکربندی پروکسی صحیح است. + سرورهای رسانه و فایل + متوسط + پذیرش عضو + عضو از نسخه قدیمی استفاده می‌کند + عضو غیرفعال + گزارش‌های عضو + اعضا می‌توانند پیام‌ها را به مدیران گزارش دهند. + آیا می‌خواهید درخواست تماس ارسال کنید؟ + ارسال خطاها + پیامی ارسال کنید تا تماس‌ها فعال شوند. + گزارش‌های خصوصی ارسال کنید + ارسال درخواست + درخواست را بدون پیام ارسال کنید + بازخورد خصوصی خود را به گروه‌ها ارسال کنید. + مستقیم ارسال شد + پیام‌های ارسال شده + تعداد کل ارسال شده + پس از اتصال به مخاطب شما ارسال شد. + توسط پروکسی ارسال شد + سرور + سرور به اپراتور %s اضافه شد. + آدرس سرور + اپراتور سرور تغییر کرد. + اپراتورهای سرور + پروتکل سرور تغییر کرد. + اطلاعات سرورها + آمار سرورها بازنشانی خواهد شد - این عمل غیرقابل بازگشت است! + نام چت را تنظیم کنید… + گزارش برای شما بایگانی خواهد شد. + نقش به %s تغییر خواهد کرد. همه در چت مطلع خواهند شد. + دومین اپراتور پیش‌فرض در برنامه! + فرستنده مطلع نخواهد شد. + سرورهای فایل‌های جدید پروفایل چت فعلی شما + آرشیو پایگاه داده بارگذاری‌شده به‌طور دائمی از سرورها حذف خواهد شد. + این عمل غیرقابل بازگشت است - پیام‌های ارسال‌شده و دریافت‌شده در این چت که قبل از تاریخ انتخاب‌شده هستند، حذف خواهند شد. + این پیام حذف شده یا هنوز دریافت نشده است. + زمان ناپدید شدن فقط برای مخاطبان جدید تنظیم شده است. + برای مطلع شدن از نسخه‌های جدید، بررسی دوره‌ای برای نسخه‌های Stable یا Beta را فعال کنید. + تغییر لیست چت: + برای برقراری تماس، اجازه دهید از میکروفن شما استفاده شود. تماس را پایان دهید و دوباره تلاش کنید. + برای جلوگیری از جایگزینی لینک شما، می‌توانید کدهای امنیتی مخاطب را مقایسه کنید. + برای دریافت + برای ارسال + کل + برای استفاده از پروفایل دیگر پس از تلاش برای اتصال، چت را حذف کرده و دوباره از لینک استفاده کنید. + شفافیت + جلسات Transport + رفع انسداد اعضا برای همه؟ + منشن‌های خوانده نشده + به‌روزرسانی + به‌روزرسانی موجود است: %s + شرایط به‌روزرسانی شده + دانلود به‌روزرسانی لغو شد + به‌روزرسانی خودکار برنامه + بارگذاری شد + فایل‌های بارگذاری‌شده + خطاهای بارگذاری + از اعتبارنامه‌های پروکسی متفاوت برای هر اتصال استفاده شود. + از اعتبارنامه‌های پروکسی متفاوت برای هر پروفایل استفاده شود. + استفاده برای فایل‌ها + استفاده برای پیام‌ها + استفاده از اعتبارنامه‌های تصادفی + نام کاربری + استفاده از %s + استفاده از سرورها + زمانی که پورتی مشخص نشده است، از پورت TCP %1$s استفاده شود. + فقط برای سرورهای پیش‌فرض از پورت TCP 443 استفاده شود. + از برنامه با یک دست استفاده کنید. + از پورت وب استفاده کنید + ویدئو + مشاهده شرایط + مشاهده شرایط به‌روزرسانی شده + وب‌سایت + پیام خوش‌آمدگویی + زمانی که بیش از یک اپراتور فعال است، هیچ‌کدام از آن‌ها فراداده‌ای برای فهمیدن اینکه چه کسی با چه کسی ارتباط برقرار می‌کند، ندارند. + سرور XFTP + بله + شما این عضو را پذیرفتید + شما به این سرورها متصل نیستید. از مسیریابی خصوصی برای ارسال پیام‌ها به آن‌ها استفاده می‌شود. + شما می‌توانید آن را در تنظیمات ظاهر تغییر دهید. + شما می‌توانید اپراتورها را در تنظیمات شبکه و سرورها پیکربندی کنید. + شما می‌توانید سرورها را از طریق تنظیمات پیکربندی کنید. + شما می‌توانید پیام را کپی کرده و اندازه آن را کاهش دهید تا ارسال شود. + شما می‌توانید در هر پیام تا %1$s عضو را منشن کنید! + شما می‌توانید به %1$s از مخاطبان بایگانی‌شده پیام ارسال کنید. + شما می‌توانید نام اتصال را تنظیم کنید تا به یاد داشته باشید که لینک با چه کسی به اشتراک گذاشته شده است. + شما هنوز می‌توانید مکالمه با %1$s را در فهرست چت‌ها مشاهده کنید. + شما نمی‌توانید پیام‌ها را ارسال کنید! + شما می‌توانید گزارش‌های خود را در چت با مدیران مشاهده کنید. + شما ترک کردید + شما می‌توانید پایگاه داده اکسپورت شده را منتقل کنید. + شما می‌توانید آرشیو اکسپورت شده را ذخیره کنید. + شما باید به مخاطب خود اجازه تماس دهید تا بتوانید با او تماس بگیرید. + بیوگرافی شما: + مخاطب تجاری شما + پروفایل چت شما به اعضای چت ارسال خواهد شد. + اتصال شما به %s منتقل شد، اما هنگام تغییر پروفایل خطایی رخ داد. + مخاطب شما + مخاطبان شما + اعتبارنامه‌های شما ممکن است به‌صورت رمزنگاری‌نشده ارسال شوند. + گروه شما + پروفایل شما + سرورهای شما + شما دیگر پیام‌هایی از این چت دریافت نخواهید کرد. تاریخچه چت حفظ خواهد شد. + بزرگنمایی + استفاده از پروفایل ناشناس + به‌روزرسانی لینک گروه + لینک کوتاه خواهد بود و پروفایل گروه از طریق لینک به اشتراک گذاشته خواهد شد. + اشتراک آدرس قدیمی + اشتراک لینک قدیمی + به مخاطبین خود خوش آمد بگویید 👋 + بیوگرافی پروفایل و پیام خوش آمد را تنظیم کنید. + چت‌های خود را تمیز نگه‌دارید + پیام‌های ناپدید شونده به‌طور پیش‌فرض فعال شوند. + آدرس کوتاه SimpleX + آدرس خود را ایجاد کنید + آدرس خود را به‌روزرسانی کنید + آدرس خود را به اشتراک بگذارید + ۴ زبان جدید رابط کاربری + کاتالان، اندونزیایی، رومانیایی و ویتنامی - با تشکر از کاربران ما! + عضو حذف شده است - نمی‌توان درخواست را قبول کرد + این تنظیمات برای پروفایل فعلی شماست + درخواست‌های تماس از گروه‌ها + درخواست اتصال از گروه %1$s + برای استفاده از ربات باز کنید + برای استفاده از ربات، روی اتصال ضربه بزنید + ربات + برای ارسال دستورات، باید متصل باشید. + گزینه‌های منسوخ شده + حذف ردیابی لینک + باز کردن لینک کامل + باز کردن لینک تمیز + اجازه دهید مخاطبین شما فایل‌ها و رسانه‌ها را ارسال کنند. + فقط در صورتی که مخاطب شما اجازه دهد، فایل‌ها و رسانه‌ها را مجاز کنید. + ارسال فایل‌ها و رسانه‌ها را ممنوع کنید. + هم شما و هم مخاطب شما می‌توانید فایل‌ها و رسانه‌ها را ارسال کنید. + فقط شما می‌توانید فایل و رسانه ارسال کنید. + فقط مخاطب شما می‌تواند فایل‌ها و رسانه‌ها را ارسال کند. + ارسال فایل‌ها و رسانه‌ها در این چت ممنوع است. + پل ارتباطی سیمپلکس diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 43fcb2f5f7..46b6e6e2fd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -2344,4 +2344,21 @@ Erreur lors de la lecture de la phase secrète de la base de données Aider les administrateurs à modérer leurs groupes. Tous les nouveaux messages de ces membres seront cachés ! + Utiliser le profil incognito + Chat ouvert + Ouvrir un nouveau chat + Ouvrir un nouveau groupe + Lien pour la voie SimpleX + Pas de session de routage privé + Membre acceptant des erreurs + Erreur effaçant le chat avec le membre + Lien de connexion pas soutenu + Ce lien requiert une version de l\'application plus récente. Veuillez s\'il-vous-plait actualiser l\'application ou demander à votre contact de vous envoyer un lien compatible. + Erreur lors de rejeter la demande de contact + Erreur en ouvrant le chat + Erreur en ouvrant le groupe + Erreur en changeant le profil + Quatres nouvelles langues d\'interface + Partagez votre adresse + Actualisez votre adresse diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml index 9e0d476dc1..e6f02f34f2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml @@ -285,4 +285,4 @@ सदस्य खोजें बंद है - \ No newline at end of file + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index c8897c4063..a3c6a65e32 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -21,12 +21,12 @@ A SimpleXről Kiemelőszín fogadott hívás - Hozzáférés a kiszolgálókhoz SOCKS-proxyn a következő porton keresztül: %d? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót. + Hozzáférés a kiszolgálókhoz SOCKS proxyn a következő porton keresztül: %d? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót. Elfogadás Elfogadás gombra fent, majd: Elfogadás inkognitóban - Elfogadja a meghívási kérést? + Elfogadja a kapcsolódási kérést? Elfogadás Elfogadás Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. @@ -37,22 +37,22 @@ Egy üres csevegési profil lesz létrehozva a megadott névvel, és az alkalmazás a szokásos módon megnyílik. %s visszavonva Előre beállított kiszolgálók hozzáadása - A hívások kezdeményezése le van tiltva ebben a csevegésben. - Az összes partneréhez és csoporttaghoz külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.\nMegjegyzés: ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet.]]> + A hívások kezdeményezése le van tiltva. + Az összes partneréhez és csoporttaghoz külön TCP-kapcsolat (és SOCKS-hitelesítési adat) lesz használva.\nMegjegyzés: ha sok kapcsolata van, akkor az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet.]]> hivatkozás előnézetének visszavonása - Az összes csevegési profiljához az alkalmazásban külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.]]> + Az összes csevegési profiljához az alkalmazásban külön TCP-kapcsolat (és SOCKS-hitelesítési adat) lesz használva.]]> Mindkét fél küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonságos tárolására használják – lehetővé teszi az értesítési szolgáltatás működését. - Érvénytelen az üzenet hasítóértéke + Hibás az üzenet kivonata Háttér - Megjegyzés: az üzenet- és fájltovábbító kiszolgálók SOCKS-proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének elküldése közvetlen kapcsolatot használnak.]]> + Megjegyzés: az üzenet- és fájltovábbító kiszolgálók SOCKS proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének küldése közvetlen kapcsolatot használ.]]> Alkalmazásadatok biztonsági mentése Az adatbázis előkészítése sikertelen A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára. A csevegési profillal (alapértelmezett), vagy a kapcsolattal (BÉTA). Egy új, véletlenszerű profil lesz megosztva. A hangüzenetek küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. - Az alkalmazás összeállítási száma: %s + Alkalmazás összeállítási száma: %s Hang- és videóhívások Speciális hálózati beállítások A hangüzenetek küldése engedélyezve van a partnerei számára. @@ -63,7 +63,7 @@ Kapcsolódás folyamatban! Nem lehet fogadni a fájlt Hitelesítés elérhetetlen - Az alkalmazás verziója + Alkalmazás verziója Üdvözlőüzenet hozzáadása titkosítás elfogadása %s számára… \nElérhető az v5.1-es kiadásban @@ -79,7 +79,7 @@ A háttérszolgáltatás mindig fut – az értesítések megjelennek, amint az üzenetek elérhetővé válnak. Az elküldött üzenetek végleges törlése engedélyezve van. (24 óra) Mindkét fél küldhet hangüzeneteket. - Téves üzenet ID + Hibás az üzenet azonosítója A reakciók hozzáadása az üzenetekhez engedélyezve van a partnerei számára. A hangüzenetek küldése engedélyezve van. A reakciók hozzáadása az üzenetekhez csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. @@ -89,8 +89,8 @@ Hívások a zárolási képernyőn: titkosítás elfogadása… Nem lehet meghívni a partnert! - téves üzenet ID - Meghívási kérések automatikus elfogadása + hibás az üzenet azonosítója + Partneri kapcsolatkérések automatikus elfogadása Megjegyzés: NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos szín @@ -107,11 +107,11 @@ Letiltás Néhány további dolog Hitelesítés visszavonva - A fájlok- és a médiatartalmak küldése engedélyezve van. + A fájlok és a médiatartalmak küldése engedélyezve van. Az összes csevegés és üzenet törölve lesz – ez a művelet nem vonható vissza! hanghívás félkövér - Az alkalmazás-jelkód helyettesítve lesz egy önmegsemmisítő-jelkóddal. + Az alkalmazásjelkód helyettesítve lesz egy önmegsemmisítő jelkóddal. Arab, bolgár, finn, héber, thai és ukrán – köszönet a felhasználóknak és a Weblate-nek. Engedélyezi a hangüzeneteket? Mindig használjon továbbítókiszolgálót @@ -122,7 +122,7 @@ Élő csevegési üzenet visszavonása Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra) Hang- és videóhívások - érvénytelen az üzenet hasítóértéke + hibás az üzenet kivonata Mindig fut Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi az értesítések fogadását. Az összes alkalmazásadat törölve. @@ -137,12 +137,12 @@ Mégse Az alkalmazás csak akkor tud értesítéseket fogadni, amikor meg van nyitva. A háttérszolgáltatás nem fog elindulni Továbbfejlesztett üzenetek - A cím módosítása meg fog szakadni. A régi fogadási cím lesz használva. + A kliens megszakítja a cím módosítását, és a régi fogadási címet fogja használni. Engedélyezés - Érvénytelen számítógépcím + Hibás a számítógép címe Profil hozzáadása Mellékelés - Alkalmazás jelkód + Alkalmazásjelkód Felkérték a kép fogadására Kamera Nem érhető el a Keystore az adatbázis jelszavának mentéséhez @@ -161,7 +161,7 @@ Mindkét fél tud hívásokat kezdeményezni. Sikertelen hitelesítés %s összes új üzenete el lesz rejtve! - Az alkalmazás verziója: v%s + Alkalmazás verziója: v%s A hívások kezdeményezése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. Kiszolgáló hozzáadása Hang bekapcsolva @@ -185,7 +185,7 @@ kapcsolódva Szerepkör módosítása Kapcsolódva - Hitelesítőadatok megerősítése + Hitelesítési adat megerősítése Módosítja a fogadási címet? módosította a címet az Ön számára Önmegsemmisítő-mód módosítása @@ -193,7 +193,7 @@ Kapcsolódás Közvetlenül kapcsolódik? Kapcsolódás - közvetlenül kapcsolódott + partneri kapcsolatot kért kapcsolat %1$d a partner e2e titkosítással rendelkezik Csoport létrehozása véletlenszerű profillal. @@ -212,8 +212,8 @@ Fő verzió: v%s Partner ellenőrizve Kapcsolódik saját magához? - Kimásolva a vágólapra - Meghívási kérés elküldve! + Vágólapra másolva + Kapcsolódási kérés elküldve! Kapcsolódás a számítógéphez Kapcsolat Helyesbíti a nevet a következőre: %s? @@ -255,7 +255,7 @@ A csevegés megállt Sötét Profil létrehozása - törölt csoport + törölte a csoportot Törlés az összes tagnál Hivatkozás létrehozása Csevegési beállítások @@ -294,15 +294,15 @@ Kiszolgáló címének ellenőrzése és újrapróbálkozás. Törli a csoportot? Adatbázis fejlesztésének megerősítése - Saját profil létrehozása + Profil létrehozása cím módosítása… kapcsolódás… Hívás kapcsolása - Törli a fájl- és a médiatartalmakat? + Törli a fájlokat és a médiatartalmakat? befejezett CSEVEGÉSI ADATBÁZIS - Önmegsemmisítő-jelkód módosítása - Sorba állítás létrehozása + Önmegsemmisítő jelkód módosítása + Várólista létrehozása színezett kapcsolódás… Sötét téma @@ -322,15 +322,15 @@ Csoporthivatkozás létrehozása Csevegési konzol Fájlok törlése az összes csevegési profilból - Sorba állítás törlése + Várólista törlése Partner törlése cím módosítása… Társítva a hordozható eszközhöz Jelenlegi jelmondat… - Fájl kiválasztása + Válasszon ki egy fájlt Kép törlése Fájl létrehozása - Tikos csoport létrehozása + Titkos csoport létrehozása Elvetés Törli a partnert? Kiürítés @@ -339,7 +339,7 @@ Fájl-összehasonlítás Csevegések Törli az üzenetet? - Törli a függőben lévő meghívót? + Törli a függőben lévő kapcsolatot? Adatbázis titkosítva! Kiüríti a csevegést? Adatbázis visszafejlesztése @@ -350,7 +350,7 @@ Az adatbázis titkosítási jelmondata frissülni fog és a beállításokban lesz tárolva. Adatbázis-azonosító Adatbázis-azonosító: %d - Adatbázis-azonosítók és átvitel-izolációs beállítások. + Adatbázis-azonosítók és átvitelelkülönítési beállítások. Az adatbázis titkosítási jelmondata frissülni fog és a Keystore-ban lesz tárolva. Az adatbázis titkosítva lesz, a jelmondat pedig a beállításokban lesz tárolva. Kiszolgáló törlése @@ -366,7 +366,7 @@ %dmp Kézbesítési jelentések! Az eszközön nincs beállítva a képernyőzár. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén. - Titkosítás visszafejtési hiba + Titkosítás-visszafejtési hiba Eltűnik: %s szerkesztve Törlés @@ -408,7 +408,7 @@ nap %d nap Duplikált megjelenítendő név! - Letiltás (felülírások megtartásával) + Letiltás (egyéni beállítások megtartása) Adatbázis fejlesztése %d üzenet letiltva Eltűnik @@ -431,7 +431,7 @@ %dp Szétkapcsolás Szerkesztés - Letiltás (csoport felülírások megtartásával) + Letiltás (csoport egyéni beállításainak megtartása) %d csoportesemény %d hónap Csoportprofil szerkesztése @@ -463,7 +463,7 @@ Hiba történt az adatbázis titkosításakor Hiba történt a csoport törlésekor Kilépés mentés nélkül - A tárolt fájlok- és a médiatartalmak titkosítása + Tárolt fájlok és médiatartalmak titkosítása Hiba történt a cím beállításakor A csoportmeghívó lejárt Hiba történt az ICE-kiszolgálók mentésekor @@ -492,7 +492,7 @@ Csoporthivatkozások Végre, megvannak! 🚀 Hiba történt a csevegés elindításakor - A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon. + A csoportprofil a tagok eszközein tárolódik, nem a kiszolgálókon. Adja meg a jelmondatot… Hiba történt a felhasználói adatvédelem frissítésekor Titkosít @@ -500,7 +500,7 @@ Hiba történt az SMP-kiszolgálók mentésekor Visszafejlesztés és a csevegés megnyitása A csoport inaktív - Gyors és nem kell várni, amíg a feladó online lesz! + Gyors és nem kell várni, amíg az üzenetküldő online lesz! Hiba történt a csoporthoz való csatlakozáskor Kedvenc Csoport moderálása @@ -509,12 +509,12 @@ a titkosítás újraegyeztetése szükséges %s számára Hiba történt a profilváltáskor! Kísérleti funkciók - Engedélyezés (felülírások megtartásával) + Engedélyezés (egyéni beállítások megtartása) Adja meg a helyes jelmondatot. A csoport törölve lesz az Ön számára – ez a művelet nem vonható vissza! Titkosítja az adatbázist? - A zárolási képernyőn megjelenő hívások engedélyezése a Beállításokban. - titkosítás elfogadva + A zárolási képernyőn megjelenő hívások engedélyezése a beállításokban. + titkosítása elfogadva Engedélyezi a kézbesítési jelentéseket? Hiba történt a csoportprofil mentésekor hiba @@ -527,12 +527,12 @@ Fájl: %s Hívás befejezése Hiba történt a csoporthivatkozás törlésekor - Fájl elmentve + Fájl mentve Kapcsolat javítása? Fájlok és médiatartalmak KONZOLHOZ Nem sikerült a titkosítást újraegyeztetni. - Hiba történt a felhasználó-profil törlésekor + Hiba történt a felhasználóprofil törlésekor Csoporttag általi javítás nem támogatott Adja meg az üdvözlőüzenetet… Titkosított adatbázis @@ -540,11 +540,11 @@ A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését. Fájl letöltése Nem sikerült betölteni a csevegést - Adja meg a kiszolgálót kézzel + Kiszolgáló megadása kézzel A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! Hiba történt a csoporthivatkozás létrehozásakor A galériából - Engedélyezés (csoport felülírások megtartásával) + Engedélyezés (csoport egyéni beállításainak megtartása) Hiba történt a partner törlésekor A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) Hiba történt a szerepkör módosításakor @@ -560,7 +560,7 @@ Hiba történt a csoporthivatkozás frissítésekor a csoport törölve csoportprofil frissítve - Hiba történt a függőben lévő meghívó törlésekor + Hiba történt a függőben lévő kapcsolat törlésekor Hiba történt a csevegési adatbázis importálásakor Hiba történt a kézbesítési jelentések engedélyezésekor! Hiba történt az XFTP-kiszolgálók mentésekor @@ -578,12 +578,12 @@ A csoportmeghívó már nem érvényes, a küldője eltávolította. A csoport teljes neve: súgó - Önmegsemmisítő-jelkód engedélyezése + Önmegsemmisítő jelkód engedélyezése KÍSÉRLETI Hiba történt a cím módosításának megszakításakor Hiba történt a fájl fogadásakor - titkosítás rendben - Hiba történt a meghívási kérés törlésekor + titkosítása rendben van + Hiba történt a partneri kapcsolatkérés törlésekor Engedélyezi a kézbesítési jelentéseket a csoportok számára? Partner általi javítás nem támogatott Fájl nem található @@ -608,15 +608,15 @@ Hiba történt a cím létrehozásakor engedélyezve Hiba történt a részletek betöltésekor - Hiba történt a meghívási kérés elfogadásakor + Hiba történt a partneri kapcsolatkérés elfogadásakor a titkosítás újraegyeztetése engedélyezve van %s számára a titkosítás újraegyeztetése szükséges Rejtett csevegési profilok Fájlok és médiatartalmak - A kép elmentve a „Galériába” + Kép mentve a galériába Elrejtés Azonnal - A fájlok- és a médiatartalmak küldése le van tiltva! + A fájlok és a médiatartalmak küldése le van tiltva! Profil elrejtése Hogyan használja a saját kiszolgálóit Csevegési üzenetek gyorsabb megtalálása @@ -656,7 +656,7 @@ Az azonnali értesítések le vannak tiltva! Azonnali értesítések! Kép - A fájlok- és a médiatartalmak küldése le van tiltva. + A fájlok és a médiatartalmak küldése le van tiltva. Hogyan működik Elrejtve: Hiba történt a partnerrel történő kapcsolat létrehozásában @@ -669,15 +669,15 @@ A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését. QR-kód beolvasásával.]]> A kapott SimpleX Chat-meghívási hivatkozását megnyithatja a böngészőjében: - Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő-jelkódot: + Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: Megtalált számítógép Számítógépek A markdown használata Csevegési profil létrehozása Védett a kéretlen tartalommal szemben Hordozható eszközök leválasztása - Különböző nevek, profilképek és átvitel-izoláció. - Elutasítás esetén a feladó NEM kap értesítést. + Különböző nevek, profilképek és átvitelelkülönítés. + Elutasítás esetén a kérés küldője NEM kap értesítést. Szerepkörválasztó kibontása A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! meghíva @@ -699,7 +699,7 @@ Nincsenek fogadott vagy küldött fájlok Megnyitás hordozható eszköz-alkalmazásban, majd koppintson a Kapcsolódás gombra az alkalmazásban.]]> Markdown az üzenetekben - meghívás a(z) %1$s csoportba + meghívás a(z) %1$s nevű csoportba Zárolási mód Új hordozható eszköz Kapcsolatok megtartása @@ -710,13 +710,13 @@ Nagy fájl! Helyi név Hálózat és kiszolgálók - Értesítés előnézete + Értesítésekben megjelenő információk Társítsa össze a hordozható eszköz- és számítógépes alkalmazásokat! 🔗 közvetett (%1$s) Hamarosan további fejlesztések érkeznek! A reakciók hozzáadása az üzenetekhez le van tiltva ebben a csevegésben. Helytelen biztonsági kód! - Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt. + Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta. Új számítógép-alkalmazás! Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (megfigyelő szerepkör) meghívta őt: %1$s @@ -734,16 +734,16 @@ soha (új)]]> Győződjön meg arról, hogy a megadott SMP-kiszolgálók címei megfelelő formátumúak, soronként elkülönítettek, és nincsenek duplikálva. - Az onion-kiszolgálók nem lesznek használva. + Az onion kiszolgálók nem lesznek használva. perc Tudjon meg többet - Új meghívási kérés + Új partneri kapcsolatkérés Csatlakozás a csoporthoz Társított számítógép beállítások meghíva a saját csoporthivatkozásán keresztül elhagyta a csoportot Társított számítógépek - Nincs alkalmazás jelkód + Nincs alkalmazásjelkód Némítás, ha inaktív! A meghívó lejárt! (csak a csoporttagok tárolják) @@ -762,7 +762,7 @@ Az üzenetek végleges törlése le van tiltva ebben a csevegésben. Max 40 másodperc, azonnal fogadható. inkognitó a kapcsolattartási címhivatkozáson keresztül - Onion-kiszolgálók szükségesek a kapcsolódáshoz.\nMegjegyzés: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz. + Onion kiszolgálók szükségesek a kapcsolódáshoz.\nMegjegyzés: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz. Olasz kezelőfelület Nincsenek háttérhívások Üzenetek @@ -774,15 +774,15 @@ Nincsenek szűrt csevegések érvénytelen adat Győződjön meg arról, hogy a megadott WebRTC ICE-kiszolgálók címei megfelelő formátumúak, soronként elkülönítettek, és nincsenek duplikálva. - Csak a csoport tulajdonosai engedélyezhetik a fájlok- és a médiatartalmak küldését. + Csak a csoport tulajdonosai engedélyezhetik a fájlok és a médiatartalmak küldését. Fájl betöltése… Nincs hozzáadandó partner Üzenetvázlat - Függőben lévő meghívó + függőben lévő kapcsolat Egyszer használható meghívó Értesítések Egyszerre csak 10 kép küldhető el - ajánlotta: %s, ekkor: %2s + felajánlotta a következőt: %s: %2s Nem kompatibilis! Tegye priváttá a profilját! Üzenetkézbesítési hiba @@ -801,23 +801,23 @@ ÉLŐ Megjelölés olvasatlanként Továbbiak - Bejelentkezés hitelesítőadatokkal - érvénytelen üzenet-formátum + Bejelentkezés hitelesítési adatokkal + érvénytelen üzenetformátum Csatlakozás Az értesítések az alkalmazás elindításáig nem fognak működni kikapcsolva` (ez az eszköz: v%s)]]> - %s ajánlotta + felajánlotta a következőt: %s Csoport elhagyása %s összes üzenete meg fog jelenni! - Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült visszafejteni az üzenetet, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült. + Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.\n3. A kapcsolat sérült. megfigyelő inkognitó a csoporthivatkozáson keresztül - Onion-kiszolgálók használata, ha azok rendelkezésre állnak. + Onion kiszolgálók használata, ha azok rendelkezésre állnak. Barátok meghívása Menük és figyelmeztetések Tagok meghívása - csatlakozás mint %s + Csatlakozás mint %s Nincs csevegés kijelölve Csak helyi profiladatok inkognitó egy egyszer használható meghívón keresztül @@ -846,7 +846,7 @@ ÜZENETEK ÉS FÁJLOK tag Privát kapcsolat létrehozása - moderálva lett %s által + %s moderálta ezt az üzenetet Győződjön meg arról, hogy a fájl helyes YAML-szintaxist tartalmaz. Exportálja a témát, hogy legyen egy példa a témafájl szerkezetére. dőlt Érvénytelen a fájl elérési útvonala @@ -871,7 +871,7 @@ elutasított hívás Időszakos fogadott, tiltott - Megismétli a meghívási kérést? + Megismétli a kapcsolódási kérést? Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra) Szerepkör SimpleX kapcsolattartási cím @@ -882,13 +882,13 @@ Megnyitás Protokoll időtúllépése titok - Értesítés előnézete + Értesítésekben megjelenő információk várakozás a visszaigazolásra… Fájl megállítása a csoporthivatkozáson keresztül Időtartam a PING-ek között Eltűnő üzenet küldése - Önmegsemmisítő-jelkód + Önmegsemmisítő jelkód Mentés és a csoportprofil frissítése Adatvédelem Profil SimpleX-címe @@ -899,12 +899,12 @@ Frissítés Videó elküldve Az adatbázis jelmondatának módosítása - Alkalmazás beállítások megnyitása + Alkalmazásbeállítások megnyitása A jelkód nem módosult! Frissítés Kijelölés Csak Ön tud hívásokat indítani. - Biztonságos sorba állítás + Biztonságos várólista Értékelje az alkalmazást Egyszer használható meghívó megosztása Hiba történt az adatbázis visszaállításakor @@ -922,8 +922,8 @@ TÉMÁK Túl sok videó! Üdvözöljük! - Önmegsemmisítő-jelkód - (beolvasás, vagy beillesztés a vágólapról) + Önmegsemmisítő jelkód + (beolvasás vagy beillesztés a vágólapról) Várakozás a videóra Válasz Ez a saját egyszer használható meghívója! @@ -931,8 +931,8 @@ Új inkognitóprofil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. SimpleX - Hivatkozás előnézete - a biztonsági kód módosult + Hivatkozások előnézetének megjelenítése + biztonsági kódja módosult Csak a partner nevének megjelenítése Hangszóró bekapcsolva Importált csevegési adatbázis használatához indítsa újra az alkalmazást. @@ -951,7 +951,7 @@ Eltávolítás Keresés Újraegyezteti a titkosítást? - Az önmegsemmisítő-jelkód engedélyezve! + Az önmegsemmisítő jelkód engedélyezve! Biztonsági kiértékelés Cím Üzenet elküldése @@ -961,7 +961,7 @@ Ön egy egyszer használható meghívót osztott meg A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX-hivatkozások pirossal vannak kiemelve. Saját ICE-kiszolgálók - Kapcsolat létrehozása + Ön elfogadta a kapcsolatot Elutasítás Partner nevének és az üzenet tartalmának megjelenítése BEÁLLÍTÁSOK @@ -1023,10 +1023,10 @@ Kapott hivatkozás beillesztése Menti a kiszolgálókat? A SimpleX Chat biztonsága a Trail of Bits által lett auditálva. - frissítette a csoport profilját + frissítette a csoportprofilt SIMPLEX CHAT TÁMOGATÁSA SimpleX Chat szolgáltatás - Nem lehet üzeneteket küldeni! + Ön megfigyelő %s hitelesítve Jelszó a megjelenítéshez Adatvédelem és biztonság @@ -1070,18 +1070,18 @@ tulajdonos Bekapcsolás %s, %s és %s kapcsolódott - Egyszer használható SimpleX-meghívó + Egyszer használható SimpleX meghívó Hívások nem sikerült elküldeni KEZELŐFELÜLET SZÍNEI Adja meg a korábbi jelszót az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem vonható vissza. Másodlagos szín - SOCKS-PROXY + SOCKS PROXY Mentés Újraindítás SMP-kiszolgálók Videó - Automatikus elfogadási beállítások mentése + SimpleX-cím beállításainak mentése Újraegyeztetés Várakozás a videóra Saját XFTP-kiszolgálók @@ -1096,7 +1096,7 @@ Adatbázis-jelmondat beállítása Biztonsági kód megtekintése Feloldja a tag letiltását? - A küldője törölhette a meghívási kérést. + A kérés küldője törölhette a kapcsolódási kérést. Érvénytelen adatbázis-jelmondat Saját SMP-kiszolgálók A kézbesítési jelentések le vannak tiltva @@ -1111,11 +1111,11 @@ %d-s port Kapcsolódás egy hivatkozáson keresztül Cím megosztása - A kiszolgáló QR-kódjának beolvasása + Kiszolgáló QR-kódjának beolvasása Megállítás Megállítja a címmegosztást? Csevegési profilok módosítása - Megismétli a meghívási kérést? + Megismétli a csatlakozási kérést? Várakozás a képre Hangüzenetek Eltávolítja a tagot? @@ -1134,7 +1134,7 @@ Csoport megnyitása Elküldve A hangüzenetek küldése le van tiltva. - Legutóbbi üzenet előnézetének megjelenítése + Legutóbbi üzenetek előnézetének megjelenítése Az előre beállított kiszolgáló címe Időszakos értesítések letiltva! A jelkód módosult! @@ -1173,7 +1173,7 @@ indítás… Leállítás elküldve - SOCKS-proxy használata + SOCKS proxy használata Élő üzenet küldése Újraértelmezett adatvédelem Hangüzenet… @@ -1186,26 +1186,26 @@ Koppintson a Mentés és a partner értesítése Elutasított hívás - SOCKS-proxybeállítások + SOCKS proxybeállítások QR-kód Titkosítás újraegyeztetése Eltávolítás - Onion-kiszolgálók használata + Onion kiszolgálók használata Felfedés Zárolási mód Fájl visszavonása XFTP-kiszolgálók - A fájlok- és a médiatartalmak küldése le van tiltva. + A fájlok és a médiatartalmak küldése le van tiltva. Fájl megosztása… Mentés - egy továbbítókiszolgálón keresztül + továbbítókiszolgálón keresztül Megosztás megállítása Ön eltávolította őt: %1$s Jelmondat mentése és a csevegés megnyitása Menti a beállításokat? - Nincsenek felhasználó-azonosítók. + Nincsenek felhasználói azonosítók. A közvetlen üzenetek küldése a tagok között le van tiltva. - SOCKS-proxy használata? + SOCKS proxy használata? Hangszóró kikapcsolva hét Megjelenítés @@ -1237,7 +1237,7 @@ Ön elhagyta a csoportot Hangüzenet rögzítése SimpleX-zár bekapcsolva - közvetlen üzenet küldése + elküldés a partnernek Beolvasás hordozható eszközről Kapcsolatok hitelesítése Üzenet megosztása… @@ -1257,9 +1257,9 @@ Felfedés Fogadott üzenetbuborék színe Csak a partnere tudja az üzeneteket véglegesen törölni (Ön csak törlésre jelölheti meg azokat). (24 óra) - Az önmegsemmisítő-jelkód módosult! - SimpleX Chat-kiszolgálók használatban. - SimpleX Chat-kiszolgálók használata? + Az önmegsemmisítő jelkód módosult! + SimpleX Chat kiszolgálók használatban. + SimpleX Chat kiszolgálók használata? Csevegési profil felfedése Videók és fájlok legfeljebb 1GB méretig TCP-kapcsolat időtúllépése @@ -1281,12 +1281,12 @@ Visszaállítás alapértelmezettre Hacsak a partnere nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát.\nA kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. videóhívás (nem e2e titkosított) - Alkalmazás új kapcsolatokhoz + Használat új kapcsolatokhoz Az új üzeneteket az alkalmazás időszakosan lekéri – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push-értesítéseket – az eszközről származó adatok nem lesznek elküldve a kiszolgálóknak. Számítógép címének beillesztése a kapcsolattartási címhivatkozáson keresztül a SimpleX a háttérben fut a push értesítések használata helyett.]]> - A partnereinek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nVisszavonhatja ezt a partnerkérelmet és eltávolíthatja a partnert (ezt később ismét megpróbálhatja egy új hivatkozással). + A partnereinek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nVisszavonhatja ezt a kapcsolatot és eltávolíthatja a partnert (ezt később ismét megpróbálhatja egy új hivatkozással). A jelmondat nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonsági mentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. A partnerei továbbra is kapcsolódva maradnak. A kiszolgálónak hitelesítésre van szüksége a feltöltéshez, ellenőrizze jelszavát @@ -1300,12 +1300,12 @@ Az alkalmazás elindításához vagy 30 másodpercnyi háttérben töltött idő után, az alkalmazáshoz való visszatéréshez hitelesítésre lesz szükség. Az üzenet az összes tag számára törölve lesz. A videó nem dekódolható. Próbálja ki egy másik videóval, vagy lépjen kapcsolatba a fejlesztőkkel. - Ez a szöveg a „Beállításokban” érhető el + Ez a szöveg a beállításokban érhető el A profilja el lesz küldve a partnere számára, akitől ezt a hivatkozást kapta. Az alkalmazás 1 perc után bezárható a háttérben. Ön meghívást kapott a csoportba Engedélyezze a következő párbeszédpanelen az azonnali értesítések fogadásához.]]> - A kiszolgálónak engedélyre van szüksége a sorba állítás létrehozásához, ellenőrizze a jelszavát + A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze a jelszavát Kapcsolódni fog a csoport összes tagjához. Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót.\nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. @@ -1323,7 +1323,7 @@ A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. Ön egy egyszer használható meghívót osztott meg inkognitóban Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál. - Később engedélyezheti a „Beállításokban” + Később engedélyezheti a beállításokban Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! különböző átköltöztetés az alkalmazásban/adatbázisban: %s / %s %1$s.]]> @@ -1337,7 +1337,7 @@ A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül. Ön meghívást kapott a csoportba A partnere a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött. - A partnerei és az üzenetek (kézbesítés után) nem a SimpleX-kiszolgálókon vannak tárolva. + A partnerei és az üzenetek (kézbesítés után) nem a SimpleX kiszolgálókon vannak tárolva. Üzenetek formázása a szövegbe szúrt speciális karakterekkel: Megnyitás az alkalmazásban gombra.]]> A csevegési profilja el lesz küldve\na partnere számára @@ -1345,27 +1345,27 @@ %1$s nevű csoporthoz.]]> Amikor az alkalmazás fut Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva - Átvitel-izoláció - Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később! + Átvitelelkülönítés + Akkor lesz kapcsolódva, ha a kapcsolódási kérését elfogadják, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Biztonságos kvantumbiztos protokollon keresztül. - - legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése. + - legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni időkorlát beállítása az üzenetek eltűnéséhez.\n- előzmények szerkesztése. Társítás számítógéppel menüt a hordozható eszköz alkalmazásban és olvassa be a QR-kódot.]]> %s ekkor: %s Akkor lesz kapcsolódva, amikor a partnerének az eszköze online lesz, várjon, vagy ellenőrizze később! Kéretlen üzenetek elrejtése. - Onion-kiszolgálók használata opciót „Nemre”, ha a SOCKS-proxy nem támogatja őket.]]> + Onion kiszolgálók használata opciót „Nemre”, ha a SOCKS proxy nem támogatja őket.]]> Megoszthatja a címét egy hivatkozásként vagy egy QR-kódként – így bárki kapcsolódhat Önhöz. Létrehozás később - A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját. + A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX kiszolgálók nem láthatják a profilját. Ön a következőre módosította %s szerepkörét: „%s” Csoportmeghívó elutasítva - Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. + Adatainak védelme érdekében a SimpleX külön azonosítókat használ minden egyes kapcsolatához. (a megosztáshoz a partnerével) Csoportmeghívó elküldve - Frissíti az átvitel-izoláció módját? - Átvitel-izoláció + Frissíti az átvitelelkülönítési módot? + Átvitelelkülönítés Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak. A csevegési adatbázis nem titkosított – állítson be egy jelmondatot annak védelméhez. Közvetlen internetkapcsolat használata? @@ -1379,8 +1379,8 @@ Ön módosította a címet A partnerei engedélyezhetik a teljes üzenet törlését. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul – nem az eszközön van tárolva. - Ha engedélyezni szeretné a hordozható eszköz-alkalmazás társítását a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, miután engedélyezte azt - A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva. + Ha engedélyezni szeretné a hordozható eszközön lévő alkalmazás társítását a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha az be van kapcsolva + A profilja, a partnerei és az elküldött üzenetei az eszközön vannak tárolva. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Ez a karakterlánc nem egy meghívási hivatkozás! Új csevegés indításához @@ -1395,20 +1395,20 @@ a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet a friss hírekről.]]> Nem kötelező üdvözlőüzenettel. Ismeretlen adatbázishiba: %s - Elrejtheti vagy lenémíthatja a felhasználó-profiljait – koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz. - Inkognitóra váltás kapcsolódáskor. - Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoportot Ön később törli, akkor nem fogja elveszíteni annak tagjait. + Elrejtheti vagy lenémíthatja a felhasználóprofiljait – koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz. + Inkognitó profil használata kapcsolódáskor ki/be. + Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoporthivatkozást később törli, akkor nem fogja elveszíteni a csoport meglévő tagjait. Ön csatlakozott ehhez a csoporthoz %1$s nevű csoporthoz!]]> A hangüzenetek küldése le van tiltva ebben a csevegésben. Ön irányítja csevegését! Kód hitelesítése a számítógépen Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak. - A meghívási kérés el lesz küldve ezen csoporttag számára. - Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. - Már küldött egy meghívási kérést ezen a címen keresztül! + A csatlakozási kérése el lesz küldve ennek a csoporttagnak. + Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt. + Már kért egy kapcsolódási kérést ezen a címen keresztül! Megoszthatja ezt a SimpleX-címet a partnereivel, hogy kapcsolatba léphessenek vele: %s. - Amikor az emberek meghívót küldenek, Ön elfogadhatja vagy elutasíthatja azokat. + Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! Köszönet a felhasználóknak a Weblate-en való közreműködésért! A kézbesítési jelentések küldése az összes partnere számára engedélyezve lesz. @@ -1435,28 +1435,28 @@ A kézbesítési jelentések le vannak tiltva %d csoportban Néhány nem végzetes hiba történt az importáláskor: Köszönet a felhasználóknak a Weblate-en való közreműködésért! - A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet. + A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét. Beállítás a rendszer-hitelesítés helyett. - A fogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása a feladó online állapotba kerülése után fejeződik be. + Az üzenetfogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása akkor fejeződik be, amikor az üzenetküldési kiszolgáló online lesz. A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni. Jelmondat mentése a Keystore-ba Köszönet a felhasználóknak a Weblate-en való közreműködésért! Jelmondat mentése a beállításokban Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem lesznek elküldve. A második jelölés, amit kihagytunk! ✅ - A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát. + A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát. Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. A mentett WebRTC ICE-kiszolgálók el lesznek távolítva. A kézbesítési jelentések engedélyezve vannak %d csoportban A tag szerepköre a következőre fog módosulni: „%s”. A csoport összes tagja értesítést fog kapni. Profil és kiszolgálókapcsolatok Egy üzenetváltó- és alkalmazásplatform, amely védi az adatait és biztonságát. - A profil aktiválásához koppintson az ikonra. + Koppintson ide a profil aktiválásához. A kézbesítési jelentések le vannak tiltva %d partnernél Munkamenet kód Köszönet a felhasználóknak a Weblate-en való közreműködésért! Kis csoportok (max. 20 tag) - Az Ön által elfogadott kérelem vissza lesz vonva! + Az Ön által elfogadott kapcsolat vissza lesz vonva! Élő üzenet küldése – az üzenet a címzett(ek) számára valós időben frissül, ahogy Ön beírja az üzenetet A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI A következő üzenet azonosítója érvénytelen (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. @@ -1475,33 +1475,33 @@ Használati útmutatóban olvasható.]]> A jelmondat a beállításokban egyszerű szövegként van tárolva. Konzol megjelenítése új ablakban - Az előző üzenet hasítóértéke különbözik. + Az előző üzenet kivonata különbözik. Ezek a beállítások csak a jelenlegi csevegési profiljára vonatkoznak Várjon, amíg a fájl betöltődik a társított hordozható eszközről - GitHub tárolónkban.]]> + GitHub-tárolónkban.]]> Hiba történt a tartalom megjelenítésekor Hiba történt az üzenet megjelenítésekor - Láthatóvá teheti a SimpleXbeli partnerei számára a „Beállításokban”. + Láthatóvá teheti a SimpleXbeli partnerei számára a beállításokban. Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára. - A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás. + A beolvasott QR-kód nem egy SimpleX-hivatkozás. A beillesztett szöveg nem egy SimpleX-hivatkozás. A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél. Elindítja a csevegést? Látható előzmények - Alkalmazás jelkód + Alkalmazásjelkód Partner hozzáadása Koppintson ide a QR-kód beolvasásához Koppintson ide a hivatkozás beillesztéséhez Partner hozzáadása: új meghívási hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]> Csoport létrehozása: új csoport létrehozásához.]]> - A csevegés megállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés elindítása előtt. + A csevegés megállt. Ha ezt az adatbázist már használta egy másik eszközön, akkor a csevegés elindítása előtt vissza kell állítania azt. Az előzmények nem lesznek elküldve az új tagok számára. Újrapróbálkozás A kamera nem elérhető Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára. Az előzmények ne legyenek elküldve az új tagok számára. Vagy mutassa meg ezt a kódot - Kamera hozzáférés engedélyezése + Kamera-hozzáférés engedélyezése Megtartja a fel nem használt meghívót? Ennek az egyszer használható meghívónak a megosztása Új csevegés @@ -1543,14 +1543,14 @@ Fejlesztői beállítások A funkció végrehajtása túl sokáig tart: %1$d másodperc: %2$s %s hordozható eszköz elfoglalt]]> - (Már nem tag) %1$s + %1$s ismeretlen vagy már nem tag ismeretlen állapot %1$s a következőre módosította a nevét: %2$s eltávolította a kapcsolattartási címet eltávolította a profilképét új kapcsolattartási címet állított be új profilképet állított be - frissített profil + frissítette a profilját %1$s a következőre módosította a nevét: %2$s Privát jegyzetek Hiba történt a privát jegyzetek törlésekor @@ -1561,7 +1561,7 @@ Létrehozva: %s Az összes üzenet törölve lesz – ez a művelet nem vonható vissza! Továbbfejlesztett üzenetkézbesítés - Csatlakozás csoportos beszélgetésekhez + Csatlakozás a csoportbeszélgetésekhez Hivatkozás beillesztése a kapcsolódáshoz! Privát jegyzetek A keresősáv elfogadja a meghívási hivatkozásokat. @@ -1624,7 +1624,7 @@ Jelmondat beállítása Kép a képben hívások Biztonságosabb csoportok - Használja az alkalmazást hívás közben. + Alkalmazás használata hívás közben. Vagy az archívum hivatkozásának beillesztése Archívum hivatkozásának beillesztése Letöltés ismét @@ -1664,8 +1664,8 @@ Ez a csevegés végpontok közötti titkosítással védett. Átköltöztetési párbeszédablak megnyitása Ez a csevegés végpontok közötti kvantumbiztos titkosítással védett. - végpontok közötti titkosítással, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.]]> - végpontok közötti kvantumbiztos titkosítással, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.]]> + végpontok közötti titkosítással, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.]]> + végpontok közötti kvantumbiztos titkosítással, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.]]> Hiba történt az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel. Keresse meg ezt az engedélyt az Android beállításaiban, és adja meg kézzel. Engedélyezés a beállításokban @@ -1691,15 +1691,15 @@ A hangüzenetek küldése le van tiltva A SimpleX-hivatkozások küldése le van tiltva. A SimpleX-hivatkozások küldése le van tiltva - A fájlok- és médiatartalmak nincsenek engedélyezve + A fájlok és a médiatartalmak küldése nincs engedélyezve A SimpleX-hivatkozások küldése engedélyezve van. Számukra engedélyezve mentett - elmentve innen: %s + mentve innen: %s Továbbítva innen A címzett(ek) nem látja(k), hogy kitől származik ez az üzenet. Mentett - Elmentve innen + Mentve innen Letöltés Továbbítás Továbbított @@ -1741,7 +1741,7 @@ Igen NE használjon privát útválasztást. Privát útválasztás - Használjon privát útválasztást ismeretlen kiszolgálókkal. + Privát útválasztás használata az ismeretlen kiszolgálókkal. Mindig használjon privát útválasztást. Üzenet-útválasztási mód Közvetlen üzenetküldés, ha az IP-cím védett és a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. @@ -1749,14 +1749,14 @@ Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. Üzenet-útválasztási tartalék PRIVÁT ÜZENET-ÚTVÁLASZTÁS - Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + Privát útválasztás használata az ismeretlen kiszolgálókkal, ha az IP-cím nem védett. NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. - Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára. + Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára. FÁJLOK IP-cím védelme - Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS-proxy engedélyezve van). + Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS proxy engedélyezve van). Ismeretlen kiszolgálók! - Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára:\n%1$s. + Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára:\n%1$s. Összes színmód Fekete Színmód @@ -1767,7 +1767,7 @@ Jó napot! Jó reggelt! Speciális beállítások - Alkalmazás erre + Használat ehhez Csevegés színei Csevegés témája Kitöltés @@ -1786,7 +1786,7 @@ Háttérkép kiemelőszíne Háttérkép háttérszíne További kiemelőszín 2 - Alkalmazás téma + Alkalmazás témája Perzsa kezelőfelület Védje az IP-címét a partnerei által kiválasztott üzenetváltási továbbítókiszolgálókkal szemben.\nEngedélyezze a *Hálózat és kiszolgálók* menüben. Ismeretlen kiszolgálókról származó fájlok megerősítése. @@ -1799,10 +1799,10 @@ Csökkentett akkumulátor-használattal. Hiba történt a WebView előkészítésekor. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel.\nHiba: %s Felhasználó által létrehozott téma visszaállítása - Üzenetsorbaállítási információ + Üzenet várólista információi nincs Kézbesítési hibák felderítése - a kiszolgáló sorbaállítási információi: %1$s\n\nutoljára fogadott üzenet: %2$s + a kiszolgáló várólista információi: %1$s\n\nutoljára fogadott üzenet: %2$s Érvénytelen kulcs vagy ismeretlen fájltöredékcím – valószínűleg a fájl törlődött. Ideiglenes fájlhiba Üzenet állapota @@ -1877,7 +1877,7 @@ Feltöltési hibák Visszaigazolt Visszaigazolási hibák - próbálkozások + kísérletek Törölt töredékek Összes profil Feltöltött töredékek @@ -1909,7 +1909,7 @@ A kiszolgáló verziója nem kompatibilis az alkalmazással: %1$s. Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál. Aktív kapcsolatok száma - Üzenetjelentés + Üzenetfogadás Feliratkozva Feliratkozási hibák Mellőzött feliratkozások @@ -1964,7 +1964,7 @@ Beszélgetés megtartása Biztosan törli a partnert? kapcsolódás - Könnyen elérhető alkalmazás-eszköztárak + Könnyen elérhető eszköztárak Törlés értesítés nélkül Beállítások keresés @@ -1988,26 +1988,26 @@ Kapcsolódjon gyorsabban a partnereihez. Folytatás Ellenőrizze a hálózatát - Média- és fájlkiszolgálók + Fájl- és médiakiszolgálók Legfeljebb 20 üzenet egyszerre való törlése. Védi az IP-címét és a kapcsolatait. - Könnyen elérhető eszköztár + Könnyen elérhető csevegési eszköztár Üzenetkiszolgálók - SOCKS-proxy + SOCKS proxy Néhány fájl nem lett exportálva Az exportált adatbázist átköltöztetheti. Mentés és újrakapcsolódás - Használja az alkalmazást egy kézzel. + Alkalmazás egy kézzel való használata. A partnerek archiválása a későbbi csevegéshez. TCP-kapcsolat - Az exportált archívumot elmentheti. + Mentheti az exportált archívumot. Tippek visszaállítása - Csevegési lista átváltása: + Csevegési lista ki/be: Ezt a „Megjelenés” menüben módosíthatja. Új médiabeállítások Lejátszás a csevegési listából. Elhomályosítás a jobb adatvédelemért. - Automatikus alkalmazás-frissítés + Automatikus alkalmazásfrissítés Létrehozás Új verziók letöltése a GitHubról. Betűméret növelése. @@ -2017,7 +2017,7 @@ Érvénytelen hivatkozás Ellenőrizze, hogy a SimpleX-hivatkozás helyes-e. Hiba történt a profilváltáskor - A kapcsolata át lett helyezve ide: %s, de egy váratlan hiba történt a profilra való átirányításkor. + A kapcsolata át lett helyezve ide: %s, de egy hiba történt a profilváltáskor. Az üzenetek törölve lesznek – ez a művelet nem vonható vissza! Eltávolítja az archívumot? A feltöltött adatbázis-archívum véglegesen el lesz távolítva a kiszolgálókról. @@ -2025,16 +2025,16 @@ Profil megosztása Rendszerbeállítások használata Csevegési profil kijelölése - Ne használja a hitelesítőadatokat proxyval. - Különböző proxy-hitelesítőadatok használata az összes profilhoz. - Különböző proxy-hitelesítőadatok használata az összes kapcsolathoz. + Ne használja a hitelesítési adatokat proxyval. + Különböző proxy-hitelesítési adatok használata az összes profilhoz. + Különböző proxy-hitelesítési adatok használata az összes kapcsolathoz. Jelszó Felhasználónév - A hitelesítőadatai titkosítatlanul is elküldhetők. + A hitelesítési adatai titkosítatlanul is elküldhetők. Hiba történt a proxy mentésekor Győződjön meg arról, hogy a proxy konfigurációja helyes. Proxyhitelesítés - Véletlenszerű hitelesítőadatok használata + Véletlenszerű hitelesítési adatok használata %1$d egyéb fájlhiba. Nincs mit továbbítani! %1$d fájl letöltése még folyamatban van. @@ -2057,9 +2057,9 @@ Üzenetbuborék alakja Farok Kiszolgáló - Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni. + Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítési adatokat fog használni. Alkalmazás munkamenete - Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva. + Az összes kiszolgálóhoz új, SOCKS-hitelesítési adatok lesznek használva. Kattintson a címmező melletti info gombra a mikrofon használatának engedélyezéséhez. Nyissa meg a Safari Beállítások / Weboldalak / Mikrofon menüt, majd válassza a helyi kiszolgálók engedélyezése lehetőséget. Hívások kezdeményezéséhez engedélyezze a mikrofon használatát. Fejezze be a hívást, és próbálja meg a hívást újra. @@ -2072,11 +2072,11 @@ Hang/Videó váltása hívás közben. Csevegési profilváltás az egyszer használható meghívókhoz. Továbbfejlesztett biztonság ✅ - A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva. + A SimpleX protokollokat a Trail of Bits auditálta. Hiba történt a kiszolgálók mentésekor - Nincsenek üzenet-kiszolgálók. + Nincsenek üzenetkiszolgálók. Nincsenek üzenetfogadási kiszolgálók. - Nincsenek média- és fájlkiszolgálók. + Nincsenek fájl- és médiakiszolgálók. A(z) %s nevű csevegési profilhoz: Cím vagy egyszer használható meghívó? Új kiszolgáló @@ -2085,7 +2085,7 @@ Üzemeltető Feltételek megtekintése Nincsenek kiszolgálók a privát üzenet-útválasztáshoz. - Nincsenek fájlküldő-kiszolgálók. + Nincsenek fájlküldési kiszolgálók. Nincsenek fájlfogadási kiszolgálók. Hibák a kiszolgálók konfigurációjában. Hiba történt a feltételek elfogadásakor @@ -2109,7 +2109,7 @@ Kiszolgálóüzemeltetők Hálózatüzemeltetők Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ. - Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. + Például, ha a partnere egy SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux kiszolgálón keresztül fogja azokat kézbesíteni. Jelölje ki a használni kívánt hálózatüzemeltetőket. Felülvizsgálat később A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja. @@ -2146,11 +2146,11 @@ Hozzáadott üzenetkiszolgálók Használat a fájlokhoz A küldéshez - Hozzáadott média- és fájlkiszolgálók + Hozzáadott fájl- és médiakiszolgálók Feltételek megnyitása Módosítások megtekintése Hiba történt a kiszolgáló frissítésekor - A kiszolgáló-protokoll módosult. + A kiszolgálóprotokoll módosult. A kiszolgáló üzemeltetője módosult. Kiszolgáló-üzemeltető Kiszolgáló hozzáadva a következő üzemeltetőhöz: %s. @@ -2160,7 +2160,7 @@ Hálózati decentralizáció A második előre beállított üzemeltető az alkalmazásban! A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a metaadatok jobb védelme érdekében. - Alkalmazás-eszköztárak + Eszköztárak a metaadatok jobb védelme érdekében. Javított csevegési navigáció - Csevegés megnyitása az első olvasatlan üzenetnél.\n- Ugrás az idézett üzenetekre. @@ -2201,11 +2201,11 @@ Csökkentse az üzenet méretét, és küldje el újra. Üzenetek ellenőrzése 10 percenként Az üzenet túl nagy! - Csökkentse az üzenet méretét vagy távolítsa el a médiát, és küldje el újra. + Csökkentse az üzenet méretét vagy távolítsa el a médiatartalmakat, és küldje el újra. A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben. Amikor egynél több üzemeltető van engedélyezve, akkor egyik sem rendelkezik olyan metaadatokkal, amelyekből megtudható, hogy ki kivel kommunikál. elfogadott meghívó - Függőben lévő meghívási kérelem + függőben lévő kapcsolat Az üzemeltetőkről A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba. A titkosítás újraegyeztetése folyamatban van. @@ -2256,7 +2256,7 @@ moderátor Közösségi irányelvek megsértése Kéretlen tartalom jelentése: csak a csoport moderátorai látják. - Csak a küldő és a moderátorok látják + Csak az üzenet küldője és a moderátorok látják Csak Ön és a moderátorok látják Jelentés indoklása? Kéretlen tartalom @@ -2291,7 +2291,7 @@ Az ebben a csevegésben lévő üzenetek soha nem lesznek törölve. 1 év alapértelmezett (%s) - Csevegési üzenetek törlése a saját eszközéről. + Csevegési üzenetek törlése az eszközről. Módosítja az automatikus üzenettörlést? Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből. A következő TCP-port használata, amikor nincs port megadva: %1$s. @@ -2342,10 +2342,9 @@ Elfogadás A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. Adatvédelmi szabályzat és felhasználási feltételek. - A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára. + A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. Kiszolgálóüzemeltetők beállítása Nem támogatott kapcsolattartási hivatkozás - Rövid hivatkozások használata (béta) Rövid hivatkozás Teljes hivatkozás Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől. @@ -2354,4 +2353,153 @@ Kikapcsolva Előre beállított kiszolgálók A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz. + Hiba a tag befogadásakor + %d csevegés a tagokkal + %d üzenet + 1 csevegés egy taggal + %d csevegés + A jelentés el lett küldve a moderátoroknak + A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben. + függőben lévő áttekintés + áttekintés + Csevegés az adminisztrátorokkal + Csevegés a tagokkal + Tagbefogadás + Nincsenek csevegések a tagokkal + Tagok áttekintése + Tagok áttekintése a befogadás előtt (kopogtatás). + Csevegés az adminisztrátorokkal + A tag csatlakozni akar a csoporthoz, befogadja a tagot? + Eltávolítás + Befogadás + Tag befogadása + összes + Csevegés a taggal + Új tag szeretne csatlakozni a csoporthoz. + kikapcsolva + Befogadás megfigyelőként + Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérését. + befogadta Önt + Tagbefogadás beállítása + Menti a befogadási beállításokat? + Ön befogadta ezt a tagot + Befogadás tagként + befogadta őt: %1$s + áttekintve a moderátorok által + nem lehet üzeneteket küldeni + partner letiltva + csoport törölve + eltávolítva a csoportból + csatlakozási kérés elutasítva + Ön elhagyta a csoportot + a tag régi verziót használ + Hiba a csevegés törlésekor + Ön nem tud üzeneteket küldeni! + a partner nem áll készen + nincs szinkronizálva + Törli a taggal való csevegést? + a partnere elhagyta a csevegést + Csevegés törlése + Elutasítás + Elutasítja a tagot? + Cím frissítése + Csevegés megnyitása + Új csevegés megnyitása + Új csoport megnyitása + végpontok közötti titkosítással vannak védve.]]> + Hiba történt a partneri kapcsolatkérés elutasításakor + Hiba a csevegés megnyitásakor + Hiba a csoport megnyitásakor + Hiba a profil módosításakor + Megnyitás a csatlakozáshoz + Megnyitás a kapcsolódáshoz + Megnyitás az elfogadáshoz + a partnernek el kell fogadnia… + Csatlakozás a csoporthoz + Üzenet hozzáadása + Kapcsolódás + Elküldi a partneri kapcsolatkérést? + miután a kérését elfogadták.]]> + Kérés küldése üzenet nélkül + Kérés küldése + kérés elküldve + Partneri kapcsolatkérés elfogadása + Partneri kapcsolatkérés elutasítása + A kérés küldője NEM lesz értesítve. + Saját profil + Elküldés a partnernek a kapcsolódást követően. + Üdvözlőüzenet + Frissíti a címet? + A cím rövid lesz és a profil meg lesz osztva a címen keresztül. + Frissítés + Frissíti a csoporthivatkozást? + Nem lehet módosítani a profilt + Másik profil használatához a kapcsolatfelvételi kísérlet után törölje a csevegést, és használja újra a hivatkozást. + Csevegés az adminisztrátorokkal + Csevegés a tagokkal mielőtt csatlakoznának. + Gyorsabb kapcsolódás! 🚀 + Kevesebb adatforgalom a mobilhálózatokon. + Az üzenet azonnal megjelenik, amint a kapcsolódás gombra koppint. + Új szerepkör: Moderátor + Nincs privát útválasztási munkamenet + Privát útválasztás időtúllépése + Protokoll időtúllépése a háttérben + Üzenetek eltávolítása és a tagok tiltása. + Csoporttagok áttekintése + Küldjön privát visszajelzést a csoportoknak. + TCP-kapcsolat időtúllépése a háttérben + Profil betöltése… + Rövid leírás: + Saját névjegy: + Névjegy: + A névjegy túl hosszú + A leírás túl hosszú + Partneri kapcsolatkérés elfogadása + Üzleti kapcsolat + Csoport + Koppintson a „Csatlakozás a csoporthoz” gombra + Saját csoport + Üzleti partner + Partner + Koppintson a „Kapcsolódás” gombra a csevegéshez + Koppintson a „Kapcsolódás” gombra a kérés elküldéséhez + Az üzeneteltűnési idő csak az új partnerekre vonatkozik. + Inkognitóprofil használata + Saját cím létrehozása + Eltűnő üzenetek engedélyezése alapértelmezetten. + Tartsa tisztán a csevegéseit + Névjegy és üdvözlőüzenet beállítása a profilokhoz. + Saját cím megosztása + Rövid SimpleX-cím + Cím frissítése + Üdvözölje a partnereit 👋 + 4 új kezelőfelületi nyelv + Katalán, indonéz, román és vietnami – köszönjük felhasználóinknak! + Csoporthivatkozás frissítése + A hivatkozás rövid lesz és a csoportprofil meg lesz osztva a hivatkozáson keresztül. + Régi cím megosztása + Régi hivatkozás megosztása + PARTNERI KAPCSOLATKÉRÉSEK A CSOPORTOKBÓL + A tag törölve lett – nem lehet elfogadni a kérést + a(z) %1$s nevű csoportból partneri kapcsolatot kért + Ez a beállítás a jelenlegi profiljára vonatkozik + Megnyitás a bot használatához + Koppintson a „Kapcsolódás” gombra a bot használatához + Bot + A parancsok küldéséhez kapcsolódva kell lennie. + A fájlok és a médiatartalmak küldése engedélyezve van a partnerei számára. + A fájlok és a médiatartalmak küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. + A fájlok és a médiatartalmak küldése le van tiltva. + Mindkét fél küldhet fájlokat és médiatartalmakat. + Csak Ön küldhet fájlokat és médiatartalmakat. + Csak a partnere küldhet fájlokat és médiatartalmakat. + A fájlok és a médiatartalmak küldése le van tiltva ebben a csevegésben. + Elavult beállítások + Tiszta hivatkozás megnyitása + Teljes hivatkozás megnyitása + Nyomonkövetési paraméterek eltávolítása a hivatkozásokból + SimpleX továbbítókiszolgáló-hivatkozás + Hiba a csevegés olvasottként való megjelölésekor + A célkiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s. + A továbbítókiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_circle_right.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_circle_right.svg new file mode 100644 index 0000000000..f5c27f5c9e --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_circle_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_person.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_person.svg new file mode 100644 index 0000000000..adfc09bd4a --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chat_person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_down.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_down.svg new file mode 100644 index 0000000000..b8416ff173 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_up.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_up.svg new file mode 100644 index 0000000000..ca6a17bbde --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_contact_support.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_contact_support.svg new file mode 100644 index 0000000000..b06e925603 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_contact_support.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cube.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cube.svg new file mode 100644 index 0000000000..252afc1acc --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_cube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_filled.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_filled.svg new file mode 100644 index 0000000000..928f1de9b2 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_add_filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_waving_hand.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_waving_hand.svg new file mode 100644 index 0000000000..28781faa07 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_waving_hand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index a269149e99..55b108cfb9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -105,13 +105,13 @@ Periksa apakah tautan SimpleX sudah benar. Anda terhubung ke server yang digunakan untuk menerima pesan dari kontak ini. Mencoba menyambung ke server yang digunakan untuk menerima pesan dari kontak ini (error: %1$s). - Migrasi basis data sedang berlangsung, + Migrasi basis data sedang berlangsung, \nmemerlukan waktu beberapa menit. menghubungkan Lokasi file tidak valid Tampilan macet Anda membagikan lokasi file yang tidak valid. Laporkan masalah ini ke pengembang aplikasi. - error + galat Tautan sekali %1$d pesan yang terlewati %1$d pesan yang dilewati @@ -341,7 +341,7 @@ %1$d berkas lainnya gagal. Kontak sudah ada menghubungkan… - kirim pesan langsung + kirim untuk terhubung Anda tidak memiliki obrolan Memuat obrolan… Ketuk untuk Hubungkan @@ -434,7 +434,7 @@ Panggilan berlangsung Menghubungkan panggilan Pesan yang terlewati - Hash dari pesan sebelumnya berbeda.\" + Hash dari pesan sebelumnya berbeda. Privasi & keamanan Enkripsi berkas lokal Terima gambar otomatis @@ -474,7 +474,7 @@ Izin Anda Izin kontak bawaan (%s) - Pesan sementara + Pesan menghilang Pesan pribadi Pesan suara Setel preferensi grup @@ -664,7 +664,7 @@ Kirim pesan suara tidak diizinkan. Hingga 100 pesan terakhir dikirim ke anggota baru. Pesan suara - Pesan sementara + Pesan menghilang Pesan langsung Profil obrolan tersembunyi Moderasi grup @@ -692,7 +692,7 @@ Kode QR tidak valid Kesalahan saat mengganti profil Ketuk untuk tempel tautan - Koneksi Anda dipindahkan ke %s tetapi terjadi kesalahan tak terduga saat mengarahkan Anda ke profil. + Koneksi Anda dipindahkan ke %s tetapi gagal saat mengarahkan Anda ke profil. Kirim kami email Kirim pertanyaan dan ide Kunci SimpleX @@ -793,7 +793,7 @@ Teks ini tersedia di pengaturan Ketuk untuk memulai obrolan baru Anda diundang ke grup - gabung sebagai %s + Gabung sebagai %s menghubungkan… Tidak dapat kirim pesan Bagikan pesan… @@ -1339,7 +1339,7 @@ Ketentuan diterima pada: %s. Ketentuan akan diterima pada: %s. Koneksi - Rol + Peran Ganti rol Profil obrolan Anda akan dikirim ke anggota grup Profil obrolan Anda akan dikirim ke anggota obrolan @@ -1359,7 +1359,7 @@ kode keamanan berubah %d kontak dipilih Tautan grup - Anggota lama %1$s + Anggota %1$s enkripsi end-to-end standar enkripsi e2e quantum resistant menghubungkan (undangan perkenalan) @@ -1393,7 +1393,7 @@ Ketentuan akan diterima untuk operator yang diaktifkan setelah 30 hari. Alat pengembang Warna obrolan - terhubung langsung + permintaan koneksi Koneksi ke desktop dalam kondisi buruk Konfirmasi hapus kontak? Waktu kustom @@ -1881,7 +1881,7 @@ Alamat penerima akan diubah ke server lain. Perubahan alamat akan selesai setelah pengirim online. Minta kontak Anda untuk aktifkan kirim pesan suara. gambar profil - Simpan pengaturan terima otomatis + Simpan pengaturan alamat SimpleX Catatan diperbarui pada: %s dtk Alamat desktop salah @@ -2143,7 +2143,7 @@ Frase sandi salah! Anda telah meminta koneksi melalui alamat ini! Hentikan obrolan - Anda tidak dapat kirim pesan! + Anda adalah pengamat Pesan suara (%1$s) Lihat kode keamanan Anda perlu mengizinkan kontak mengirim pesan suara agar dapat mengirimkannya. @@ -2356,7 +2356,151 @@ Tautan lengkap Tautan ini perlu versi aplikasi yang baru. Harap perbarui aplikasi atau minta kontak untuk kirim tautan kompatibel. Tautan saluran SimpleX - Gunakan tautan singkat (BETA) Tautan koneksi tidak didukung Tautan singkat + gagal mengirim pesan + tinjau + sedang ditinjau + Penerimaan anggota + Mengobrol dengan anggota + Tidak ada obrolan dengan anggota + Terima sebagai anggota + Terima sebagai pengamat + Ada kesalahan saat menerima anggota + 1 obrolan dengan anggota + %d obrolan + %d mengobrol dengan anggota + %d pesan + Anda dapat meninjau laporan anda di Obrolan dengan pengurus. + grup telah dihapus + Silakan tunggu pengurus grup untuk meninjau permintaanmu untuk bergabung ke grup + ditinjau oleh pengurus + Tingkatkan tautan + menerimamu + diterima %1$s + Atur penerimaan anggota + Tinjau anggota + Mengobrol dengan pengurus + Hapus obrolan + Hapus obrolan dengan anggota? + luring + Tolak + Tinjau anggota sebelum menerima (mengetuk) + Mengobrol dengan pengurus + semua + Ada kesalahan saat menghapus obrolan dengan anggota + Laporan telah dikirim ke pengurus + Anda tidak dapat mengirim pesan! + kontak telah dihapus + kontak tidak siap + kontak telah dinonaktifkan + tidak diselaraskan + permintaan bergabung ditolak + anggota menggunakan versi lama + dihapus dari grup + Anda keluar + Simpan pengaturan penerimaan? + Anggota baru ingin bergabung ke grup. + kamu menerima anggota ini + Mengobrol dengan anggota + Terima + Terima anggota + Anggota akan bergabung ke grup, terima? + Tolak anggota? + 4 bahasa antarmuka baru + Terima permintaan kontak + Terima permintaan kontak + Tambahkan pesan + Izinkan berkas dan media jika kontak Anda mengizinkan. + Izinkan kontak Anda mengirim berkas dan media. + Bio: + Bio terlalu panjang + Bot + Anda dan kontak Anda dapat mengirim berkas dan media. + Koneksi bisnis + Tak dapat ubah profil + Bahasa Katalan, Indonesia, Rumania, dan Vietnam - terima kasih kepada pengguna kami! + enkripsi end-to-end.]]> + hanya setelah permintaan Anda diterima.]]> + Obrolan dengan admin + Chat dengan anggota sebelum mereka bergabung. + Hubungkan + Terhubung lebih cepat! 🚀 + PERMINTAAN KONTAK DARI GRUP + kontak harus menerima… + Buat alamat Anda + Opsi tidak berlaku + Deskripsi terlalu panjang + Aktifkan pesan menghilang secara default. + Gagal mengubah profil + Gagal membuka obrolan + Gagal membuka grup + Gagal menolak permintaan kontak + Berkas dan media dilarang dalam obrolan ini. + Grup + Gabung grup + Membersihkan obrolan Anda + Lalu lintas jaringan seluler lebih sedikit. + Memuat profil… + Anggota dihapus - tidak dapat menerima permintaan + Kirim pesan instan setelah tekan Hubungkan. + Peran grup baru: Moderator + Tidak ada sesi routing pribadi + Hanya Anda yang dapat kirim berkas dan media. + Hanya kontak Anda yang dapat kirim berkas dan media. + Buka obrolan + Buka tautan bersih + Buka tautan + Buka obrolan baru + Buka grup baru + Buka untuk terima + Buka untuk hubungkan + Buka untuk gabung + Buka untuk gunakan bot + Routing pribadi terlalu lama + Larang mengirim berkas dan media. + Latar belakang protokol terlalu lama + Tolak permintaan kontak + Hapus tautan pelacak + Menghapus pesan dan memblokir anggota. + permintaan koneksi dari grup %1$s + permintaan dikirim + Tinjau anggota grup + Kirim permintaan kontak? + Kirim permintaan + Kirim permintaan tanpa pesan + Kirimkan masukan pribadi Anda ke grup. + Dikirim ke kontak Anda setelah terhubung. + Pasang bio profil dan pesan sambutan. + Bagikan alamat lama + Bagikan tautan lama + Bagikan alamat Anda + Deskripsi singkat: + Alamat singkat SimpleX + Tautan relay SimpleX + Sambut kontak Anda 👋 + Bio Anda: + Kontak bisnis Anda + Kontak Anda + Grup Anda + Profil Anda + Ketuk Hubungkan untuk chat + Tekan Hubungkan untuk kirim permintaan + Tekan Hubungkan untuk gunakan bot + Tekan Gabung grup + Batas waktu koneksi TCP bg + Alamat akan singkat, dan profil Anda akan dibagikan melalui alamat tersebut. + Tautan akan singkat, dan profil grup akan dibagikan melalui tautan tersebut. + Pengirim TIDAK akan diberi tahu. + Pengaturan ini untuk profil Anda saat ini + Waktu menghilang hanya diatur untuk kontak baru. + Untuk mengirim perintah, Anda harus terhubung. + Untuk menggunakan profil lain setelah mencoba hubungkan, hapus chat dan gunakan tautan lagi. + Perbarui alamat Anda + Tingkatkan + Tingkatkan alamat? + Tingkatkan tautan grup + Tingkatkan tautan grup? + Gunakan profil samaran + Pesan sambutan diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 6c086835ea..35b0bab541 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -71,7 +71,7 @@ Errore di eliminazione del gruppo Errore di eliminazione della richiesta di contatto Errore di eliminazione della connessione del contatto in attesa - Errore di modifica dell\'indirizzo + Errore cambiando l\'indirizzo Test fallito al passo %s. Il server richiede l\'autorizzazione di creare code, controlla la password Connetti @@ -146,8 +146,8 @@ Benvenuto/a! Questo testo è disponibile nelle impostazioni Сhat - sei stato invitato in un gruppo - entra come %s + Sei stato/a invitato/a in un gruppo + Entra come %s in connessione… Tocca per iniziare una conversazione Scrivi agli sviluppatori @@ -513,7 +513,7 @@ Attivare l\'eliminazione automatica dei messaggi\? Crittografare il database\? Crittografare - Errore nella modifica dell\'impostazione + Errore cambiando l\'impostazione Errore nella crittografia del database Le tue impostazioni Verrai connesso/a al gruppo quando il dispositivo dell\'host del gruppo sarà in linea, attendi o controlla più tardi! @@ -940,7 +940,7 @@ Errore nell\'aggiornamento del link del gruppo osservatore Contatta l\'amministratore del gruppo. - Non puoi inviare messaggi! + sei un osservatore Sistema Aggiungi messaggio di benvenuto Messaggio di benvenuto @@ -1115,7 +1115,7 @@ Ciao! \nConnettiti a me tramite SimpleX Chat: %s Invita amici - Salva le impostazioni di accettazione automatica + Salva le impostazioni dell\'indirizzo SimpleX Puoi crearlo più tardi Condividi indirizzo Inserisci il messaggio di benvenuto… @@ -1379,8 +1379,8 @@ Apri Errore di creazione del contatto Invia messaggio diretto per connetterti - invia messaggio diretto - si è connesso/a direttamente + invia per connettere + connessione richiesta Espandi Ripetere la richiesta di connessione? contatto eliminato @@ -1569,7 +1569,7 @@ Il desktop è inattivo Il desktop è stato disconnesso Tempo scaduto durante la connessione al desktop - Membro passato %1$s + Membro %1$s L\'esecuzione della funzione impiega troppo tempo: %1$d secondi: %2$s Funzione lenta Mostra chiamate API lente @@ -2063,7 +2063,7 @@ Rimuovere l\'archivio? I messaggi verranno eliminati. Non è reversibile! L\'archivio del database caricato verrà rimosso definitivamente dai server. - La tua connessione è stata spostata a %s, ma si è verificato un errore imprevisto durante il reindirizzamento al profilo. + La tua connessione è stata spostata a %s, ma si è verificato un errore cambiando il profilo. Non usare credenziali con proxy. Assicurati che la configurazione del proxy sia corretta. Autenticazione del proxy @@ -2134,7 +2134,7 @@ Operatori del server Seleziona gli operatori di rete da usare. Continua - Aggiorna + Aggiornamento Leggi più tardi Server preimpostati Condizioni accettate @@ -2386,9 +2386,154 @@ Link breve Link del canale SimpleX Link di connessione non supportato - Usa link brevi (BETA) Tutti i server Off Server preimpostati Usa la porta TCP 443 solo per i server preimpostati. + 1 chat con un membro + %d chat + %d chat con membri + %d messaggi + Salvare le impostazioni di ammissione? + ha accettato %1$s + ti ha accettato/a + Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo. + hai accettato questo membro + revisiona + Ammissione dei membri + Nessuna chat con membri + off + Revisiona i membri + Revisiona i membri prima di ammetterli (bussare). + Accetta + Chat con amministratori + Rimuovi + Il membro entrerà nel gruppo, accettarlo? + revisionato dagli amministratori + Accetta membro + Chatta con gli amministratori + Accetta come osservatore + Un nuovo membro vuole entrare nel gruppo. + tutti + Chatta con il membro + Chat con membri + Errore di accettazione del membro + Accetta come membro + Imposta l\'ammissione dei membri + Segnalazione inviata ai moderatori + in attesa di revisione + Puoi vedere le tue segnalazioni nella chat con gli amministratori. + Non puoi inviare messaggi! + contatto non pronto + contatto eliminato + contatto disattivato + non sincronizzato + richiesta di entrare rifiutata + impossibile inviare messaggi + il gruppo è eliminato + il membro ha una versione vecchia + rimosso dal gruppo + sei uscito/a + Eliminare la chat con il membro? + Rifiutare il membro? + Elimina chat + Errore di eliminazione della chat con il membro + Aggiorna l\'indirizzo + Accetta la richiesta di contatto + Aggiungi un messaggio + crittografia end-to-end.]]> + solo dopo che la tua richiesta verrà accettata.]]> + Connetti + il contatto dovrebbe accettare… + Errore cambiando l\'profilo + Errore di apertura della chat + Errore di apertura del gruppo + Errore nel rifiuto della richiesta di contatto + Entra nel gruppo + Apri la chat + Apri una chat nuova + Apri un gruppo nuovo + Apri per accettare + Apri per connettere + Apri per entrare + L\'indirizzo sarà breve e il tuo profilo verrà condiviso attraverso l\'indirizzo. + Rifiuta la richiesta di contatto + richiesta inviata + Inviare una richiesta di contatto? + Invia richiesta + Invia richiesta senza messaggio + Inviato al tuo contatto dopo la connessione. + Aggiornare il link del gruppo? + Aggiorna + Aggiornare l\'indirizzo? + Il mittente NON verrà avvisato. + Messaggio di benvenuto + Il tuo profilo + Impossibile cambiare profilo + Per usare un altro profilo dopo il tentativo di connessione, elimina la chat e usa di nuovo il link. + Chatta con gli amministratori + Chatta con i membri prima che si uniscano. + Connettiti più velocemente! 🚀 + Meno traffico sulle reti mobili. + Parla immediatamente appena tocchi Connetti. + Nuovo ruolo nei gruppi: Moderatore + Nessuna sessione di instradamento privato + Scadenza dell\'instradamento privato + Scadenza del protocollo in sec. piano + Rimuove i messaggi e blocca i membri. + Revisiona i membri del gruppo + Invia i tuoi commenti privati ai gruppi. + Scadenza conness. TCP in sec. piano + Caricamento del profilo… + Bio: + Descrizione breve: + La tua bio: + Bio troppo lunga + Descrizione troppo lunga + Accetta la richiesta di contatto + Connessione lavorativa + Gruppo + Tocca Connetti per chattare + Tocca Connetti per inviare la richiesta + Tocca Entra nel gruppo + Il tuo contatto lavorativo + Il tuo contatto + Il tuo gruppo + Il tempo di scomparsa è impostato solo per i contatti nuovi. + 4 nuove lingue + Catalano, indonesiano, rumeno e vietnamita - grazie ai nostri utenti! + Crea il tuo indirizzo + Attiva i messaggi a tempo in modo predefinito. + Mantieni le chat pulite + Imposta la bio del profilo e il messaggio di benvenuto. + Condividi il tuo indirizzo + Indirizzo breve di SimpleX + Aggiorna il tuo indirizzo + Usa profilo in incognito + Dai il benvenuto ai tuoi contatti 👋 + Condividi l\'indirizzo vecchio + Condividi il link vecchio + Il link sarà breve e il profilo del gruppo verrà condiviso attraverso il link. + Aggiorna il link del gruppo + RICHIESTE DI CONTATTO DAI GRUPPI + Il membro è eliminato - impossibile accettare la richiesta + connessione richiesta dal gruppo %1$s + Questa impostazione è per il tuo profilo attuale + Consenti file e contenuti multimediali solo se il tuo contatto li consente. + Consenti ai tuoi contatti di inviare file e contenuti multimediali. + Bot + Sia tu che il tuo contatto potete inviare file e contenuti multimediali. + File e contenuti multimediali sono vietati in questa chat. + Solo tu puoi inviare file e contenuti multimediali. + Solo il tuo contatto può inviare file e contenuti multimediali. + Apri per usare il bot + Proibisci l\'invio di file e contenuti multimediali. + Tocca Connetti per usare il bot + Per inviare comandi devi essere connesso/a. + Opzioni deprecate + Apri link pulito + Apri link completo + Rimuovi il tracciamento del link + Link del relay SimpleX + Errore nel segnare la chat con il membro come letta diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 1103e3a0e6..b71831c9e0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -559,10 +559,7 @@ מחיקת הודעות בלתי הפיכה אסורה. להצטרף בתור %s זה מאפשר חיבורים אנונימיים רבים ללא שום נתונים משותפים ביניהם בפרופיל צ׳אט יחיד. - זה יכול לקרות כאשר: -\n1. פג תוקפן של ההודעות בלקוח השולח לאחר 2 ימים או בשרת לאחר 30 ימים. -\n2. פיענוח הצפנת הודעה נכשל, מכיוון שאתם או איש הקשר שלכם השתמשתם בגיבוי ישן של מסד הנתונים. -\n3. החיבור נפגע. + זה יכול לקרות כאשר:\n1. פג תוקפן של ההודעות בלקוח השולח לאחר 2 ימים או בשרת לאחר 30 ימים.\n2. פיענוח הצפנת הודעה נכשל, מכיוון שאתם או איש הקשר שלכם השתמשתם בגיבוי ישן של מסד הנתונים.\n3. החיבור נפגע. איך זה משפיע על הסוללה זה יכול לקרות כאשר אתם או איש הקשר שלכם השתמשתם בגיבוי ישן של מסד הנתונים. להצטרף לקבוצה\? @@ -1088,7 +1085,7 @@ בטל השתקה ממתין לאישור… ממתין למענה… - איננו מאחסנים אף אחד מאנשי הקשר או ההודעות שלך (לאחר המסירה) בשרתים. + איננו מאחסנים את אנשי הקשר או ההודעות שלך (לאחר המסירה) בשרתים. SimpleX אתם באמצעות קישור לכתובת איש קשר @@ -1189,8 +1186,7 @@ אנשי הקשר שלך יכולים לאפשר מחיקת הודעות מלאה. הצ׳אטים איש הקשר שלך שלח קובץ גדול יותר מהגודל המרבי הנתמך כעת (%1$s). - איש הקשר שלך צריך להיות מקוון כדי שהחיבור יושלם. -\nניתן לבטל חיבור זה ולהסיר את איש הקשר (ולנסות מאוחר יותר עם קישור חדש). + איש הקשר שלך צריך להיות מקוון כדי שהחיבור יושלם.\nניתן לבטל חיבור זה ולהסיר את איש הקשר (ולנסות מאוחר יותר עם קישור חדש). פרופיל הצ׳אט שלך יישלח \nלאיש הקשר שלך מסד הנתונים שלך @@ -1222,8 +1218,7 @@ לא תאבדו את אנשי הקשר שלכם אם תמחקו מאוחר יותר את הכתובת שלכם. כתובת SimpleX שלך שרתי SMP שלך - הפרופיל שלך מאוחסן במכשירך ומשותף רק עם אנשי הקשר שלך. -\nשרתי SimpleX אינם יכולים לראות את הפרופיל שלך. + הפרופיל שלך מאוחסן במכשירך ומשותף רק עם אנשי הקשר שלך.\nשרתי SimpleX אינם יכולים לראות את הפרופיל שלך. הפרופיל, אנשי הקשר וההודעות שנמסרו מאוחסנים במכשיר שלך. אתם תפסיקו לקבל הודעות מקבוצה זו. היסטוריית הצ׳אט תישמר. הינך מנסה להזמין איש קשר איתו שיתפת פרופיל זהות נסתרת לקבוצה בה ישנו שימוש בפרופיל הראשי שלך @@ -1360,8 +1355,7 @@ אפליקציה חדשה למחשב השולחני! 6 שפות ממשק חדשות האפליקציה מצפינה קבצים מקומיים חדשים (למעט סרטונים). - ביטוי סיסמה אקראי מאוחסן בהגדרות כטקסט רגיל. -\nאתה יכול לשנות את זה מאוחר יותר. + ביטוי סיסמה אקראי מאוחסן בהגדרות כטקסט רגיל.\nאתה יכול לשנות את זה מאוחר יותר. שלח הודעה ישירה כדי להתחבר גלה והצטרף לקבוצות ביטוי הסיסמה להצפנת מסד הנתונים יעודכן ויישמר בהגדרות. @@ -1703,7 +1697,7 @@ מתי שהIP מוסתר השתמש במסלול פרטי עם שרתים לא ידועים אזהרה על אופן שליחת ההודעה - הראה סטטוס הודעה + הצג מצב הודעה מסלול פרטי להודעה 🚀 סטטוס הודעה:%s אנא וודא שהמכשיר והמחשב מחוברים לאותה רשת מקומית, ושהפיירוול של המחשב מאפשר את החיבור. @@ -1742,7 +1736,7 @@ אוזניות ערכת נושא לפרופיל צבעי הצא\'ט - הראה רשימת צא\'טים בחלון חדש + הצג רשימת שיחות בחלון חדש מצב הקובץ סטטוס הודעה מצב הקובץ:%s @@ -1766,7 +1760,7 @@ הגדרות מתקדמות הגדר ערכת נושא ברירת מחדל אפס ערכת נושא למשתמש - החל ל + החל על ערכת נושא משתמשים יכולים לשלוח קישורי SimpleXצ עשה שהצאט\'ים שלך יראו אחרת! @@ -1785,7 +1779,7 @@ ערכת נושא צבעי מצב כהה שגיאה בשרת היעד:%1$s - "אל תשלח הודעות ישירות אפילו אם שרת היעד לא תומך במסלול פרטי" + אל תשלח הודעות ישירות אפילו אם שרת היעד לא תומך במסלול פרטי שיפור בשליחת הודעות מצלמה אפשר שליחת קישורי SimpleX @@ -1828,7 +1822,7 @@ אין עדיין חיבור ישיר, ההודעה תעובר ע"י מנהל. חבר לא פעיל שרתי XFTP אחרים - הראה אחוזים + הצג אחוזים מושבת יציבה הותקן בהצלחה @@ -1924,7 +1918,7 @@ %1$d הקבצים עדיין בהורדה. הסכם לתנאים %1$d ההורדה של הקובץ/ים עדיין לא הסתיימה. - התנאים המקובלים עלי + התנאים שקיבלתי שנה סיבה אחרת כתובת עסקית @@ -2091,4 +2085,57 @@ איש הקשר יימחק - לא ניתן לבטל זאת! איש הקשר נמחק. ספאם + ניתן לשנות זאת בהגדרות המראה. + אין שרתי הודעות. + חיבור נחסם + ממתין לאישור + ממתין + סרגל כלים נגיש לצ\'אט + שימוש בפורט אינטרנט + לשליחה + צ\'אט אחד עם חבר + הודעה חדשה + הגדרת מפעילי שרת + ניתן להגדיר שרתים דרך הגדרות. + אישר אותך + ממתין לסקירה + אשר + אישור חבר + הוסף קישור קצר + קצוות + צורת ההודעה + סקירה + ממתין + אין צ\'אטים ברשימה %s. + שמור רשימה + גרסת השרת אינה תואמת לאפליקציה שלך: %1$s. + סרגלי כלים נגישים לאפליקציות + כל + דחיה + אשר כחבר + לדחות את החבר? + החבר יצטרף לקבוצה, לקבל אותו? + קבל כצופה + שימוש באפליקציה ביד אחת. + החל מ-%s.\nכל הנתונים נשמרים פרטיים במכשיר שלך. + כל השרתים + צ\'אטים פרטיים, קבוצות ואנשי הקשר שלך אינם נגישים למפעילי השרת. + לדוגמה, אם איש הקשר שלך מקבל הודעות דרך שרת SimpleX Chat, האפליקציה שלך תעביר אותן דרך שרת Flux. + מדיניות פרטיות ותנאי שימוש. + ניתן להגדיר מפעילים בהגדרות רשת ושרתים. + לא + זנב לבועה + סרגל כלים נגיש לצ\'אט + %1$s אושר + חבר חדש רוצה להצטרף לקבוצה. + אנא המתן עד שמנהלי הקבוצה יבדקו את בקשתך להצטרף לקבוצה. + שימוש בהודעות + צורת הודעה הניתנת להתאמה אישית. + שמור וחבר מחדש + שקיפות + אנא בדוק שקישור SimpleX תקין. + פתח שיחה + פתח שיחה חדשה + פתח קבוצה חדשה + שלח את המשוב הפרטי שלך לקבוצות. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 21c04b8473..efc1810bac 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -1989,7 +1989,7 @@ リストを作成 サーバオペレータ 利用条件を開く - バックグラウンドサービスを使用しない + バックグラウンドサービスを使用しません 利用条件の承諾 %s の利用条件に承諾しています。]]> 後で作成する場合はメニューから「SimpleXのアドレスを作成」を選択してください。 @@ -2008,4 +2008,38 @@ リストを追加 すべて ワンタイムリンクを生成 + %d 件を選択中 + グループのパフォーマンス向上 + 選択 + プライバシーとセキュリティの向上 + 承諾 + プライバシーポリシーと利用条件 + サーバオペレータの設定 + 承諾 + サーバオペレータは、プライベートチャット・グループ・連絡先にはアクセスできません。 + SimpleX Chat を利用することで、以下の事項に同意したものと見なされます:\n- パブリックグループでは合法なコンテンツのみを送信すること。\n- 他のユーザを尊重すること、またスパムメッセージを送信しないこと。 + チャットを開く + 新しいチャットを開始 + 新しいグループを開始 + 無効なリンク + SimpleXのリンクが正しいか確認してください + あなたとモデレーターのみが見ることができます + 送信者とモデレーターのみが見ることができます + アーカイブされたレポート + %sによってアーカイブされたレポート + E2E暗号化.]]> + 接続を要求されました + 招待を受け入れました + SimpleXチャンネルのリンク + スパム + 不適切なコンテンツ + コミュニティガイドライン違反 + 不適切なプロフィール + 他の理由 + サーバーの保存中にエラーが発生しました + メッセージサーバーがありません + メッセージを受信するサーバーがありません + プライベートメッセージルーティング用のサーバーがありません。 + メディアおよびファイルサーバーは存在しません。 + ファイルを送信するサーバーがありません。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index dc692c1968..d21b8b8f83 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -416,4 +416,4 @@ %s വാഗ്ദാനം ചെയ്തു തിരയുക ഓഫാണ് - \ No newline at end of file + diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index d07cb6db39..b5be8b9773 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -936,7 +936,7 @@ Fout bij bijwerken van groep link Initiële rol Waarnemer - Je kunt geen berichten versturen! + jij bent waarnemer je bent waarnemer Systeem Audio en video oproepen @@ -1059,7 +1059,7 @@ Toegangscode ingesteld! Systeem Decodering fout - De hash van het vorige bericht is anders.\" + De hash van het vorige bericht is anders. %1$d berichten overgeslagen. Het kan gebeuren wanneer u of de ander een oude database back-up gebruikt. Meld het alsjeblieft aan de ontwikkelaars. @@ -2379,9 +2379,61 @@ Voor deze link is een nieuwere app-versie vereist. Werk de app bij of vraag je contactpersoon om een compatibele link te sturen. Volledige link Niet-ondersteunde verbindingslink - Gebruik korte links (BETA) Korte link Serveroperators configureren Privacybeleid en gebruiksvoorwaarden. Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. + contact verwijderd + Chat met beheerders + Chats met leden + lid heeft oude versie + Controleer de leden voordat u ze toelaat (knocking). + Gebruik TCP-poort 443 alleen voor vooraf ingestelde servers. + Fout bij het accepteren van lid + 1 chat met een lid + %d chat(s) + %d chats met leden + %d berichten + geaccepteerde %1$s + beoordeling + Chat met lid + Accepteren als lid + Accepteren als waarnemer + beoordeeld door beheerders + Korte link toevoegen + Nieuw lid wil zich bij de groep aansluiten. + Chat met beheerders + Chat verwijderen + Chat met lid verwijderen? + Geen chats met leden + Afwijzen + Lid afwijzen? + kan geen berichten versturen + verwijderd uit de groep + je bent weggegaan + Uit + alle + uit + Leden beoordelen + Fout bij het verwijderen van chat met lid + Rapport verzonden naar moderators + Je kunt geen berichten versturen! + U kunt uw rapporten bekijken in Chat met beheerders. + contact uitgeschakeld + contact niet klaar + groep is verwijderd + niet gesynchroniseerd + verzoek tot toetreding afgewezen + Alle servers + Vooraf ingestelde servers + Toegangsinstellingen opslaan? + heb je geaccepteerd + Wacht totdat de moderators van de groep uw verzoek tot lidmaatschap van de groep hebben beoordeeld. + je hebt dit lid geaccepteerd + in afwachting van beoordeling + Toegang voor leden instellen + Toelating van leden + Accepteer + Lid accepteren + Lid zal toetreden tot de groep, lid accepteren? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 5102c98563..a7862ffcf1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -641,7 +641,7 @@ Zaproszenie do grupy wygasło zaktualizowano profil grupy zaproszony przez Twój link grupy - zaproszony %1$s + zaprosił %1$s opuścił opuścił członek @@ -1878,7 +1878,7 @@ Połączony Bieżący profil Otrzymane wiadomości - Odebranie wiadomości + Odbiór wiadomości Błąd resetowania statystyk duplikaty Zakończono @@ -2166,7 +2166,7 @@ Treść narusza warunki użytkowania Połączenie zablokowane Połączenie jest zablokowane przez operatora serwera:\n%1$s. - Lista zmian + Zmień listę Utwórz listę Usuń Nie można załadować tekstu aktualnych warunków, możesz przejrzeć warunki za pomocą tego linku: @@ -2184,4 +2184,9 @@ Warunki będą akceptowane dla operatorów włączonych po 30 dniach. Zmień kolejność Połączenie wymaga renegocjacji szyfrowania. + przyjął %1$s + przyjął cię + Nowy członek chce dołączyć do grupy. + 1 rok + Akceptuj diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index a0cce488af..285d6f3802 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -87,7 +87,7 @@ Aceitar solicitações de contato automaticamente Aparência O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis. - Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. + Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. \nAtenção: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar. Bom para bateria. O aplicativo procura por mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]> chamda encerrada %1$s @@ -739,7 +739,7 @@ Chamada perdida Salvar senha na Keystore Seu banco de dados de bate-papo não está criptografado - defina uma senha para protegê-lo. - convite para o grupo%1$s + convite para o grupo %1$s Você esta usando um perfil anônimo para este grupo - para evitar compartilhar seu perfil principal, convidar contatos não é permitido Confirmação de migração inválida Migrações: %s @@ -1445,7 +1445,7 @@ Modo anônimo simplificado Desktop encontrado Aleatório - Migração do banco de dados em progresso. + Migração do banco de dados em progresso. \nIsso pode levar alguns minutos. %1$d mensagens moderadas por %2$s %d mensagens bloqueadas @@ -2268,7 +2268,7 @@ Aprimorada a navegação de bate-papo - Abra o chat na primeira mensagem não lida.\n- Pule para mensagens citadas. Os membros serão removidos do chat. Essa ação não pode ser desfeita! - Said do chat + Sair do chat Os membros serão removidos do grupo. Essa ação não pode ser desfeita! Nove servidor Nenhum chat encontrado @@ -2382,4 +2382,16 @@ Aceitar Ao usar o SimpleX Chat, você concorda em:\n- enviar apenas conteúdo legal em grupos públicos.\n- respeitar outros usuários – sem spam. Política de privacidade e condições de uso. + Aceitar como membro + Adicionar link curto + 1 conversa com um membro + Todos servidores + %1$s aceito + aceitou você + Aceitar + não pode enviar mensagens + Aceitar como observador + Aceitar membro + todos + Conversas com membros diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 544ee8af89..5f12e762aa 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -268,7 +268,7 @@ O contacto permite Preferências de conversa SimpleX - m + mil Conectar através do endereço de contacto? Conectar via link de convite? Conectar através da ligação do grupo\? @@ -971,4 +971,9 @@ Todos os perfis Já conectando! Já entrando no grupo! + Não foi possível descarregar %1$d ficheiros(s). + %1$d erro(s) nos ficheiros:\n%2$s + Ainda a descarregar %1$d ficheiros(s). + %1$d ficheiros(s) eliminado(s). + Não foi possível descarregar %1$d ficheiro(s). diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 92572516b9..7232cc56d3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -1,74 +1,74 @@ %1$d mesaje moderate de %2$s - Întrerupi schimbarea adresei? + Anulezi schimbarea adresei? 30 secunde Acceptă Acceptă incognito Adaugă server - Reglări avansate de rețea + Setări avansate de rețea %1$s dorește să se conecteze cu tine prin Acceptă %1$d mesaje omise. 1 lună 1 săptămână administrator - Accent adițional - Adițional secundar + Accent suplimentar + Suplimentar secundar Acceptă - 6 limbi noi pentru interfață - %1$d mesaje nu au putut fi descifrate. + 6 noi limbi de interfață + %1$d mesaje nu au putut fi decriptate. %1$d mesaj(e) omis(e) %1$s MEMBRI 1 zi 1 minut - link de unică folosință + Link unic 5 minute Despre SimpleX Despre adresa SimpleX Despre SimpleX Chat - deasupra, apoi: + mai sus, apoi: Accent Acceptă - Accepți cererea de conexiune? + Accepți cererea de conectare? Adaugă contact apel acceptat - Adaugă servere prestabilite + Adaugă servere presetate Adaugă profil Adresă - Adaugă servere prin scanare de coduri QR. - Adaugă la un alt dispozitiv - Adaugă mesaj de întâmpinare - se acceptă cifrarea… - se acceptă cifrarea pentru %s… - Administratorii pot crea linkuri de participare la grupuri. - Câteva lucruri în plus + Adaugă servere prin scanarea codurilor QR. + Adaugă pe alt dispozitiv + Adaugă mesaj de bun venit + se agreează criptarea… + se agreează criptarea pentru %s… + Administratorii pot crea linkurile pentru a se alătura grupurilor. + Încă câteva lucruri Toate datele aplicației sunt șterse. - Schimbarea de adresă va fi întreruptă. Se va folosi vechea adresa de primire. - Adaugă adresa la profil, astfel încât contactele să o poată partaja cu alte persoane. Profilul reînoit va fi comunicat contactelor. - Accesezi serverele prin proxy SOCKS cu portul %d? Proxyul trebuie pornit înaintea activării acestei opțiuni. - Întrerupe schimbarea adresei - Întrerupe - Toate conversațiile și mesajele vor fi șterse - operațiunea este definitivă! - Toate mesajele vor fi șterse - acest lucru nu poate fi anulat! + Schimbarea adresei va fi anulată. Vechea adresă de primire va fi folosită. + Adaugă o adresă la profilul tău, pentru ca persoanele din lista ta de contacte să o poată partaja cu alții. Actualizarea profilului va fi trimisă contactelor tale. + Accesați serverele via proxy SOCKS pe portul %d? Proxy-ul trebuie pornit înainte de a activa această opțiune. + Anulează schimbarea adresei + Anulează + Toate conversațiile și mesajele vor fi șterse – această acțiune nu poate fi anulată! + Toate mesajele vor fi șterse - această acțiune nu poate fi anulată! Permite reacții la mesaje doar dacă și contactul tău le permite. - Permite ștergerea ireversibilă a mesajelor trimise. (24 ore) + Permite ștergerea definitivă a mesajelor trimise. (24 de ore) Permite trimiterea de fișiere și media. - Toate datele sunt șterse când este introdusă. + Toate datele sunt șterse la introducerea lor. Toți membrii grupului vor rămâne conectați. - Toate mesajele vor fi șterse - operațiunea este definitivă! Mesajele vor fi șterse DOAR pentru tine. + Toate mesajele vor fi șterse - această acțiune nu poate fi anulată! Mesajele vor fi șterse NUMAI pentru tine. Toate mesajele noi de la %s vor fi ascunse! Permite Permite Permite apeluri doar dacă le permite contactul tău. - Permiți mesaje vocale? - Permite mesajele vocale numai dacă le permite și contactul tău. - Permite ștergerea mesajelor ireversibile doar dacă și contactul tău îți permite ție. (24 ore) + Permiteți mesaje vocale? + Permite mesaje vocale doar dacă contactul tău le permite. + Permite ștergerea definitivă a mesajelor doar dacă și contactul tău o permite. (24 de ore) Permite reacții la mesaje. Permite trimiterea de mesaje directe membrilor. Permite trimiterea de mesaje efemere. Permite trimiterea de mesaje vocale. - Permite mesaje efemere doar dacă le permite contactul tău. + Permite mesaje autodistructive doar dacă și contactul tău le permite. Permite contactelor tale să trimită mesaje care dispar. Aspect Versiunea aplicației @@ -88,55 +88,54 @@ Camera și microfon Atenție: arhiva va fi ștearsă.]]> Anulează previzualizarea fișierului - Un profil de conversație gol cu numele furnizat este creat, și aplicația se deschide ca de obicei. + Un profil de conversație gol cu numele furnizat este creat, iar aplicația se deschide ca de obicei. Se conectează deja! Aplicația poate primi notificări doar când rulează, niciun serviciu în fundal nu va fi lansat. Se alătură deja grupului! Mereu pornit Un nou profil aleatoriu va fi distribuit. - Android Keystore este folosit pentru a stoca în siguranță fraza de acces - permite serviciului de notificare să funcționeze. + Android Keystore este folosit pentru a stoca în siguranță parola. Acest lucru permite funcționarea serviciului de notificări. și %d alte evenimente Răspunde la apel - Android Keystore va fi folosit pentru a stoca în siguranță fraza de acces după ce repornești aplicația sau schimbi fraza de acces - va permite primirea notificărilor. + Android Keystore va fi folosit pentru a stoca în siguranță parola după ce repornești aplicația sau schimbi parola — acest lucru va permite primirea de notificări. APLICAȚIE Creează grup Apeluri audio și video Arhivează și încarcă Bază de date de arhivare Se creează un link de arhivare - Creează un link de invitare unic. + Creează link de invitație de unică folosință Acceptă automat imagini Apel audio - Apel audio + apel audio Audio oprit PICTOGRAMĂ APLICAȚIE Cod de acces aplicație Creează grup secret Creează coadă - Serviciul în fundal rulează mereu - notificările vor fi afișate imediat ce sunt disponibile. + Serviciul de fundal rulează permanent – notificările vor fi afișate imediat ce mesajele sunt disponibile. Autentificare Autentificare eșuată - Codul de acces curent + Codul de acces actual Autentificare indisponibilă Atașează Înapoi Creează grup secret - Se creează link… - apel audio (necifrat e2e) + Se creează linkul… + apel audio (necriptat e2e) Creează fișier Adaugă contact: pentru a crea un nou link de invitare, sau a te conecta printr-un link pe care l-ai primit.]]> Autentificare anulată Cod de acces aplicație este înlocuit cu cod de acces de autodistrugere. Apeluri audio/video - " -\nDisponibil în v5.1" + \nDisponibil în v5.1 Cod de acces aplicație Migrare date aplicație Eroare critică Aplică Creează profil Creează profil - Acceptă automat cererile de contactare + Acceptare automată a cererilor de contact personalizat În prezent dimensiunea maximă pentru fișiere este %1$s. Creează adresă SimpleX @@ -144,8 +143,8 @@ Creează-ți profilul Apeluri audio și video Audio pornit - Frază de acces curentă… - Creează link pentru grup + Parola actuală… + Creează link de grup Creează link creator Fundal @@ -155,11 +154,11 @@ Arabă, Bulgară, Finlandeză, Ebraică, Thailandeză și Ucraineană - mulțumită utilizatorilor și Weblate. Apelurile audio/video sunt interzise. (actual) - Poză de profil eliminată + fotografia de profil a fost eliminată Temă întunecată eliminat Întunecată - Adresă de contact eliminată + adresa de contact eliminată Personalizează și distribuie teme colorate. Teme personalizate Repetă descărcarea @@ -170,174 +169,174 @@ Elimină Elimină Elimină membru - Elimini membrul? - Resetează la implicit - Resetează culoarea - Resetează culorile - Elimină imagine + Eliminați membrul? + Resetează la valorile implicite + Resetezi culoarea + Resetați culorile + Elimină imaginea Repetă încărcarea apel respins - Elimini fraza de acces din Keystore? - Elimini parola din reglări? + Elimini parola din Keystore? + Elimini parola din setări? Repetă cererea de conectare? Necesar Reîncearcă - Primește fișiere în siguranță + Primiți fișiere în siguranță Grupuri mai sigure - Restabilește + Restaurați Reîmprospătează - Revoci fișierul? + Revocați fișierul? Revocă - Renegociezi cifrarea? - Resetează + Renegociezi criptarea? + Resetare Respinge - Salvează parola din reglări + Salvează parola în setări Salvează și actualizează profilul grupului Repetă cererea de alăturare? - Repornește conversația + Reporniți conversația salvat - Salvat de la %s + salvat de la %s Salvează Salvat - Salvat de la + Salvat din Renegociază - Salvează servere - Servere WebRTC ICE salvate vor fi eliminate. + Salvează serverele + Serverele ICE WebRTC salvate vor fi eliminate. Salvează parola profilului - Salvează fraza de acces și deschide conversația + Salvează parola și deschide conversația %s și %s - Renegociază cifrarea + Renegociază criptarea Salvează și notifică contactul Salvează și notifică contactele Salvează și notifică membrii grupului - Rulează când aplicația este pornită + Rulează când aplicația este deschisă Răspunde Revocă fișierul Salvează - Salvezi reglările? - Salvezi preferințe? + Salvezi setările? + Salvezi preferințele? Repornire - Restabilește copia de rezervă a bazei de date - Restabilești copia de rezervă a bazei de date? - Eroare la restabilirea bazei de date + Restaurează backupul bazei de date + Restaurezi backupul bazei de date? + Eroare de restaurare a bazei de date %1$s eliminat %s și %s conectați Rol Salvează Arată Respinge - Salvezi servere? + Salvezi serverele? Apel respins Mesaj salvat - Repornește aplicația pentru a crea un nou profil - Salvează fraza de acces în Keystore + Repornește aplicația pentru a crea un profil de conversație nou. + Salvează parola în Keystore Salvează profilul grupului Repetă - Trimite previzualizări ale link-ului - Setează frază de acces - Distribuie adresă + Trimite previzualizări de linkuri + Setează parola + Partajează adresa Trimis la Secundar Mesaj trimis - Setează preferințele grupului + Setați preferințele grupului trimis - Adresa serverului este incompatibilă cu reglările rețelei. - Versiunea serverului este incompatibilă cu reglările rețelei. - Trimițând prin - trimiterea de fișiere nu este acceptată încă - Scanează codul de securitate din aplicația contactului tău - Selectează contacte + Adresa serverului este incompatibilă cu setările de rețea. + Versiunea serverului este incompatibilă cu setările de rețea. + Trimitere prin + trimiterea de fișiere nu este încă acceptată + Scanează codul de securitate din aplicația persoanei tale de contact. + Selectează contactele Autodistrugere Răspuns trimis - Distribuie media… - Distribuie mesaj… - Arată lista conversațiilor într-o fereastră nouă - Arată consola într-o fereastră nouă - Setează fraza de acces a bazei de date - Setează fraza de acces a bazei de date - setează adresă de contact nouă + Partajează media… + Partajează mesaj… + Afișează lista de conversații într-o fereastră nouă + Afișează consola în fereastră nouă + Configurează parola bazei de date + Setează parola bazei de date + setați adresă de contact nouă %s (actual) - Cod de sesiune + Codul sesiunii Expeditorul a anulat transferul de fișiere. - Serverul necesită autorizație pentru a crea cozi, verifică parola - Distribuie + Serverul necesită autorizare pentru a crea cozi. Verifică parola + Partajează trimitere eșuată - Caută sau lipește link SimpleX - Setează numele de contact + Caută sau lipește linkul SimpleX + Setează numele contactului Salvezi mesajul de bun venit? - Evaluare de securitate - Scanează cod QR de pe desktop + Evaluarea securității + Scanează codul QR de pe desktop Caută - Trimite un mesaj live - se va actualiza pentru destinatar(i) în timp ce îl tastezi - Distribuie fișier - Trimite până la ultimele 100 de mesaje membrilor noi. - Bara de căutare acceptă link-uri de invitație. + Trimiteți un mesaj live - acesta se va actualiza pentru destinatar(i) pe măsură ce îl tastați + Partajează link + Trimiteți până la 100 de mesaje recente noilor membri. + Bara de căutare acceptă linkuri de invitație. secunde Scanează de pe mobil Mesaj trimis Scanează cod QR Trimite Trimite întrebări și idei - Arată opțiuni dezvoltator + Afișează opțiunile pentru dezvoltatori sec - Mesajele trimise vor fi șterse după timpul setat. - Serverul necesită autorizație pentru a încărca, verifică parola - Arată contact și mesaje - Distribuie fișier… - Setează numele de contact… + Mesajele trimise vor fi șterse după ora setată. + Serverul necesită autorizare pentru a încărca fișiere. Verifică parola + Afișează contactul și mesajul + Partajează fișier… + Setează numele contactului… Trimite mesaj - Trimite mesaj temporar + Trimiteți un mesaj care dispare (scanează sau lipește din clipboard) - Arată cod QR - Test server eșuat! - Arată: - Arată erori interne + Afișează codul QR + Testul serverului a eșuat! + Afișează: + Afișează erorile interne secret - REGLĂRI + SETĂRI %s conectat - setează imagine de profil - Trimis către: %s + setați o nouă poză de profil + Trimis la: %s SERVERE - Trimite mesaj live + Trimite mesaj în direct %s descărcat - Distribui adresa cu contactele? - Arată previzualizare - trimite mesaj direct - Trimite mesaj direct pentru a te conecta + Partajați adresa cu contactele? + Afișează previzualizare + trimite pentru a te conecta + Trimiteți mesaj direct pentru a vă conecta Selectează - Trimiterea de fișiere va fi oprită. + Trimiterea fișierului va fi oprită. Trimite - Reglări - Scanează cod + Setări + Scanează codul Cod de securitate - Trimite-ne email - Scanează codul QR al serverului - Distribuie contactelor - Arată - cod de securitate schimbat - Arată ultimul mesaj - Trimite mesaj direct + Trimiteți-ne un e-mail + Scanați codul QR al serverului + Partajează cu contactele + Afișează + codul de securitate a fost schimbat + Afișează ultimele mesaje + Trimiteți un mesaj direct Setează tema implicită SimpleX - SimpleX nu poate rula în fundal. Vei primi notificările doar când aplicația rulează. - Serviciu SimpleX Chat + SimpleX nu poate rula în fundal. Vei primi notificări doar atunci când aplicația rulează. + Serviciul SimpleX Chat Adresă SimpleX - Închide - Link-uri SimpleX - Link-urile SimpleX sunt interzise în acest grup. - Securitatea SimpleX Chat a fost verificată de Trail of Bits. + Oprire + Linkuri SimpleX + Linkurile SimpleX sunt interzise. + Securitatea SimpleX Chat a fost auditată de Trail of Bits. Mesaje SimpleX Chat Apeluri SimpleX Chat Adresă SimpleX simplexmq: v%s (%2s) - Link-uri SimpleX nepermise + Linkurile SimpleX nu sunt permise Echipa SimpleX - Închizi? + Opriți? Adresă de contact SimpleX Link pentru grup SimpleX - Link-uri SimpleX - Invitație unică SimpleX - Siglă SimpleX + Linkuri SimpleX + Invitație de unică folosință SimpleX + Logo SimpleX Grupuri mici (max 20) Mod incognito simplificat Pătrat, cerc, sau orice între. @@ -352,282 +351,280 @@ Niște servere au eșuat testul: Difuzor oprit Difuzor pornit - Copie de rezervă a datelor aplicației + Backup al datelor aplicației Tema aplicației Accent suplimentar 2 Începe conversația Toate modurile de culoare Folosește mereu releu - Aplică pentru + Aplică la Începe o nouă conversație - Stea pe GitHub - Cifrare standard de la un capăt la altul + Acordă o stea pe GitHub + criptare standard end-to-end Pornește periodic Mereu Folosește mereu rutare privată. Toate contactele vor rămâne conectate. Actualizarea profilului va fi trimisă contactelor tale. pornire… - %s secunde + %s secundă(e) Începi conversația? - Reglări avansate - Adresă desktop rea - ID de mesaj incorect - Hash de mesaj incorect - ID de mesaj incorect - Hash de mesaj incorect + Setări avansate + Adresă desktop incorectă + ID mesaj incorect + hash mesaj incorect + ID mesaj incorect + Hash mesaj incorect Schimbă adresa de primire Conversația este oprită. Dacă ai folosit deja această bază de date pe alt dispozitiv, ar trebui să o transferi înapoi înainte de a porni conversația. APELURI - ai schimbat rolul pentru tine la %s + v-ați schimbat rolul în %s Capacitate depășită - destinatarul nu a primit mesajele trimise anterior. Schimbă codul de acces autodistructibil Conversația este oprită se schimbă adresa… Contact verificat Creat la - Migrează de pe alt dispozitiv pe dispozitivul nou și scanează codul QR.]]> + Migrează de pe alt dispozitiv pe noul dispozitiv și scanați codul QR.]]> Te conectezi prin adresa de contact? - Te conectezi printr-un link unic? + Vrei să te conectezi printr-un link unic? Conectare incognito Contactul deja există Schimbă codul de acces - Poți porni Blocare SimpleX din Reglări. + Puteți activa SimpleX Lock din Setări. Conversații Alege un fișier - Contactul tău trebuie să fie online pentru a se completa conexiunea. -\nPoți anula această conexiune și elimina contactul (și poți încerca mai târziu cu un nou link). + Persoana ta de contact trebuie să fie online pentru ca procesul de conectare să se finalizeze.\nPoți anula această conexiune și șterge contactul (apoi poți încerca mai târziu cu un link nou). eroare apel Apelurile tale Schimbă Schimbă rolul Contactul permite - Grupuri mai bune + Grupuri îmbunătățite Desktop conectat Preferințe contact Te conectezi cu %1$s? Anulează previzualizarea imaginii - Discută cu dezvoltatorii - Trebuie să introduci fraza de acces de fiecare dată când aplicația pornește - nu este stocată pe dispozitiv. + Conversează cu dezvoltatorii + Trebuie să introduci parola de fiecare dată când pornești aplicația - aceasta nu este stocată pe dispozitiv. blocat Preferințe conversație Mod întunecat Interfață chineză și spaniolă - Crează un grup folosind un profil aleatoriu. + Creează un grup folosind un profil aleatoriu. Blochează membrii grupului Mobil conectat Conectat la desktop Anulează migrarea Celular - Nu poți trimite mesaje! + ești observator Schimbi adresa de primire? Contactul nu este conectat încă! Conectare prin link - Poți vedea linkul de invitație din nou în detaliile conexiunii. + Puteți vedea din nou linkul de invitație în detaliile conexiunii. Profilurile tale de conversație - Crează profil de conversație - apel terminat %1$s - Crează + Creează un profil de conversație + apel încheiat %1$s + Creează Bluetooth Camera Apeluri pe ecranul blocat: - contactul are cifrare e2e - contactul nu are cifrare e2e + contactul are criptare e2e + contactul nu are criptare e2e Contacte - DISCUȚII + CONVERSAȚII BAZĂ DE DATE CONVERSAȚIE - Baza de date a conversației ștearsă + Baza de date a conversației a fost ștearsă Conversația rulează - Baza ta de date a conversațiilor - Baza de date a conversațiilor tale nu este cifrată - pune o parolă pentru a o proteja. - Parola de cifrare a bazei de date va fi actualizată și stocată în reglări. - ai schimbat rolul %s la %s + Baza de date a conversațiilor tale + Baza de date a conversațiilor tale nu este criptată - setează o parolă pentru a o proteja. + Parola pentru criptarea bazei de date va fi actualizată și stocată în setări. + ați schimbat rolul %s în %s Nu se pot invita contactele! - Te-ai alăturat grupului + Te-ai alăturat acestui grup Nu se poate invita contactul! se conectează (acceptat) Creat la: %s Blochează Blochează membru Conectare directă? - Profilul tău de conversație va fi trimis membrilor grupului + Profilul tău de chat va fi trimis membrilor grupului Întunecat Culori mod întunecat - Și tu și contactul tău puteți face apeluri. + Atât tu, cât și contactul tău puteți efectua apeluri. %s anulat Conectat la mobil Eroare copiere Te conectezi prin link? - Nu ai putut fi verificat(ă); te rog încearcă din nou. + Nu ați putut fi verificat; vă rugăm să încercați din nou. Copiază Anulează mesajul live Conectare prin link / cod QR Contactele tale vor rămâne conectate. - Parola de cifrare a bazei de date va fi actualizată. + Parola pentru criptarea bazei de date va fi actualizată. Contactele tale pot permite ștergerea totală a mesajelor. - Trebuie să permiți contactului tău să trimită mesaje vocale pentru a le putea trimite. - Versiunede bază: v%s + Trebuie să îi permiți contactului tău să trimită mesaje vocale pentru a le putea trimite. + Versiunea de bază: v%s Creează o adresă pentru a permite oamenilor să se conecteze cu tine. %s blocat - ai schimbat adresa - ai schimbat adresa pentru %s + ați schimbat adresa + ați schimbat adresa pentru %s se schimbă adresa… se schimbă adresa pentru %s… - Și tu și contactul tău puteți trimite mesaje temporare. + Atât tu, cât și contactul tău puteți trimite mesaje autodistructive. Nu se pot primi fișiere - Poate fi dezactivat din reglări – notificările vor fi afișate dacă aplicația este în funcțiune.]]> + Poate fi dezactivat din setări – notificările vor fi afișate dacă aplicația este în funcțiune.]]> Verifică mesajele noi la fiecare 10 minute timp de până la 1 minut - Nu ai conversații + Nu aveți conversații Contactul și toate mesajele vor fi șterse - acest lucru nu poate fi anulat! - Copiat în clipboard + Copiat Camera - Ai invitat un contact - Profilul tău de conversație va fi trimis -\ncontactului tău + Ați invitat un contact + Profilul tău de conversație va fi trimis contactului tău. Contribuie Profil conversație aldin - Poți folosi markdown pentru a formata mesaje: + Puteți folosi marcarea pentru a formata mesajele: se apelează… Schimbă modul de autodistrugere - Baza de date a conversației importată - Trebuie să folosești cea mai recentă versiune a bazei de date a conversațiilor DOAR pe un singur dispozitiv, altfel se poate să nu mai primești mesajele de la unele contacte. + Baza de date a conversației a fost importată + Trebuie să utilizați cea mai recentă versiune a bazei de date de chat DOAR pe un singur dispozitiv, altfel este posibil să nu mai primiți mesaje de la unele contacte. Nu se poate accesa Keystore pentru a salva parola bazei de date - Conversație migrată! + Conversația a fost migrată! Continuă apel în curs Apel în curs - Și tu și contactul tău puteți trimite mesaje vocale. + Atât tu, cât și contactul tău puteți trimite mesaje vocale. Contact ascuns: Nume contact - Crează adresă - Bază de date cifrată! - Schimbi fraza de acces a bazei de date? + Creează adresă + Bază de date criptată! + Schimbi parola bazei de date? Conversația este oprită - Poți porni discuția din Reglările aplicației / Baza de date sau repornind aplicația. - ai ieșit - Blochezi membrul? + Puteți porni discuția din Setările aplicației / Baza de date sau repornind aplicația. + ați ieșit + Blocați membrul? Blocat de admin - Și tu și contactul tău puteți adăuga reacții la mesaje. - Mesaje mai bune + Atât tu, cât și contactul tău puteți adăuga reacții la mesaje. + Mesaje îmbunătățite blocat blocat de admin Nu se poate inițializa baza de date - Contactul tău a trimis un fișier care este mai mare decât dimensiunea maximă suportată în prezent (%1$s). - anulează previzualizarea link-ului + Contactul tău a trimis un fișier care este mai mare decât dimensiunea maximă acceptată în prezent (%1$s). + anulează previzualizarea linkului Verifică adresa serverului și încearcă din nou. - Tu îți controlezi conversația! - Apel deja terminat! - Apel terminat + Tu îți controlezi chatul! + Apelul s-a încheiat deja! + Apel încheiat Blochează pentru toți - Ai cerut deja conexiunea prin această adresă! + Ați solicitat deja conexiunea prin această adresă! Te conectezi la tine? Verifică conexiunea la internet și încearcă din nou Culori conversație - Aspectul discuției + Tema conversației Bun pentru baterie. Aplicația verifică mesajele la fiecare 10 minute. Pot fi pierdute apeluri sau mesaje urgente.]]> Negru - Blochezi membrul pentru toți? - Și tu și contactul tău puteți șterge ireversibil mesajele trimise. (24 de ore) - Folosește mai multă baterie! + Blocați membrul pentru toți? + Atât tu, cât și contactul tău puteți șterge definitiv mesajele trimise. (24 de ore) + Folosește mai multă baterie! \nServiciul în fundal rulează mereu – notificările sunt afișate imediat ce mesajele sunt disponibile. Nu se poate trimite mesajul - Ștergeți + Șterge Confirmați fișiere de la servere necunoscute. - schimbat adresa pentru dumneavoastră - Confirmați parola nouă… + am schimbat adresa pentru tine + Confirmă noua parolă… Conectare conexiune %1$d conexiune stabilită Eroare de conexiune Conexiune expirată - se conectează + se conectează… Confirmare În curând! se conectează… Schimbați modul de blocare Versiunea aplicației: %s - pentru fiecare profil de conversație pe care le aveți în aplicație]]> + pentru fiecare profil de conversație pe care îl ai în aplicație.]]> Permiteți downgrade-ul - pentru fiecare contact și membru de grup \nVa rugăm considerați că: dacă aveți prea multe conexiuni, consumul dumneavoastră de baterie și trafic de internet pot fi considerabil mai mari, iar unele conexiuni pot eșua.]]> + pentru fiecare contact și membru al grupului.\nRețineți: dacă aveți multe conexiuni, consumul bateriei și al traficului de date poate crește considerabil, iar unele conexiuni pot eșua.]]> Conexiune terminată Se conectează la desktop - Rugat să primească imaginea + S-a solicitat primirea imaginii Cerere de conexiune trimisă! De reținut: releele de mesaje și fișiere sunt conectate prin proxy SOCKS. Apelurile și trimiterea de previzualizări ale adreselor web utilizează conexiunea directă.]]> conectat Confirmați codul de access Confirmare actualizare bază de date - "schimbat rolul lui %s la %s" + a schimbat rolul lui %s în %s conectat se conectează (introdus) conectat se conectează Schimbați rolul de grup? Modul color - Prin profil de conversație (implicit) sau prin conexiune (BETA). - Comparați codurile de securitate cu contactele dumneavoastră. + După profilul de conversație (implicit) sau după conexiune (BETA). + Compară codurile de securitate cu contactele tale. Conexiune oprită Conexiune oprită Conectare la desktop Conexiunea la desktop este într-o stare proastă Conectare Conexiune - Ștergi notițele private? + Ștergeți notele private? Conectare automată se conectează apelul… - se conectează (invetație la introducere) + se conectează (invitație de introducere) se conectează… %s este într-o stare proastă]]> - Confirmă reglările rețelei + Confirmă setările de rețea se conectează… Eroare de conexiune (AUTENTIFICARE) - Optimizarea bateriei este activă, dezactivând serviciul de funcționare în ascuns și solicitările periodice pentru mesaje noi. Pot fi reactivate din reglări. - conectat - Crează un grup: pentru a crea un nou grup.]]> + Optimizarea bateriei este activă, dezactivând serviciul de fundal și cererile periodice pentru mesaje noi. Le puteți reactiva din setări. + Conectat + Creează grup:: pentru a crea un nou grup.]]> Ștergeți conversația? - Ștergeți - Ștergeți conversația + Șterge + Șterge conversația Conectare Consolă conversație - Confirmați parola + Confirmă parola colorat - Toate contactele, conversațiile și fișierele tale vor fi cifrate într-un mod sigur și încărcate pe bucăți pe releurile XFTP configurate. - Rugat să primească videoclipul + Toate contactele, conversațiile și fișierele tale vor fi criptate securizat și încărcate în fragmente către releele XFTP configurate. + S-a solicitat primirea videoclipului Comparați fișierul Vă rugăm să rețineți: nu veți putea recupera sau schimba parola dacă o veți pierde.]]> - schimbat rolul dumneavoastră la %s + rolul tău a fost schimbat în %s conectat se conectează - conectat + Conectat Ștergeți Buton de închidere Ștergeți verificarea Configurare servere ICE - conectat direct + conexiune solicitată se conectează (anunțat) Cel mai bun pentru baterie. Veți primi notificări doar când aplicația rulează (FĂRĂ servicii de fundal).]]> Se conectează apelul complet De reținut: folosirea aceleiași baze de date în două aparate, va intrerupe descifrarea mesajelor din conexiunile tale, ca măsură de protecție.]]> Confirmați încărcarea - Confirmați că țineți minte parola de la baza de date pentru a o migra. + Confirmă că-ți amintești parola bazei de date pentru a o migra. Conexiune Ștergi profilul de conversație? Șterge pentru mine %dd șters - Șterge contact + Șterge contactul Șters la Versiunea aplicației desktop %s nu este compatibilă cu această aplicație. - Ștergi mesajul membrului? - Șterge grup + Ștergeți mesajul membrului? + Șterge grupul Șterge și notifică contactele - Decentralizat + Descentralizat grup șters %d contact(e) selectat(e) Șters la: %s Ștergi profilul de conversație? - Șterge profil + Șterge profilul implicit (%s) Confirmări de livrare! Confirmările de livrare sunt dezactivate! @@ -635,51 +632,51 @@ Dispozitive desktop Desktop Eroare de decriptare - Șterge imagine + Șterge imaginea Șterge după Șterge pentru toată lumea Descriere - Șterge fișier - Șterge coadă + Șterge fișierul + Șterge coada Ștergi contactul? Șterge Șterge - Ștergi fișiere și media? + Ștergi fișierele și conținutul media? %d zile %d zi Șterge adresa - Șterge mesaje - Ștergi %d mesaje? + Șterge mesajele + Ștergeți %d mesaje? Ștergi adresa? Șterge toate fișierele - Șterge link - Ștergi link? + Șterge linkul + Ștergi linkul? zile Șterge - Ștergi mesajul? + Ștergeți mesajul? Livrare Ștergi conexiunea în așteptare? - Șterge server + Ștergeți serverul Șterge baza de date contact șters Ștergi grupul? Șterge baza de date de pe acest dispozitiv - Șterge profil de conversație - Șterge fișiere pentru toate profilurile de conversație + Șterge profilul de conversație + Șterge fișierele pentru toate profilurile de conversație Oricine poate găzdui servere. Contacte arhivate Beta Apeluri interzise! Nu se poate apela membrul grupului - Bază de date conversație exportată + Baza de date a conversației a fost exportată Verifică pentru actualizări - Contactul %1$s a schimbat la %2$s + contactul %1$s s-a schimbat în %2$s Contactul este șters. Controlează-ți rețeaua - Parola pentru cifrarea bazei de date va fi actualizată și stocată în Keystore. - Fraza de acces a bazei de date + Parola pentru criptarea bazei de date va fi actualizată și stocată în Keystore. + Parola bazei de date Servere XFTP configurate - Baza de date este cifrată folosind o parolă aleatorie. Trebuie schimbată înainte de exportare. + Baza de date este criptată folosind o parolă aleatorie. Trebuie schimbată înainte de exportare. apel conectare Contact șters! @@ -688,23 +685,22 @@ Verifică pentru actualizări Creează Estompează media - BAZĂ DE DATE DISCUȚIE + BAZĂ DE DATE CONVERSAȚIE Conectează-te cu prietenii mai ușor. încercări - Completat + Finalizat ID-urile bazei de date și opțiunea de izolare a transportului. ID bază de date ID bază de date: %d Conexiuni Creat - Estompează pentru intimitate mai bună. - Stare conexiune și servere + Estompează pentru o mai bună confidențialitate. + Statusul conexiunii și al serverelor. Eroare bază de date - Contactele pot marca mesajele pentru ștergere; tu le vei putea vedea. + Contactele pot marca mesajele pentru ștergere; tu le vei putea vizualiza. Downgrade al bazei de date - Migrarea bazei de date este în proces. -\nPoate dura câteva minute. - Fraza de acces a bazei de date și export + Migrarea bazei de date este în curs.\nAceasta poate dura câteva minute. + Parola și exportul bazei de date Toate profilurile Profil actual Confirmi ștergerea contactului? @@ -713,84 +709,84 @@ Conectat Corectează numele la %s? Continuă - Baza de date este cifrată folosind o parolă aleatorie; o poți schimba. + Baza de date este criptată folosind o parolă aleatorie, o poți schimba. Nu se pot trimite mesaje membrului grupului Se conectează Servere conectate Nu se poate apela contactul - Se conectează la contact, așteaptă sau verifică mai târziu! - Reglări avansate - Reglări - Reglările tale - apel vocal cifrat e2e - apel video cifrat e2e - APARAT + Se conectează la contact, vă rugăm să așteptați sau să verificați mai târziu! + Setări avansate + Setări + Setări + apel audio criptat e2e + apel video criptat e2e + DISPOZITIV EXPERIMENTAL - Cifrează - erori la descifrare + Criptează + erori de decriptare TU - necifrat e2e - cifrat e2e + nicio criptare e2e + criptat e2e Apel video primit Dezactivează Permiți apeluri? - terminat + încheiat Sunete la apel Apeluri îmbunătățite Apel audio primit - Termină apelul - marcate şterse + Încheie apelul + marcat ca șters %d mesaje marcate șterse moderat de %s %1$d alte erori de fișier. - %1$s mesaje netransmise - ai distribuit un link de unică folosință - ai distribuit un link ascuns de unică folosință - prin link pentru grup - ascuns prin link pentru grup + %1$s mesaje nu au fost redirecționate + ai partajat linkul de unică folosință + ai partajat linkul de unică folosință incognito + prin link de grup + incognito prin link de grup Prin browser - Reclamă + Spam Conținut inadecvat - Încălcă normelor comunitare + Încălcarea liniilor directoare ale comunității Profil inadecvat Alt motiv Eroare la salvarea serverelor SMP - Asigurați-vă că adresele serverelor XFTP sunt în format corect, pe randuri separate și nu sunt duplicate. + Asigurați-vă că adresele serverelor XFTP sunt în format corect, separate pe linii și nu sunt duplicate. Eroare la încărcarea serverlor XFTP Eroare la actualizarea configurației de rețea - Eșec la încărcarea conversațiilor + Nu s-au putut încărca conversațiile Vă rugăm să actualizați aplicația și contactați dezvoltatorii. Eroare la crearea profilului! %d mesaje blocate Eroare de renegociere a criptării invitat să se conecteze - ascuns prin link pentru adresa de contact - printr-un link de unică folosință + incognito prin linkul adresei de contact + prin link de unică folosință 1 raport 1 an - înaintat - eroare de afișare a mesajului - eroare de afișat conținutul - criptare cap-coadă cu secretizare înaintată perfecta, repudiere si recuperare în caz de spargere.]]> - criptare cap-coadă rezistentă la algoritmi cuantici cu secretizare înaintată perfecta, repudiere si recuperare în caz de spargere.]]> - Această conversație este protejată prin criptare cap-coadă. - Link întreg - ascuns printr-un link de unică folosință + redirecționat + eroare la afișarea mesajului + eroare la afișarea conținutului + criptare end-to-end, cu confidențialitate perfectă în avans, nerepudiere și recuperare în caz de compromitere.]]> + criptare end-to-end rezistentă la computere cuantice, cu confidențialitate perfectă în avans, nerepudiere și recuperare în caz de compromitere.]]> + Această conversație este protejată prin criptare integrală. + Link complet + incognito printr-un link de unică folosință a + b prin %1$s Eroare la încărcarea serverlor SMP - %1$d fișier(e) șterse. + %1$d fișier(e) au fost șterse. raport arhivat de %s - invitație acceptată - Asigurați-vă că adresele serverelor SMP sunt în format corect, pe randuri separate și nu sunt duplicate. + Invitație acceptată + Asigurați-vă că adresele serverelor SMP sunt în format corect, separate pe linii și nu sunt duplicate. Eroare la salvarea serverelor XFTP - %1$d fișier(e) încă în descărcare. - %1$d fișier(e) a eșuat să se descărcarce. + %1$d fișier(e) sunt încă în curs de descărcare. + %1$d fișier(e) nu au putut fi descărcate. %1$d fișier(e) nu au fost descărcate. - prin link pentru adresa de contact - Deschiderea link-ului în browser poate reduce confidențialitatea și securitatea conexiunii. Link-urile SimpleX de neîncredere vor fi roșii. + prin linkul adresei de contact + Deschiderea linkului în browser poate reduce confidențialitatea și securitatea conexiunii. Linkurile SimpleX nesigure vor fi afișate cu roșu. solicitat să se conecteze - Eșec la încărcarea conversației + Conversația nu a putut fi încărcată Doar tu și moderatorii vedeți asta Doar expeditorul şi moderatorii văd asta raport arhivat @@ -798,11 +794,1708 @@ primirea de fișiere nu este acceptată încă tu format mesaj necunoscut - format mesaj invalid + format de mesaj nevalid LIVE moderat conversație nevalidă date nevalide - Această conversație este protejată prin criptare cap-coadă, rezistentă la algoritmi cuantici. - Notițe private + Această conversație este protejată prin criptare end-to-end rezistentă la atacuri cuantice. + Note private + Eroare la citirea parolei bazei de date + Versiunea bazei de date este incompatibilă + Invitația la grup nu mai este validă, a fost eliminată de expeditor. + Se alătură grupului + Părăsește grupul + Eroare la ștergerea linkului de grup + Eroare la trimiterea invitației + dezactivat + Membrii pot trimite mesaje vocale. + Filtrează conversațiile necitite și favorite. + Migrare în curs + Managementul rețelei + Finalizează migrarea pe un alt dispozitiv. + Incompatibil! + Cum să utilizezi serverele tale + Importă baza de date a conversației? + Ștergeți raportul + Redirecționați mesajele… + Șterge doar conversația + Păstrezi invitația nefolosită? + Introdu numele tău: + Acordați permisiunea (permisiunile) de a efectua apeluri + Notificări și baterie + Deschide + Activați confirmarea de primire pentru grupuri? + PENTRU CONSOLĂ + Moderat la + Remediați conexiunea + Profiluri de conversație multiple + Ascuns + Introducere parolă + Redirecționare server: %1$s\nEroare:%2$s + Fișierul nu a fost găsit - cel mai probabil fișierul a fost șters sau anulat. + Adresă de server invalidă! + Dezactivat + Fișier: %s + Eroare la crearea linkului de grup + Dispare la + dezactivat + direct + Fișiere și media + Vrei să te alături grupului? + k + Se deschide baza de date… + Cale de fișier nevalidă + Nume afișat duplicat! + Moderare + Marchează ca necitit + Eroare la redirecționarea mesajelor + Sau scanați codul QR + Gazdă + Cum se folosește marcarea + Imun la spam + Baza de date va fi criptată. + Grup inactiv + Bună dimineaţa! + Fișierele și conținutul media sunt interzise. + moderatori + Max 40 de secunde, primit instantaneu. + Configurație îmbunătățită a serverului + Mesaj de bun venit pentru grup + Reducerea suplimentară a consumului de baterie + Alătură-te conversațiilor de grup + Livrarea mesajelor îmbunătățită + Interfață lituaniană + Fă-ți conversațiile să arate diferit! + Menționați membrii 👋 + Ajutați administratorii să modereze grupurile lor. + Doar un singur dispozitiv poate funcționa în același timp + Grupul exită deja! + Se descarcă arhiva + Finalizați migrarea + Eroare la verificarea parolei: + Eroare la importarea bazei de date a conversației + Mesaje + Eroare la exportarea bazei de date a conversației + Nicio informație, încercați să reîncărcați + Activează jurnalele + Autentificarea dispozitivului este dezactivată. Se dezactivează SimpleX Lock. + Invitația a expirat! + Grup + Invitați în grup + Deconectează dispozitivele mobile + Nume afișat nevalid! + Eroare la schimbarea profilului! + Fără servere media și de fișiere. + Nu există servere de mesaje. + Nu există servere pentru rutarea mesajelor private. + Nu există servere pentru a primi mesaje. + Pentru profilul de conversație %s: + Nu există servere pentru a primi fișiere. + Eroare la conectarea la serverul de redirecționare %1$s. Vă rugăm să încercați mai târziu. + Versiunea serverului de destinație %1$s este incompatibilă cu serverul de redirecționare %2$s. + Notificările instantanee sunt dezactivate! + Dezactivare notificări + Migrați de pe un alt dispozitiv + Eroare la ștergerea bazei de date a conversației + Actualizarea bazei de date + este necesară renegocierea criptării + membru + Ștergi conversația? + eroare + Link nevalid + Fișierul va fi primit după ce persoana de contact va finaliza încărcarea. + Interfață italiană + Desktopul este ocupat + Eroare la reconectarea serverului + Eroare la adăugarea membrului/membrilor + Eroare la alăturarea grupului + Ascunde + Listă + Nicio conversație necitită + Nicio conversație în lista %s. + Nimic selectat + deschis + AJUTOR + Doar contactul tău poate trimite mesaje care dispar. + Se importă arhiva + Migrare dispozitiv + nu + oprit` + %ds + Repară criptarea după restaurarea backupurilor. + Ștergeți sau moderați până la 200 de mesaje. + Primirea mesajelor + Eroare la resetarea statisticilor + Eroare la crearea mesajului + Redirecționați mesajul… + Eroare + Importă + Opțiuni de conectate desktop + Eroare la crearea raportului + Adresa de destinație a serverului %1$s este incompatibilă cu setările de redirecționare ale serverului %2$s . + Eroare la trimiterea mesajului + Eroare la schimbarea adresei + Eroare la sincronizarea conexiunii + Eroare + Deconectează + Execuția funcției durează prea mult: %1$d secunde: %2$s + Ascunde + Introduceți parola + Serviciu de notificare + Redirecționare server: %1$s\nEroare server destinatar: %2$s + Fișierul este blocat de operatorul serverului:\n%1$s. + Editează + Info + Redirecționat de la + Ștergi cele %d mesaje ale membrilor? + Rapoarte de membri + Fișierele și conținutul media sunt interzise! + Fișierul nu a fost găsit + Fișier + Operatori de rețea + Draft de mesaje + Parola a fost schimbată! + Activare (păstrați suprascrierile grupului) + Dezactivează (păstrează suprascrierile pentru grupuri) + Activează pentru toate grupurile + Versiunea bazei de date este mai nouă decât app, dar nu există migrare ulterioară pentru: %s + Alătură-te anonim + Deschide + Pentru rutare privată + Activați funcția TCP keep-alive + Fără sunet când este inactiv! + Teme noi pentru conversații + Descentralizarea rețelei + altul + Doar 10 imagini pot fi trimise simultan + Mesajul este prea mare! + Imaginea va fi primită atunci când persoana ta de contact este online. Te rugăm să aștepți sau să verifici mai târziu! + Șterge mesajele conversației de pe dispozitivul tău. + Dacă alegeți să respingeți, expeditorul NU va fi notificat. + Cum se utilizează + Marcare în mesaje + Eroare la salvarea proxy-ului + Dacă confirmi, serverele de mesagerie vor putea vedea adresa ta IP, iar furnizorul tău - la ce servere te conectezi. + Cum ajută la confidențialitate + mesaj duplicat + Invitați prietenii + Să vorbim în SimpleX Chat + Salut!\nConectează-te cu mine prin SimpleX Chat: %s + Pentru social media + Ascunde profilul + Numele afișat nu poate conține spații albe. + Nume nevalid! + Eroare la deschiderea browserului + Cum funcționează + Doar dispozitivele client stochează profiluri de utilizator, contacte, grupuri și mesaje. + Instalat cu succes + Dezactivează + Opțiuni dezvoltator + Notificările vor înceta să funcționeze până când nu redeschideți aplicația + Eroare la salvarea setărilor + Șterge mesajele după + %d sec + Email + Eroare la salvarea parolei utilizatorului + Eroare la salvarea bazei de date + Conectați aplicații mobile și desktop! 🔗 + Dispozitive + Fișierul exportat nu există + Sau distribuiți în siguranță acest link pentru fișier + Statusul mesajului + Niciun telefon conectat + Doar tu poți șterge definitiv mesajele (contactul tău le poate marca pentru ștergere). (24 de ore) + Doar contactul tău poate trimite mesaje vocale. + Găsește conversații mai rapid + Luminos + Nu + Deschizi linkul web? + MESAJE ȘI FIȘIERE + moderator + Rol inițial + Doar proprietarii grupului pot modifica preferințele grupului. + Introdu mesajul de bun venit… + Doar contactul tău poate adăuga reacții la mesaje. + Erori la ștergere + Dezactivați ștergerea mesajelor + Păstrează conversația + Cod QR nevalid + Cod de securitate incorect! + Dispare la: %s + Conectează-te cu datele tale de autentificare + Deschide conversația + Eroare la keychain + Imediat + %d minute + Probleme de rețea - mesajul a expirat după multe încercări de a -l trimite. + Nicio informație despre livrare + Mesajul poate fi transmis mai târziu dacă membrul devine activ. + editat + Se încarcă conversațiile… + Nicio conversație filtrată + Server nou + Asigurați -vă că adresele Serverului WebRTC ICE sunt în format corect, separate pe linii și nu sunt duplicate. + Rutarea mesajelor + Editează imaginea + Deschide setările + Acordare în setări + Instant + Cum afectează bateria + Activare (păstrați suprascrierile) + Activează codul de autodistrugere + MEMBRU + Operator de rețea + Desktop-uri conectate + Eroare la acceptarea membrului + Mesaj text + mesaj nou + Ascunde contactul și mesajul + Cerere de contact nouă + Deschide consola conversației + Autentificarea dispozitivului nu este activată. Puteți activa SimpleX Lock din Setări, după ce activați autentificarea dispozitivului. + Deschideți freastra de migrare + Niciun mesaj + Pentru mine + Eroare server fișiere: %1$s + Pentru toți + Mesajele vor fi marcate pentru ștergere. Destinatarul (destinatarii) va (vor) putea dezvălui aceste mesaje. + Descărcare + Fișierul va fi șters de pe servere. + Alătură-te ca %s + Nicio conversație selectată + Încărcarea fișierului + Eroare la salvarea fișierului + Șterge fără notificare + Deconectat + Repari conexiunea? + Mesaj care dispare + Mai mult + Pentru fiecare server vor fi utilizate acreditări noi SOCKS. + Sau pentru a partaja în privat + Microfon + Acordare permisiuni + Cască + Închide + Criptează fișierele locale + Blochează după + Mod de blocare + Cod de acces nou + Caracteristici experimentale + Este necesară parola bazei de date pentru a deschide conversația. + Introdu parola… + Retrogradează și deschide conversația + Vor fi afișate mesaje de la acești membri! + nici unul + Eroare la eliminarea membrului + indirect (%1$s) + Remedierea nu este acceptată de membrul grupului + Operator + Fă profilul privat! + Deschideți contiții + Nu mai afișa + Permite să ai mai multe conexiuni anonime fără a partaja date între ele într-un singur profil de conversație. + Luminos + Mesajele care dispar sunt interzise în această conversație. + Numai tu poți trimite mesaje care dispar. + %d lună + Mai multe îmbunătățiri vor veni în curând! + Mai multe îmbunătățiri vor veni în curând! + Livrarea mesajelor îmbunătățită + Conexiune mai fiabilă. + luni + Forma mesaj personalizabilă. + Versiunea pentru desktop nu este compatibilă. Asigurați-vă că utilizați aceeași versiune pe ambele dispozitive. + Statistici detaliate + Descărcat + Eroare la reconectarea serverelor + Activat pentru + %d evenimente de grup + %d conversații cu membri + activat + Conexiunea a atins limita de mesaje nelivrate, este posibil ca persoana de contact să fie offline. + Dezactivează blocarea SimpleX + Eroare la salvarea setărilor + Deconectezi desktopul? + Nu activați + Eroare la primirea fișierului + Eroare la ștergerea solicitării de contact + Dezactivați ștergerea automată a mesajelor? + Nu trimiteți istoricul noilor membri. + renegocierea criptării este permisă pentru %s + Mesaje care dispar + Eroare la ștergerea grupului + %d rapoarte + Eroare + Eroare la anularea modificării adresei + Eroare la ștergerea conexiunii de contact în așteptare + criptare agreată pentru %s + Introduceți mesajul de bun venit… (opțional) + Criptează fișierele și conținutul media stocat + Eroare la acceptarea condițiilor + Eroare la activarea confirmărilor de livrare! + Eroare fișier + Eroare la pornirea conversației + Eroare la setarea adresei + Eroare la actualizarea linkului de grup + Eroare la actualizarea confidențialității utilizatorului + Fișier + Imagine salvată în Galerie + Exportați baza de date + apel ratat + Căști + Activați autodistrugerea + Dezactivare primire confirmare? + Activați confirmarea de primire? + Nume nou afișat: + Instrumente pentru dezvoltatori + FIȘIERE + Deschide linkurile din lista de conversații + Forma mesajului + Importați baza de date + Arhivă nouă a bazei de date + Arhivă veche a bazei de date + Deschideți folderul bazei de date + implicit (%s) + Activezi ștergerea automată a mesajelor? + Notificările vor fi livrate doar până când aplicația se oprește! + Baza de date va fi criptată, iar parola va fi stocată în Keystore. + Bază de date criptată + migrare diferită în aplicație/baza de date: %s / %s + Confirmare de migrare nevalidă + Membrul %1$s schimbat la %2$s + Niciun contact de adăugat + Extindeți selecția de roluri + Invitați la conversație + Nou rol de membru + Invitați membrii + Editează profilul grupului + Grupul va fi șters doar pentru tine – această acțiune nu poate fi anulată! + Membrul va fi eliminat din grup - acest lucru nu poate fi anulat! + Membrii vor fi eliminați din chat - acest lucru nu poate fi anulat! + Membrii vor fi eliminați din grup - acest lucru nu poate fi anulat! + Mesajele de la %s vor fi afișate! + inactiv + Eroare la blocarea membrului pentru toți + Profilul grupului este stocat pe dispozitivele membrilor, nu pe servere. + Eroare la salvarea profilului de grup + Textul condițiilor actuale nu a putut fi încărcat, puteți consulta condițiile prin intermediul acestui link: + Eroare la actualizarea serverului + Serverul operator + Șterge profilul de conversație pentru + Introdu parola în câmpul de căutare + Incognito + Modul incognito îți protejează confidențialitatea prin utilizarea unui profil aleatoriu nou pentru fiecare contact. + Numai date despre profilul local + Potrivire + Bună ziua! + Mesaje directe + Mesaje care dispar + Doar persoana ta de contact poate șterge definitiv mesajele (tu le poți marca pentru ștergere). (24 de ore) + Ștergerea definitivă a mesajelor este interzisă în această conversație. + Numai tu poți trimite mesaje vocale. + Reacțiile la mesaje sunt interzise în această conversație. + Numai tu poți efectua apeluri. + Doar contactul tău poate efectua apeluri. + %dh + %d ore + Mesajele directe între membri sunt interzise. + Mesajele directe între membri sunt interzise în această conversație. + Membrii pot trimite linkuri SimpleX. + %d saptamana + oferit %s + oferit %s: %2s + Șterge conversația + Ștergi conversația cu membrul? + Membru se va alătura grupului, acceptați membru? + Ascunde ecranul aplicației din aplicațiile recente. + Nume, avatare și izolare transport diferite. + Interfață franceză + Draft de mesaje + În sfârșit, le avem! 🚀 + Conectare mai rapidă și mesaje mai de încredere. + Interfață în japoneză și portugheză + Interfață de utilizator maghiară și turcă + Redirecționează și salvează mesajele + Activează Flux în setările Rețea și servere pentru o mai bună confidențialitate a metadatelor. + pentru o mai bună confidențialitate a metadatelor. + Redirecționați până la 20 de mesaje simultan. + Nu rata mesajele importante. + Organizează conversațiile în liste + Trimitere mai rapidă a mesajelor. + Ștergere mai rapidă a grupurilor. + ore + Activați + Dispozitive mobile conectate + Introduceți numele acestui dispozitiv… + Eroare + Versiune incompatibilă + Dispozitiv mobil nou + Detectabil prin rețeaua locală + Detectează prin rețeaua locală + Deschide portul în firewall + Desktopul este inactiv + Desktopul a fost deconectat + Desktopul are codul de invitație greșit + Descărcarea a eșuat + Importul a eșuat + Introdu parola + Eroare la descărcarea arhivei + Migrați către un alt dispozitiv + duplicate + Mesaje trimise + Șters + Fișiere descărcate + Erori de descărcare + Găsește această permisiune în setările Android și activeaz-o manual. + Dimensiunea fontului + Dacă nu vă puteți întâlni în persoană, arătați codul QR într-un apel video sau distribuiți linkul. + Cod QR nevalid + Cod de acces incorect + Redirecționați %1$s mesaj/mesaje? + Redirecționarea serverului %1$s nu s-a putut conecta la serverul de destinație %2$s. Vă rugăm să încercați mai târziu. + Ascunde: + Imaginea va fi primită după ce persoana de contact o va termina de încărcat. + Versiunea serverului de redirecționare este incompatibilă cu setările de rețea: %1$s. + Redirecționați mesajele fără fișiere? + Link de conexiune nevalid + Invitația la grup a expirat + Eroare la crearea listei de conversație + Eroare la încărcarea listelor de conversații + Eroare la actualizarea listei de conversații + Notificări instant + Fără parolă pentru aplicație + Favorite + Fișier salvat + Fișierul va fi primit când contactul tău este online. Te rugăm să aștepți sau să verifici mai târziu! + niciun detaliu + Deschide cu %s + Link de invitație unic + Ștergi lista? + Marchează ca verificat + Activează pentru toți + grup șters + criptare ok + invitat + Fără sunet + ajutor + niciun text + Membrii pot trimite mesaje directe. + - opțional, notificați contactele șterse.\n- nume de profil cu spații.\n- și multe altele! + Activează în conversațiile directe (BETA)! + Navigare îmbunătățită în chat + Noile acreditări SOCKS vor fi utilizate de fiecare dată când porniți aplicația. + Vă protejează adresa și conexiunile IP. + Mod luminos + Sursa mesajelor rămâne privată. + Mesaje în direct + Asigurați-vă că configurația proxy este corectă. + Cel mai probabil, această persoană a șters conexiunea cu tine. + Avertizare de livrare a mesajelor + Membrul va fi eliminat din chat - acest lucru nu poate fi anulat! + Mesaje primite + Noua aplicație pentru desktop! + Nu s-au găsit conversații + Fără istoric + Niciun contact selectat + Previzualizare notificări + Fără apeluri în fundal + Nu + Note + Fără servicii în fundal + Gazdele Onion vor fi folosite atunci când sunt disponibile. + Numai tu poți adăuga reacții la mesaje. + Încă nu există conexiune directă, mesajul a fost redirecționat de administrator. + - Deschide conversația la primul mesaj necitit.\n- Mergi la mesajele citate. + Oprit + Deschide setările serverului + criptare ok pentru %s + profilul grupului a fost actualizat + proprietar + Șterge conversația + părăsit + Remedierea nu este suportată de contact + Erori + Deschide grupul + Mesajele vor fi șterse - acest lucru nu poate fi anulat! + Eroare la criptarea bazei de date + Fișiere + Păstrează-ți conexiunile + Membru inactiv + Doar 10 videoclipuri pot fi trimise simultan + Imagine trimisă + mesaj + Favorit + Marchează ca citit + Fără sunet + Nume listă... + Numele listei și emoji ar trebui să fie diferite pentru toate listele. + Află mai multe + Invită + Eroare la inițializarea WebView. Asigurați-vă că aveți WebView instalat și că arhitectura sa suportată este arm64.\nEroare: %s + Stabilește o conexiune privată + Deschide SimpleX Chat pentru a accepta apelul + Activați blocarea + Acest lucru se poate întâmpla atunci când tu sau contactul tău ați folosit un backup vechi al bazei de date. + Cod de acces + Mod incognito + Conexiune de rețea + Statusul rețelei + Asigurați-vă că fișierul are sintaxa YAML corectă. Exportați tema pentru a avea un exemplu de structură de fișiere tematice. + Grupuri incognito + Experiență nouă de conversație 🎉 + Noi opțiuni media + Primiți notificări când sunteți menționați. + Migrare completă + Altul + Migrați aici + Numele complet: + Ajutor pentru marcare + Dacă introduceți această parolă la deschiderea aplicației, toate datele aplicației vor fi șterse ireversibil! + Eroare la modificarea setării + invitație la grup %1$s + Confidențialitate și securitate îmbunătățite + Moderarea grupului + Fișierul a fost șters sau linkul este invalid + Pentru a continua, conversația trebuie oprită. + Extinde + Parola bazei de date este diferită de cea salvată în Keystore. + observator + Ascunde + Eroare la încărcarea detaliilor + Eroare la ștergerea profilului de utilizator + Descărcare + NU trimite mesaje direct, chiar dacă serverul tău sau cel de destinație nu acceptă rutare privată. + Se redirecționează %1$s mesaje + grupul este șters + nesincronizat + Din Galerie + Imagine + (stocat doar de membrii grupului) + Mesaj nou + Link complet + Păstrează + Sau arată acest cod + Eroare la schimbarea profilului + Nicio persoană de contact filtrată + Servere media și fișiere + Alte servere XFTP + NU utilizați rutare privată. + Activează apelurile de pe ecranul de blocare prin Setări. + Dezactivează (păstrează suprascrierile) + Dacă introduceți parola de autodistrugere în timp ce deschideți aplicația: + Mediu + Părăsește conversația + Fără conversații cu membrii + Confirmare de primire a mesajelor! + minute + Conectează un dispozitiv mobil + Creează listă + Șterge + Instalați SimpleX Chat pentru terminal + Eroare la salvarea serverelor ICE + CULORILE INTERFEȚEI + %d fișier(e) cu dimensiunea totală de %s + Fișiere și media + invitat %1$s + Membrii pot șterge definitiv mesajele trimise. (24 de ore) + Membrii pot trimite mesaje ce dispar. + Mesajele directe între membri sunt interzise în acest grup. + Mesajele care dispar sunt interzise. + %dw + Desktop găsit + Se descarcă detaliile linkului + Eroare internă + Sau importă un fișier de arhivă + Sau lipește linkul arhivei + Eroare la inițializarea WebView. Actualizați sistemul la noua versiune. Vă rugăm să contactați dezvoltatorii.\nEroare: %s + Grupul va fi șters pentru toți membrii - această acțiune nu poate fi anulată! + Nume local + Depanați livrarea + Eroare la schimbarea rolului + Reacții la mesaje + expirat + Descărcați %s (%s) + Instalează actualizarea + Nu crea adresă + De exemplu, dacă un contact al tău primește mesaje printr-un server SimpleX Chat, aplicația ta le va trimite printr-un server Flux. + Deschide Setările Safari / Site-uri web / Microfon, apoi selectează Permite pentru localhost. + Apel ratat + Renegocierea criptării a eșuat. + Oprit + Dezactivați confirmări pentru grupuri? + Dezactivează pentru toate grupurile + Dezactivează pentru toți + Deschide linkul + Baza de date va fi criptată, iar parola va fi stocată în setări. + Eroare: %s + Părăsești grupul? + Grupul nu a fost găsit! + Eroare la crearea contactului membrului + Doar proprietarii conversației pot schimba preferințele. + Stare fișier + Starea fișierului: %s + Statusul mesajului: %s + Mesajul prea mare + Reparare + Deschideți modificările + Eroare la adăugarea serverului + Meniuri și alerte + pornit + oprit + activat pentru contact + activat pentru tine + Profiluri de conversație ascunse + Acum administratorii pot:\n- șterge mesajele membrilor.\n- dezactiva membrii (rol de observator) + Reacții la mesaje + Rapid și fără așteptare până când expeditorul este online! + Descoperă și alătură-te grupurilor + Chiar și atunci când este dezactivat în conversație. + - livrare mai stabilă a mesajelor.\n- grupuri mai bune.\n- și multe altele! + Fă un mesaj să dispară + Migrați către un alt dispozitiv prin codul QR. + Ștergeți până la 20 de mesaje odată. + Vrei să te alături grupului tău? + Fără conexiune la rețea + Notificări instant! + Deschide setările aplicației + %d secunde + Activează blocarea SimpleX + Eroare la afișarea notificării, contactați dezvoltatorii. + Eroare de livrare a mesajelor + Eroare server destinație: %1$s + Eroare: %1$s + Pentru toți moderatorii + Redirecționat + Istoric + Ca răspuns la + Mesaj redirecționat + Grupuri + Mesajele au fost șterse după ce le-ați selectat. + Fără conversații + Nimic de redirecționat! + %d conversație(i) + %d mesaje + Eroare de decodare + Fișierele și conținutul media nu sunt permise + Mesaj + membrul are o versiune veche + Renegocierea criptării în desfășurare. + Reparare + Fișier mare! + Mesajele din această conversație nu vor fi niciodată șterse. + Notificări + Doar proprietarii grupului pot activa mesajele vocale. + Editați + Mesaj în direct! + Fără sunet toate + OK + Previzualizare imagine din link + Conversație nouă + Link de invitație unic + Servere mesaje + Alte servere SMP + Introduceți serverul manual + Servere ICE (unul pe linie) + Rețea și servere + Nu utilizați acreditările cu proxy. + Se descarcă actualizarea aplicației, nu închideți aplicația + Oprit + Deschideți locația fișierului + Ieșire fără salvare + Parolă profil ascuns + italic + Cum funcționează SimpleX + Fără identificatori de utilizator. + Acest lucru se poate întâmpla atunci când:\n1. Mesajele au expirat în aplicația de trimitere după 2 zile sau pe server după 30 de zile.\n2. Decriptarea mesajului a eșuat, deoarece tu sau contactul tău ați folosit un backup vechi al bazei de date.\n3. Conexiunea a fost compromisă. + Eroare la exportarea bazei de date a conversației + Eroare la oprirea conversației + niciodată + Niciun fișier primit sau trimis + Parolă nouă… + Criptezi baza de date? + Introdu parola corectă. + Vrei să te alături grupului? + Migrații: %s + Alătură-te + Părăsește + Părăsești conversația? + a părăsit + invitat prin linkul tău de grup + Un membru nou vrea să se alăture grupului. + criptare agreată + renegocierea criptării este permisă + renegocierea criptării este necesară pentru %s + Invită + Moderat la: %s + Numele complet al grupului: + Preferințele grupului + Ștergerea definitivă a mesajelor este interzisă. + Membrii pot adăuga reacții la mesaje. + Membrii pot trimite fișiere și media. + Reacțiile la mesaje sunt interzise. + %d oră + %dm + %d min + %d luni + %dmth + Istoricul nu este trimis noilor membri. + Membrii pot raporta mesaje către moderatori. + proprietari + Admiterea membrilor + oprit + Linkuri de grup + Ștergere definitivă a mesajelor + Măriți dimensiunea fontului. + Deconectează + Deconectat din motivul: %s + Eroare la ștergerea bazei de date + Eroare la încărcarea arhivei + Detalii + alte erori + Eroare la ștergerea conversației cu membrul + Link grup + Remediați conexiunea? + %d săptămâni + Redirecţionează + Mesajul va fi șters - acesta nu poate fi anulat! + Mesajul va fi marcat pentru ștergere. Destinatarul (destinatarii) va (vor) putea dezvălui acest mesaj. + Doar proprietarii grupului pot activa fișiere și media. + Imagine + Permite accesul la cameră + Cum să + Ignoră + Informații despre coada de mesaje + Complet descentralizat – vizibil doar pentru membri. + Introduceți numele grupului: + Umple + Descărcați noi versiuni de pe GitHub. + Dacă ai primit un link de invitație SimpleX Chat, îl poți deschide în browser-ul tău: + Link nevalid! + Nu + Invitați membrii + Nou în %s + Gazdele Onion vor fi necesare pentru conectare.\nRețineți: nu vă veți putea conecta la servere fără adresa .onion. + Nu se vor folosi gazde Onion. + Exportă tema + Importați tema + Eroare la importul temei + Eroare la acceptarea cererii de contact + Descărcați fișierul + Eroare la ștergerea contactului + Eroare la crearea adresei + Eroare la ștergerea notelor private + Eroare la salvarea serverelor + Erori în configurarea serverelor. + Adresa serverului de redirecționare este incompatibilă cu setările rețelei: %1$s. + Nu există servere pentru a trimite fișiere. + Mod de rutare a mesajelor + Niciodată + Întoarce camera + Link nevalid + în așteptarea aprobării + Notificări periodice + Notificările periodice sunt dezactivate! + Lipește linkul arhivei + Se pregătește descărcarea + Lipește + Vă rugăm să rugați persoana de contact să activeze apelurile. + Rutare privată + Confidențialitatea redefinită + Vă rugăm să verificați conexiunea la rețea cu %1$s și să încercați din nou. + Rutare mesaje private 🚀 + Bara de instrumente pentru conversație, accesibilă + Redă din lista de conversații. + Vă rugăm să încercați mai târziu. + Citeşte mai mult + Te rog să verifici dacă linkul SimpleX este corect. + Vă rugăm să contactați administratorul grupului. + Se pregătește încărcarea + Acces refuzat! + Vă rugăm să reduceți dimensiunea mesajului și să îl trimiteți din nou. + În așteptare + peer-to-peer + Vă rugăm să reporniți aplicația. + Mesaje primite + Lipește linkul pentru conectare! + Interziceți reacțiile la mesaje. + Interzicerea trimiterii de linkuri SimpleX + Lipește linkul primit pentru a te conecta cu contactul tău… + Te rugăm să-i ceri persoanei tale de contact să activeze trimiterea de mesaje vocale. + Cod QR + portul %d + Periodic + Servere presetate + Reține parola sau păstreaz-o în siguranță – nu există nicio modalitate de a o recupera dacă o pierzi! + Confidențialitate și securitate + Protejează ecranul aplicației + Imagini de profil + Te rugăm să stochezi parola în siguranță, altfel NU vei putea accesa conversația dacă o pierzi. + Interziceți trimiterea de mesaje vocale. + Apeluri cu funcție picture-in-picture + Interfață persană + Prin proxy + Conexiuni de profil și server + răspuns primit… + Politica de confidențialitate și condițiile de utilizare. + RUTAREA MESAJELOR PRIVATE + Te rugăm să stochezi parola în siguranță, altfel NU o vei putea schimba dacă o pierzi. + Parola nu a fost găsită în Keystore. Te rugăm să o introduci manual. Acest lucru s-ar putea întâmpla dacă ai restaurat datele aplicației folosind un instrument de backup. Dacă nu este cazul, te rugăm să contactezi dezvoltatorii. + Parola stocată în Keystore nu poate fi citită. Acest lucru se poate întâmpla după o actualizare a sistemului incompatibilă cu aplicația. Dacă nu este cazul, te rugăm să contactezi dezvoltatorii. + Membru %1$s + în așteptare + recenzie în așteptare + Primit la: %s + Expirare protocol + Număr de PING-uri + Parola profilului + Interziceți reacțiile la mesaje. + Interzice ștergerea definitivă a mesajelor. + Interziceți trimiterea de mesaje directe către membri. + Interziceți trimiterea de mesaje care dispar. + Interziceți raportarea mesajelor către moderatori. + Interziceți trimiterea de mesaje vocale. + Păstrează ultima schiță a mesajului, cu atașamente. + Nume de fișiere private + Criptare rezistentă cuantic + Confidențialitate pentru clienții tăi. + Nume de fișiere media private. + Aleatoriu + Vă rugăm să raportați dezvoltatorilor:\n%s\n\nSe recomandă repornirea aplicației. + Vă rugăm să confirmați că setările de rețea sunt corecte pentru acest dispozitiv. + Este necesară o parolă + Parolă + Servere presetate + Lipiți adresa desktopului + Este posibil ca amprenta certificatului din adresa serverului să fie incorectă + Interval PING-uri + Expirare protocol per KB + Notificări private + Actualizarea profilului va fi trimisă contactelor tale. + Interziceți apelurile audio/video. + Nume profil: + Autentificare proxy + Parolă de afișat + Vă rugăm să reduceți dimensiunea mesajului sau să eliminați fișierul media și să îl trimiteți din nou. + Protejați adresa IP + Mesaj primit + Note private + Bara de instrumente a conversației + Temă de profil + Lipește linkul pe care l-ai primit + Parola nu a fost modificată! + Parolă setată! + Parola stocată în Keystore nu poate fi citită. Te rugăm să o introduci manual. Acest lucru se poate întâmpla după o actualizare a sistemului incompatibilă cu aplicația. Dacă nu este cazul, te rugăm să contactezi dezvoltatorii. + Bări de instrumente ale aplicației, accesibile + Vă rugăm să așteptați ca moderatorii grupului să vă examineze cererea de alăturare. + Interziceți trimiterea de mesaje care dispar. + Interziceți trimiterea de fișiere și fișiere media. + Vă rugăm să raportați dezvoltatorilor:\n%s + Servere proxy + Apel în așteptare + Vă rugăm să raportați dezvoltatorilor. + Te rugăm să introduci parola anterioară după restaurarea backupului bazei de date. Această acțiune nu poate fi anulată. + criptare e2e rezistentă cuantic + Chitanțele sunt dezactivate + Primit la + Interfață poloneză + Protejează-ți adresa IP de releele de mesagerie alese de contactele tale.\nActivează din setările *Rețea și servere*. + Mesaj primit + Vă rugăm să așteptați cât timp fișierul se încarcă de pe dispozitivul mobil conectat + imagine de profil + substituent pentru imaginea de profil + Lipește linkul + Adresa prestabilită a serverului + Server presetat + Evaluează aplicația + Port + Conversațiile private, grupurile și contactele tale nu sunt accesibile operatorilor serverului. + O parolă aleatorie este stocată în setări, ca text simplu.\nO poți schimba ulterior. + Te rugăm să introduci parola actuală\ncorectă. + Previzualizare + Te rugăm să verifici dacă dispozitivul mobil și cel desktop sunt conectate la aceeași rețea locală și dacă firewall-ul de pe desktop permite conexiunea.\nTe rugăm să raportezi orice alte probleme dezvoltatorilor. + Servere conectate anterior + În așteptare + confirmare primită… + Protejează-ți profilurile de conversații cu o parolă! + Te rog să verifici dacă ai folosit linkul corect sau cere contactului tău să îți trimită unul nou. + Eroare de rutare privată + respins + Răspuns primit + Destinatarul/Destinatarii nu pot vedea de la cine provine acest mesaj. + Trimiterea confirmărilor este dezactivată pentru %d contacte + primit, interzis + Reamintește mai târziu + Server adăugat la operatorul %s. + Setați mesajul afișat noilor membri! + Setați-l în locul autentificării sistemului. + Setează o parolă pentru export + Raportați conținutul: doar moderatorii grupului îl vor vedea. + Adresa serverului + Primirea fișierului va fi oprită. + Resetează la tema aplicației + Înregistrare actualizată la: %s + Trimiteți un mesaj pentru a activa apelurile. + Resetare + Rapoarte + informații despre coada serverului: %1$s\n\nultimul mesaj primit: %2$s + Salvează și reconectează-te + Raportați altceva: doar moderatorii grupului îl vor vedea. + caută + Setează numele conversației… + Serverul de retransmisie este utilizat doar dacă este necesar. O altă parte poate observa adresa ta IP. + Resetezi toate statisticile? + Resetați toate sugestiile + Trimite mesaje direct atunci când adresa IP este protejată, iar serverul tău sau cel de destinație nu acceptă rutare privată. + Parola de autodistrugere activată! + Setați parola + Examinați condițiile + Raport + Adresa de primire va fi schimbată la un alt server. Schimbarea adresei se va finaliza după ce expeditorul se conectează online. + Salvezi setările de acces? + Revizuiți mai târziu + Trimiterea confirmărilor este dezactivată pentru %d grupuri + recenzie + Operatorul serverului s-a schimbat. + Scalare + Utilizare redusă a bateriei + Mesaje trimise + Primit total + Primiți erori + Reconectați serverul pentru a forța livrarea mesajului. Consumă trafic suplimentar. + Resetează toate statisticile + Trimis prin proxy + Trimis direct + revizuit de administratori + Operatori de server + Serverul de retransmisie protejează adresa IP, dar poate observa durata apelului. + PORNIȚI CHATUL + te-a eliminat + %s la %s + Protocolul serverului a fost modificat. + Recepție simultană + Resetează la tema utilizatorului + Setați admiterea membrilor + Respinge + Respingeți membrul? + Destinatarii văd actualizările pe măsură ce le tastați. + Istoric recent și bot de directoare îmbunătățit. + Setează expirarea mesajelor în conversații. + Trimiterea confirmărilor de livrare va fi activată pentru toate contactele. + Trimiterea confirmărilor de livrare va fi activată pentru toate contactele din toate profilurile de conversații vizibile. + Securizat + Selectat %d + Trimiterea confirmărilor este activată pentru %d contacte + Informații despre servere + Reconectați serverele? + Statisticile serverelor vor fi resetate - această acțiune nu poate fi anulată! + Reconectează toate serverele + Reconectați serverul? + Total trimise + Preferințele conversației selectate interzic acest mesaj. + Trimiterea confirmărilor este activată pentru %d grupuri + Parolă de autodistrugere + Trimiteți rapoarte private + Trimite mesaje direct atunci când serverul tău sau cel de destinație nu acceptă rutarea privată. + eliminat din grup + cererea de înscriere a fost respinsă + Selectează profilul de conversație + Salvează setările adresei SimpleX + Salvează lista + TRIMITE CONFIRMĂRI DE LIVRARE LA + Parola de autodistrugere a fost schimbată! + Înregistrare actualizată la + Selectează operatorii de rețea de utilizat. + Telefoane mobile la distanță + Trimite confirmări de citire + Setați 1 zi + Parolă de autodistrugere + Reconectează toate serverele conectate pentru a forța livrarea mesajelor. Aceasta folosește trafic suplimentar. + Se primesc mesaje… + Motivul raportării? + respins + Raport: %s + Se salvează %1$s mesaje + Raport trimis moderatorilor + Raportați spam: numai moderatorii grupului îl vor vedea. + Raportați profilul membrului: numai moderatorii grupului îl vor vedea. + Raportați încălcarea: numai moderatorii grupului o vor vedea. + Înregistrați mesajul vocal + Scanează / Lipește link + Server + Repornește aplicația pentru a folosi baza de date de conversații importată. + Eliminați membri? + Primirea prin intermediul + Raportarea mesajelor este interzisă în acest grup. + Examinați membrii + Verificați membrii înainte de a-i primi (a bate la ușă). + Eliminați arhiva? + Trimiteți erorile + Reconectați-vă + Selectează + Adresa serverului este incompatibilă cu setările de rețea: %1$s. + Coadă securizată + Este posibil ca expeditorul să fi șters cererea de conectare. + Versiunea serverului este incompatibilă cu aplicația ta: %1$s. + Deblocare + Încărcarea a eșuat + Aplicația se poate închide după 1 minut în fundal. + Cu sunet + Această setare se aplică mesajelor din profilul de conversație actual + Folosiți servere SimpleX Chat? + Proxy SOCKS + Nume de utilizator + SimpleX Chat și Flux au ajuns la un acord pentru a include în aplicație servere operate de Flux. + Aplicația preia mesajele noi periodic și consumă câteva procente din baterie pe zi. Aplicația nu folosește notificări push, așadar datele de pe dispozitivul tău nu sunt trimise către servere. + Blocare SimpleX + Cheie greșită sau adresă necunoscută a fragmentului de fișier - cel mai probabil fișierul este șters. + Prea multe videoclipuri! + Videoclip trimis + Oprește partajarea + Servere ICE WebRTC + Actualizați și deschideți chatul + Fișiere încărcate + Folosește profilul curent + Folosește noul profil incognito + Vizualizarea a eșuat + Funcție lentă + Blocare SimpleX + Deblocați pentru toți + Deblocați membrii pentru toate? + Actualizare + Până la 100 de mesaje recente sunt trimise noilor membri. + Ce este nou + Mulțumim utilizatorilor – contribuiți prin Weblate! + Mulțumim utilizatorilor – contribuiți prin Weblate! + Cu un consum redus de energie al bateriei. + Cu un consum redus de energie al bateriei. + Începând cu %s.\nToate datele sunt păstrate confidențiale pe dispozitivul tău. + Abonat + Erori de încărcare + Apel video + Pentru a vă conecta prin link + Mesajele vocale nu sunt permise + Neprotejat + Da + Actualizare + Aveți deja un profil de chat cu același nume afișat. Vă rugăm să alegeți un alt nume. + Acest nume afișat este invalid. Vă rugăm să alegeți un alt nume. + Mesajele vor fi șterse pentru toți membrii. + Folosiți serverul + Sistem + Se încearcă conectarea la serverul folosit pentru a primi mesaje de la acest contact. + Ești conectat la serverul folosit pentru a primi mesaje de la acest contact. + Mulțumim utilizatorilor – contribuiți prin Weblate! + Poți să încerci încă o dată. + Ești deja conectat la %1$s. + Cheie greșită sau conexiune necunoscută - cel mai probabil această conexiune este ștearsă. + Video + Mesaj vocal (%1$s) + Trimite + Comută lista de conversații: + Pentru a primi notificări, te rugăm să introduci parola bazei de date + Se așteaptă videoclipul + Mesaje nelivrate + Testul a eșuat la pasul %s. + Opriți fișierul + trimitere neautorizată + Bun venit, %1$s! + Atinge pentru a începe o conversație nouă + Folosește acreditări proxy diferite pentru fiecare conexiune. + Adresă SimpleX sau link unic? + Pentru a-ți proteja confidențialitatea, SimpleX folosește ID-uri separate pentru fiecare persoană de contact. + Ai fost invitat în grup + Pentru a primi + Utilizare pentru fișiere + Pentru a trimite + Temă + Utilizați aplicația cu o singură mână. + Videoclipul va fi primit după ce persoana de contact termină de încărcat. + Videoclipul va fi primit atunci când persoana ta de contact este online. Te rugăm să aștepți sau să verifici mai târziu! + video + Utilizarea serverelor SimpleX Chat. + Folosiți proxy SOCKS? + Browserul web implicit este necesar pentru apeluri. Vă rugăm să configurați browserul implicit în sistem și să partajați mai multe informații cu dezvoltatorii. + Afișează statusul mesajului + Port TCP pentru mesagerie + Descărcarea actualizării a fost anulată + Omiteți invitarea membrilor + Începând de la %s. + Se așteaptă conectarea mobilului: + Server XFTP + Această acțiune nu poate fi anulată - profilul, contactele, mesajele și fișierele tale vor fi pierdute definitiv. + Sistem + Deblocați membrul? + Cu sunet + Mesaje vocale + Statistici + Opriți chatul? + necunoscut + Poți ascunde sau dezactiva notificările unui profil de utilizator – ține apăsat pentru meniu. + Ați permis + Partajează linkul unic cu un prieten + Mesaje vocale + Mesaj vocal… + Setări proxy SOCKS + profil actualizat + Mod Blocare SimpleX + Mesajele vor fi marcate ca moderate pentru toți membrii. + necitit + Bun venit! + Atingeți pentru conectare + Mesajele vocale sunt interzise! + Ați acceptat conexiunea + Partajează adresa public + Partajează acest link de invitație unic + Pentru a verifica criptarea end-to-end, compară (sau scanează) codul de pe dispozitivele voastre cu persoana de contact. + Utilizare pentru conexiuni noi + Utilizați proxy SOCKS + Oprești partajarea adresei? + Când aplicația rulează + Aplicația vă protejează confidențialitatea utilizând operatori diferiți în fiecare conversație. + Vizualizează condițiile + Afișează doar contactul + Deblocare + Porniți + Oprește chatul + Acest mesaj a fost șters sau nu a fost încă primit. + Mesajul va fi marcat ca moderat pentru toți membrii. + Stop + Opriți primirea fișierului? + Se așteaptă fișierul + Această acțiune nu poate fi anulată - mesajele trimise și primite în această conversație, mai vechi decât data selectată, vor fi șterse. + Vizualizează codul de securitate + Atinge pentru a lipi linkul + Textul pe care l-ai lipit nu este un link SimpleX. + Stabil + Puteți configura operatorii în setările Rețea și servere. + Actualizare + apel video (necriptat e2e) + ID-ul următorului mesaj este incorect (mai mic sau egal cu precedentul).\nSe poate întâmpla din cauza unei erori sau când conexiunea este compromisă. + Fără Tor sau VPN, adresa ta IP va fi vizibilă pentru serverele de fișiere. + Aplicația va cere să confirmați descărcările de pe servere de fișiere necunoscute (cu excepția celor .onion sau când proxy-ul SOCKS este activat). + Sistem + Acestea pot fi ignorate în setările de contact și de grup. + SUPORT SIMPLEX CHAT + PROXY SOCKS + TEME + În timpul importului au apărut câteva erori non-fatale: + Atingeți pentru a vă alătura + Atenție: este posibil să pierdeți unele date! + Folosiți serverele + Reafișează profilul de chat + Titlu + Transparenţă + Videoclipuri și fișiere de până la 1 GB + Comută modul incognito la conectare. + Formă imagine de profil + Schimbă profilul de conversații pentru invitații unice. + Puteți activa mai târziu prin Setări + săptămâni + Acesta este linkul tău unic, valabil o singură dată! + Servere necunoscute! + Fără Tor sau VPN, adresa ta IP va fi vizibilă pentru aceste retransmiteri XFTP:\n%1$s. + Actualizează parola bazei de date + Opriți trimiterea fișierului? + tăiere + Viitorul mesageriei + Pentru a efectua apeluri, permiteți utilizarea microfonului. Încheiați apelul și încercați să sunați din nou. + Când sunt activați mai mulți operatori, niciunul dintre ei nu are metadate pentru a afla cine comunică cu cine. + Video pornit + Aceste setări sunt pentru profilul tău actual + Da + Coada + Utilizare de pe desktop + Oprește conversațiile pentru a exporta, importa sau șterge baza de date a conversațiilor. Nu vei putea primi și trimite mesaje cât timp conversațiile sunt oprite. + Unele fișiere nu au fost exportate + Parola este stocată în setări ca text simplu. + deblocat %s + status necunoscut + tu: %1$s + Mesaj de bun venit + Deblocați membrul + Deblocați membrul pentru toate? + Comută + Rolul va fi schimbat în %s. Toți participanții la chat vor fi notificați. + Rolul va fi schimbat în %s. Toți membrii grupului vor fi notificați. + Folosește %s + Site web + %s Servere + Actualizați setările de rețea? + Atingeți pentru a activa profilul. + Sistem + Mulțumim utilizatorilor – contribuiți prin Weblate! + Verifică securitatea conexiunii + Izolarea transportului + Pentru a proteja fusul orar, fișierele imagine/voce utilizează UTC. + - mesaje vocale de până la 5 minute.\n- timp personalizat de dispariție.\n- istoricul modificărilor. + Pentru a ascunde mesajele nedorite. + Prin protocol securizat de rezistență cuantică. + Cu fișiere și media criptate. + Va fi activat în conversațiile directe! + Al doilea operator presetat din aplicație! + Vizualizați condițiile actualizate + Verificați conexiunea + Le poți activa mai târziu din setările de Confidențialitate și securitate ale aplicației. + Verificați codul pe mobil + Numele acestui dispozitiv + Acest dispozitiv + Deconectare + Pentru a permite unei aplicații mobile să se conecteze la desktop, deschideți acest port în firewall, dacă îl aveți activat + Verificați codul cu desktopul + Această funcție nu este încă compatibilă. Încercați următoarea versiune. + Vă conectați deja prin intermediul acestei legături unice! + Atenție: inițierea chatului pe mai multe dispozitive nu este acceptată și va cauza erori de livrare a mesajelor. + Verifică parola bazei de date + Ethernet prin cablu + Abonamente ignorate + Mărime + Poți menționa până la %1$s membri per mesaj! + Blocarea SimpleX este activată + Verifică codul de securitate + Criptarea funcționează și noul acord de criptare nu este necesar. Poate duce la erori de conexiune! + Expirare conexiune TCP + Afișează + Se afișează informații pentru + Mesajul va fi șters pentru toți membrii. + Vă mulțumim pentru instalarea SimpleX Chat! + Partajează profil + Eroare necunoscută + Sesiuni de transport + Când IP-ul este ascuns + Sunet oprit + Reafișează profilul + Accent imagine de fundal + Arhiva bazei de date încărcată va fi eliminată definitiv de pe servere. + WiFi + Oprire + Mesaje ignorate + Parola va fi stocată în setări ca text simplu după ce o schimbi sau repornești aplicația. + Conexiune TCP + Erori de abonare + Atingeți pentru a vă conecta incognito + Imaginea nu poate fi decodificată. Vă rugăm să încercați o altă imagine sau să contactați dezvoltatorii. + Serverele pentru fișierele noi ale profilului tău de conversații actual + Acest grup nu mai există. + Se încearcă conectarea la serverul folosit pentru a primi mesaje de la acest contact (eroare: %1$s). + Actualizarea setărilor va reconecta clientul la toate serverele. + Acest șir de caractere nu este un link! + S-a atins timpul de expirare la conectarea la desktop + Pentru a te proteja împotriva înlocuirii linkului tău, poți compara codurile de securitate ale contactelor. + Pentru a-ți proteja informațiile, activează Blocare SimpleX.\nVa trebui să finalizezi autentificarea înainte de a activa această funcție. + Dacă persoana ta de contact nu a șters conexiunea sau dacă acest link a mai fost folosit, este posibil să fie o eroare. Te rugăm să o raportezi.\nPentru a te conecta, roagă persoana ta de contact să creeze un nou link și asigură-te că ai o conexiune stabilă la internet. + Când partajezi un profil incognito cu cineva, acest profil va fi folosit pentru grupurile la care te invită. + Mesaj vocal + La conectarea apelurilor audio și video. + Când oamenii solicită conectarea, poți accepta sau respinge solicitarea. + Acest grup are peste %1$d membri, confirmările de livrare nu sunt trimise. + Comutați între audio și video în timpul apelului. + Deconectați desktopul? + Se așteaptă desktopul… + Prea multe imagini! + Încarcă fișier + Atinge butonul + Atingeți pentru a scana + (pentru a partaja cu persoana de contact) + Pentru a începe o nouă conversație + Video + Partajează link unic + Acest link nu este un link de conectare valid! + Acest cod QR nu este un link! + Link scurt + Server XFTP + Actualizați modul de izolare a transportului? + Folosește rutare privată cu servere necunoscute. + Folosește rutare privată cu servere necunoscute atunci când adresa IP nu este protejată. + Folosiți portul TCP %1$s atunci când nu este specificat niciun port. + Nu stocăm niciunul dintre contactele sau mesajele tale (odată livrate) pe servere. + prin releu + Soft + Puternic + A doua ticăitură pe care am ratat-o! ✅ + Mulțumim utilizatorilor – contribuiți prin Weblate! + Afișează procentul + Serverele pentru noile conexiuni ale profilului tău de conversație actual + Afișează apeluri API lente + Pentru a fi notificat despre noile versiuni, activați verificarea periodică pentru versiunile Stabile sau Beta. + Folosește o parolă aleatorie + Această acțiune nu poate fi anulată - toate fișierele și fișierele media primite și trimise vor fi șterse. Imaginile cu rezoluție mică vor rămâne. + Îl poți modifica în setările Aspect. + ați acceptat acest membru + Istoric vizibil + Te alături deja grupului prin intermediul acestui link. + Server SMP + Servere necunoscute + Folosește aplicația în timpul apelului. + Numele dispozitivului va fi partajat cu clientul mobil conectat. + Încărcat + Folosiți portul TCP 443 doar pentru serverele presetate. + Omiteți această versiune + O poți crea mai târziu + O poți face vizibilă contactelor tale SimpleX din Setări. + Platforma de mesagerie și aplicații care vă protejează confidențialitatea și securitatea. + Profilul este partajat doar cu contactele tale. + Pentru a-ți dezvălui profilul ascuns, introdu parola completă în câmpul de căutare de pe pagina Profilurile tale de conversație. + Puteți configura serverele prin intermediul setărilor. + apel video + Video dezactivat + Parolă greșită pentru baza de date + Eroare necunoscută în baza de date: %s + l-ai blocat pe %s + Rolul va fi schimbat în %s. Membrul va primi o nouă invitație. + Mesaj de bun venit + Mesajul de bun venit este prea lung + Imagine de fundal + Mod sistem + da + Mesajele vocale sunt interzise în acest chat. + Cu mesaj opțional de bun venit. + Suportă Bluetooth și alte îmbunătățiri. + Aceasta este propria ta adresă SimpleX! + Se încarcă arhiva + %s a fost încărcat + Verifică parola + Blocarea SimpleX nu este activă! + Autentificare sistem + Baza de date nu funcționează corect. Atingeți pentru a afla mai multe. + Raportul va fi arhivat. + Atingeți Creează o adresă SimpleX în meniu pentru a o crea mai târziu. + Acest text este disponibil în setări + Ești invitat în grup + Se așteaptă imaginea + Se așteaptă imaginea + Se așteaptă videoclipul + Eroare temporară de fișier + Elimină din favorite + Mențiuni necitite + Conexiunea pe care ați acceptat-o va fi anulată! + Contactul cu care ați partajat acest link NU se va putea conecta! + vrea să se conecteze cu tine! + Partajează adresa SimpleX pe rețelele de socializare. + Pentru a te conecta, persoana ta de contact poate scana codul QR sau poate folosi linkul din aplicație. + Poți seta numele conexiunii, ca să-ți amintești cu cine ai partajat linkul. + Adresa SimpleX și linkurile unice pot fi partajate în siguranță prin orice aplicație de mesagerie. + Codul scanat nu este un cod QR de tip link SimpleX. + Server de testare + Servere de testare + Folosește credențiale aleatorii + Actualizare disponibilă: %s + Folosește chatul + Hash-ul mesajului anterior este diferit. + Această acțiune nu poate fi anulată - mesajele trimise și primite anterior celei selectate vor fi șterse. Poate dura câteva minute. + Parolă greșită! + Încercarea de a schimba parola bazei de date nu a fost finalizată. + Ai fost invitat(ă) în grup. Alătură-te pentru a te conecta cu membrii grupului. + profil de grup actualizat + Condiții actualizate + Utilizați pentru mesaje + Mesajele vocale sunt interzise. + Protocoalele SimpleX analizate de Trail of Bits. + Acest link a fost utilizat cu un alt dispozitiv mobil, vă rugăm să creați un link nou pe desktop. + Verifică conexiunile + Poți să încerci încă o dată. + Nu ești conectat la aceste servere. Pentru livrarea mesajelor către ele se folosește rutarea privată. + Se opresc conversațiile + Total + ești observator + Videoclipul nu poate fi decodificat. Vă rugăm să încercați un alt videoclip sau să contactați dezvoltatorii. + Puteți copia și micșora dimensiunea mesajului pentru a-l trimite. + aștept răspunsul… + aștept confirmarea… + Actualizați aplicația automat + Folosește acreditări proxy diferite pentru fiecare profil. + Folosești conexiune directă la internet? + Poți trimite mesaje către %1$s din contactele arhivate. + Pentru a vă proteja adresa IP, rutarea privată utilizează serverele SMP pentru a livra mesaje. + Izolarea transportului + Folosește gazde .onion + Când este disponibil + Link canal SimpleX + Spam + Acest link necesită o versiune mai nouă a aplicației. Vă rugăm să actualizați aplicația sau să solicitați persoanei de contact să vă trimită un link compatibil. + Legătură de conexiune neacceptată + Utilizați portul web + %s.]]> + %s]]> + Arhivare raport + Conversația va fi ștearsă doar pentru tine - această acțiune nu poate fi anulată! + Utilizarea bateriei aplicației / Nerestricționat în setările aplicației.]]> + Ghidul utilizatorului.]]> + V-ați alăturat acestui grup. Se conectează la membrul invitat al grupului. + Profilul tău va fi trimis persoanei de contact de la care ai primit acest link. + Te vei conecta cu toți membrii grupului. + Toate mesajele noi de la acești membri vor fi ascunse! + Profilul tău aleatoriu + (acest dispozitiv v%s)]]> + %s are o versiune neacceptată. Asigurați-vă că utilizați aceeași versiune pe ambele dispozitive]]> + %1$s.]]> + Conexiuni active + Conversații de afaceri + l-ai blocat pe %s + Bucăți șterse + Serverele tale ICE + ai eliminat %1$s + Preferințele tale + Adaugă listă + Toate + Puteți vizualiza în continuare conversația cu %1$s în lista de conversații. + Condiții acceptate + %s.]]> + %1$d erori de fișier:\n%2$s + Conexiune blocată + Conexiunea este blocată de operatorul serverului:\n%1$s. + criptate end-to-end, cu securitate post-cuantică în mesajele directe.]]> + Bările de instrumente ale aplicației + Securitate îmbunătățită ✅ + Toate conversațiile vor fi eliminate din lista %s, iar lista va fi ștearsă + Conexiunea ta a fost mutată la %s, dar a apărut o eroare la schimbarea profilului. + Puteți migra baza de date exportată. + Serverele tale XFTP + Utilizați gazdele .onion la Nu dacă proxy-ul SOCKS nu le acceptă.]]> + Profilul tău de chat va fi trimis membrilor chatului + Vei primi în continuare apeluri și notificări de la profilurile dezactivate atunci când acestea sunt active. + Întreabă + %s.]]> + Schimbați ștergerea automată a mesajelor? + Adresă sau link unic? + Sesiune de aplicație + Contactele tale + Datele tale de conectare pot fi trimise necriptat. + Adresă de afaceri + Adaugă membrii echipei tale în conversații. + Verifică mesajele la fiecare 10 minute + %s, acceptați condițiile de utilizare.]]> + Confirmă-ți datele de autentificare + Schimbă profilurile de conversație + Contacte + Conexiunea necesită renegocierea criptării. + Adresa ta SimpleX + Creează link unic + Profilul tău actual + Aplicația rulează întotdeauna în fundal + acceptat %1$s + %s.]]> + Condițiile vor fi acceptate pe: %s. + Servere media și de fișiere adăugate + Estompare + - conectare la serviciul de directoare (BETA)!\n- confirmări de livrare (până la 20 de membri).\n- mai rapid și mai stabil. + Arhivează contactele pentru a conversa mai târziu. + %1$s.]]> + Ai partajat o cale de fișier nevalidă. Raportează problema dezvoltatorilor aplicației. + Deschide în aplicația mobilă, apoi atinge Conectare în aplicație.]]> + Actualizează adresa + Configurați operatorii serverului + Condițiile vor fi acceptate pentru operatorii activați după 30 de zile. + Apasă pe butonul de informații de lângă bara de adrese pentru a permite accesul la microfon. + Colţ + Puteți salva arhiva exportată. + Nu veți mai primi mesaje de la acest chat. Istoricul conversației va fi păstrat. + Nu veți mai primi mesaje de la acest grup. Istoricul conversației va fi păstrat. + Încercați să invitați persoana de contact cu care ați partajat un profil incognito în grupul în care utilizați profilul principal + Poți partaja un link sau un cod QR - oricine se va putea alătura grupului. Nu vei pierde membrii grupului dacă îl ștergi ulterior. + Blocați membrii pentru toți? + Conversație + %s.]]> + Condiții acceptate pe: %s. + Condițiile vor fi acceptate automat pentru operatorii activați pe: %s. + %s.]]> + %s.]]> + %s.]]> + Condiții de utilizare + Permite raportarea mesajelor către moderatori. + Conversează cu membrii + Convorbire cu administratori + Acceptă + Acceptă membru + Date de mesaj îmbunătățite. + Performanță îmbunătățită a grupurilor + Confidențialitate și securitate îmbunătățite + %s este inactiv]]> + %s lipsește]]> + %1$s!]]> + Confirmat + Erori de confirmare + Vi se va solicita să vă autentificați când porniți sau reluați aplicația după 30 de secunde în fundal. + să te conectezi la dezvoltatorii SimpleX Chat pentru a le adresa orice întrebări și pentru a primi actualizări.]]> + Adaugă prieteni + Adaugă membri echipei + Conversația există deja! + Deschide în aplicația mobilă.]]> + Continuă + %s a fost deconectat]]> + Despre operatori + %s este ocupat]]> + Nu puteți trimite mesaje! + ați ieșit + nu se pot trimite mesaje + contact șters + contact dezactivat + contactul nu este gata + Profilul, contactele și mesajele livrate sunt stocate pe dispozitiv. + Serverele tale ICE + Adaugă la listă + Modificați lista + invitație acceptată + Îți poți partaja adresa sub formă de link sau cod QR - oricine se poate conecta cu tine. + Acceptă + Confidențialitatea ta + Acceptă condițiile + Zoom + %1$s.]]> + %1$s.]]> + Bucăți încărcate + Arhivați toate rapoartele? + Arhivați %d rapoarte? + Arhivare raport? + Dispozitive Xiaomi: vă rugăm să activați Pornirea automată în setările de sistem pentru ca notificările să funcționeze.]]> + Arhivă + Pictogramă de context + Poți vizualiza rapoartele tale în conversația cu administratorii. + Schimbă ordinea + Te vei conecta atunci când dispozitivul contactului tău va fi online, te rugăm să aștepți sau să verifici mai târziu! + scana codul QR în timpul apelului video sau contactul tău poate partaja un link de invitație.]]> + Profilul tău %1$s va fi distribuit. + Nu îți vei pierde contactele dacă ulterior îți ștergi adresa. + Securitatea conexiunii + Serverele tale SMP + Toate serverele + Profilul tău este stocat pe dispozitiv și este partajat doar cu contactele tale. Serverele SimpleX nu pot vedea profilul tău. + Baza ta de date actuală de chat va fi ȘTERSĂ și ÎNLOCUITĂ cu cea importată.\nAceastă acțiune nu poate fi anulată - profilul, contactele, mesajele și fișierele tale vor fi pierdute iremediabil. + Convorbire cu administratori + Poți partaja această adresă cu contactele tale pentru a le permite să se conecteze cu %s. + Conversează cu membrul + Cererea de conectare va fi trimisă acestui membru al grupului. + Servere de mesaje adăugate + %s cu motivul: %s]]> + %s a fost deconectat]]> + (nou)]]> + Serverul tău + Trebuie să îi permiți contactului tău să te sune pentru a-l putea suna. + Adresa serverului tău + Serverele tale + Vei fi conectat la grup atunci când dispozitivul gazdei grupului va fi online, te rugăm să aștepți sau să verifici mai târziu! + Tu decizi cine se poate conecta. + Vei fi conectat când cererea ta de conectare va fi acceptată, te rugăm să aștepți sau să verifici mai târziu! + Acceptați ca observator + Experiență de utilizare îmbunătățită + 1 chat cu un membru + Toate rapoartele vor fi arhivate pentru tine. + depozitul nostru GitHub.]]> + Permiteți în următoarea fereastră de dialog să primească notificări instantaneu.]]> + Setări adresă + Arhivare rapoarte + Acceptă ca membru + toți + Afaceri + Prin utilizarea SimpleX Chat ești de acord să:\n- trimiți doar conținut legal în grupurile publice.\n- respecți ceilalți utilizatori – fără spam. + doar cu un singur contact - partajează-l personal sau prin orice altă aplicație de mesagerie.]]> + Nu trebuie să utilizați aceeași bază de date pe două dispozitive. + arătați codul QR în apelul video sau distribuiți linkul.]]> + Utilizare de pe desktop în aplicația mobilă și scanează codul QR.]]> + SimpleX rulează în fundal în loc să utilizeze notificări push.]]> + Utilizarea bateriei aplicației / Nerestricționat în setările aplicației.]]> + Bucăți descărcate + Conversația va fi ștearsă pentru toți membrii - această acțiune nu poate fi anulată! + Conexiunea nu este gata. + Conținutul încalcă condițiile de utilizare + Folosești un profil incognito pentru acest grup - pentru a preveni partajarea profilului principal, invitarea contactelor nu este permisă. + Ai trimis o invitație la grup + Ați respins invitația de grup + Scanare cod QR.]]> + cererea a fost trimisă + Alătură-te grupului + Acceptă cererea de contact + Adaugă mesaj + Biografie: + Biografie prea mare + Nu se poate modifica profilul + Conversează cu administratorii + Conversează cu membrii înainte ca aceștia să se alăture. + Conectează-te + Conectează-te mai rapid! 🚀 + contactul ar trebui să accepte… + Descriere prea lungă + Eroare la modificarea profilului + Folosește profil incognito + Deschide conversația + Eroare la respingerea cererii de contact + 4 noi limbi de interfață + Catalană, Indoneziană, Română și Vietnameză - mulțumită utilizatorilor noștri! + criptare end-to-end.]]> + Deschide o conversație nouă + Creează un grup nou + Deschide pentru a accepta + Deschide pentru conectare + Deschide pentru a te alătura + Timp de așteptare depășit pentru rutarea privată + Te poți conecta la conversație și poți trimite mesaje imediat ce apeși pe Conectare. + Rol nou în grup: Moderator + Descriere scurtă: + Trimis contactului tău după conectare. + Actualizezi la o adresă permanentă? + Mesaj de bun venit + SOLICITĂRI DE CONTACT DE LA GRUPURI + Această setare este pentru profilul tău actual + Partajează adresa veche + Partajează linkul vechi + Se încarcă profilul… + cerere de conectare din grupul %1$s + Eroare la deschiderea conversației + Eroare la deschiderea grupului + Mai puțin trafic pe rețelele mobile. + Adresa va fi scurtă, iar profilul tău va fi partajat prin intermediul acestei adrese. + Actualizează + Adresă SimpleX scurtă + Atinge Conectare pentru a începe conversația + Creează-ți adresa + Acceptă cererea de contact + doar după ce cererea ta este acceptată.]]> + Fișierele și conținutul media sunt interzise în această conversație. + Activează implicit mesajele care dispar. + Atât tu, cât și contactul tău puteți trimite fișiere și conținut media. + Permite contactelor tale să trimită fișiere și conținut media. + Conexiune de afaceri + Membrul a fost șters – nu poate accepta cererea + Permite fișierele și conținutul media doar dacă persoana de contact le permite. + Opțiuni depreciate + Păstrează-ți conversațiile curate + Nicio sesiune de rutare privată + Deschide linkul complet + Deschide pentru a folosi botul + Interzice trimiterea de fișiere și conținut media. + Timp de expirare protocol în fundal + Revizuiește membrii grupului + Trimiți cerere de contact? + Trimite cererea + Trimite cererea fără mesaj + Trimite feedbackul tău privat grupurilor. + Setează biografia și mesajul de bun venit al profilului. + Partajează-ți adresa + Atinge Conectează-te pentru a trimite cererea + Atinge Conectează-te pentru a folosi botul + Actualizezi linkul grupului? + Urează bun venit contactelor tale 👋 + Biografia ta: + Contactul tău de afaceri + Contactul tău + Grupul tău + Doar tu poți trimite fișiere și conținut media. + Doar contactele tale pot trimite fișiere și conținut media. + Deschide linkul curat + Respinge cererea de contact + Timp expirare conexiune TCP în fundal + Linkul va fi scurt, iar profilul grupului va fi partajat prin acesta. + Expeditorul NU va fi notificat. + Timpul de dispariție este setat doar pentru contactele noi. + Pentru a trimite comenzi, trebuie să fii conectat. + Pentru a folosi un alt profil după încercarea de conectare, șterge conversația și folosește din nou linkul. + Actualizează-ți adresa + Elimină urmărirea linkurilor + Șterge mesaje și blochează membri. + Atinge Alătură-te grupului + Actualizează linkul grupului + Profilul tău + Link de releu SimpleX + Grup + Eroare la marcarea conversației cu membrul ca fiind citită diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 97742f82a8..6bb357fea8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -48,13 +48,13 @@ В браузере Использование ссылки в браузере может уменьшить конфиденциальность и безопасность соединения. Ссылки на неизвестные сайты будут красными. - Ошибка при сохранении SMP серверов - Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. + Ошибка при сохранении SMP-серверов + Пожалуйста, проверьте, что адреса SMP-серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. Ошибка при сохранении настроек сети Превышено время соединения Ошибка соединения - Пожалуйста, проверьте Ваше соединение с сервером %1$s и попробуйте еще раз. + Пожалуйста, проверьте Ваше соединение с сервером %1$s и попробуйте ещё раз. Ошибка при отправке сообщения Ошибка при добавлении членов группы Ошибка при вступлении в группу @@ -67,8 +67,7 @@ Ошибка в ссылке контакта Пожалуйста, проверьте, что Вы использовали правильную ссылку, или попросите Ваш контакт отправить Вам новую. Ошибка соединения (AUTH) - Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом. -\nЧтобы установить соединение, попросите Ваш контакт создать еще одну ссылку и проверьте Ваше соединение с сетью. + Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите Ваш контакт создать ещё одну ссылку и проверьте Ваше соединение с сетью. Ошибка при принятии запроса на соединение Отправитель мог удалить запрос на соединение. Ошибка при удалении контакта @@ -77,8 +76,8 @@ Ошибка удаления ожидаемого соединения Ошибка при изменении адреса Ошибка теста на шаге %s. - Сервер требует авторизации для создания очередей, проверьте пароль - Возможно, хэш сертификата в адресе сервера неверный + Сервер требует авторизации для создания очередей, проверьте пароль. + Хэш в адресе сервера не соответствует сертификату. Соединение Создание очереди Защита очереди @@ -90,7 +89,7 @@ Мгновенные уведомления выключены! SimpleX выполняется в фоне вместо уведомлений через сервер.]]> Он может быть выключен через Настройки – Вы продолжите получать уведомления о сообщениях пока приложение запущено.]]> - Разрешите это в следующем окне чтобы получать нотификации мгновенно.]]> + Разрешите это в следующем окне, чтобы получать уведомления мгновенно.]]> Оптимизация батареи включена, поэтому сервис уведомлений выключен. Вы можете снова включить его через Настройки. Периодические уведомления Периодические уведомления выключены! @@ -175,7 +174,7 @@ Чаты соединяется… Вы приглашены в группу - вступить как %s + Вступить как %s соединяется… Нажмите, чтобы начать чат Соединиться с разработчиками @@ -195,10 +194,10 @@ Не получается декодировать изображение. Пожалуйста, попробуйте другое изображение или свяжитесь с разработчиками. Изображение - Ожидается прием изображения + Ожидается приём изображения Предложено получить изображение Изображение отправлено - Ожидается прием изображения + Ожидается приём изображения Изображение будет принято, когда Ваш контакт будет в сети, подождите или проверьте позже! Изображение сохранено в Галерею @@ -206,7 +205,7 @@ Большой файл! Ваш контакт отправил файл, размер которого превышает поддерживаемый в настоящее время максимальный размер (%1$s). В настоящее время максимальный поддерживаемый размер файла составляет %1$s. - Ожидается прием файла + Ожидается приём файла Файл будет принят, когда Ваш контакт будет в сети, подождите или проверьте позже! Файл сохранен Файл не найден @@ -248,8 +247,8 @@ Начать новый разговор Создать ссылку-приглашение - Соединиться через ссылку / QR код - Сканировать\nQR код + Соединиться через ссылку или QR-код + Сканировать\nQR-код Создать секретную группу (чтобы отправить Вашему контакту) (сканировать или вставить из буфера) @@ -268,8 +267,8 @@ Нажмите кнопку сверху, затем: Чтобы соединиться через ссылку - Если Вы получили ссылку с приглашением из SimpleX Chat, Вы можете открыть ее в браузере: - Сканировать QR код.]]> + Если Вы получили ссылку с приглашением из SimpleX Chat, Вы можете открыть её в браузере: + Сканировать QR-код.]]> Open in mobile app на веб странице, затем нажмите Соединиться в приложении.]]> Принять запрос на соединение? @@ -298,7 +297,7 @@ Контакт, которому Вы отправили эту ссылку, не сможет соединиться! Подтвержденное соединение будет отменено! - Соединение еще не установлено! + Соединение ещё не установлено! Ваш контакт должен быть в сети, чтобы установить соединение. \nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой). @@ -311,7 +310,7 @@ изображение превью ссылки удалить превью ссылки Настройки - QR код + QR-код SimpleX адрес помощь SimpleX команда @@ -319,20 +318,20 @@ Email Больше - Показать QR код + Показать QR-код - Неверный QR код - Этот QR код не является ссылкой! + Неверный QR-код + Этот QR-код не является ссылкой! Неверная ссылка! Эта ссылка не является ссылкой-приглашением! Запрос на соединение послан! Соединение с группой будет установлено, когда хост группы будет онлайн. Пожалуйста, подождите или проверьте позже! Соединение будет установлено, когда Ваш запрос будет принят. Пожалуйста, подождите или проверьте позже! - Соединение будет установлено, когда Ваш контакт будет онлайн. Пожалуйста, подождите или проверьте позже! - показать QR код во время видеозвонка или поделиться ссылкой.]]> + Соединение будет установлено, когда Ваш контакт будет в сети. Пожалуйста, подождите или проверьте позже. + показать QR-код во время видеозвонка или поделиться ссылкой.]]> Ваш профиль будет отправлен \nВашему контакту - сосканировать QR код во время видеозвонка, или Ваш контакт может отправить Вам ссылку.]]> + сосканировать QR-код во время видеозвонка, или Ваш контакт может отправить Вам ссылку.]]> Поделиться одноразовой ссылкой Соединиться @@ -351,7 +350,7 @@ Написать нам письмо Блокировка SimpleX Консоль - SMP серверы + SMP-серверы Адрес сервера по умолчанию Добавить серверы по умолчанию Добавить сервер @@ -360,7 +359,7 @@ Сохранить серверы Ошибка теста сервера! Серверы не прошли тест: - Сканировать QR код сервера + Сканировать QR-код сервера Ввести сервер вручную Сервер по умолчанию Ваш сервер @@ -372,11 +371,11 @@ Проверьте адрес сервера и попробуйте снова. Удалить сервер SimpleX Chat для терминала - Поставить звездочку в GitHub + Поставить звёздочку на GitHub Внести свой вклад Оценить приложение - Использовать серверы предосталенные SimpleX Chat? - Ваши SMP серверы + Использовать серверы, предосталенные SimpleX Chat? + Ваши SMP-серверы Используются серверы предоставленные SimpleX Chat. Инфо Как использовать серверы @@ -390,10 +389,10 @@ Сеть и серверы Настройки сети Настройки сети - Использовать SOCKS прокси? - Соединяться с серверами через SOCKS прокси через порт %d? Прокси должен быть запущен до включения этой опции. + Использовать SOCKS-прокси? + Соединяться с серверами через SOCKS-прокси через порт %d? Прокси должен быть запущен до включения этой опции. Использовать прямое соединение с Интернет? - Если Вы подтвердите, серверы смогут видеть Ваш IP адрес, а провайдер - с какими серверами Вы соединяетесь. + Если Вы подтвердите, серверы смогут видеть Ваш IP-адрес, а провайдер - с какими серверами Вы соединяетесь. Использовать .onion хосты Когда возможно Нет @@ -422,7 +421,7 @@ Сохранить и уведомить членов группы Выйти без сохранения - Вы котролируете Ваш чат! + Вы контролируете Ваш чат! Платформа для сообщений и приложений, которая защищает Вашу личную информацию и безопасность. Мы не храним Ваши контакты и сообщения (после доставки) на серверах. Создать профиль @@ -466,7 +465,7 @@ Будущее коммуникаций Более конфиденциальный Без идентификаторов пользователей. - Защищен от спама + Защищён от спама Вы определяете, кто может соединиться. Децентрализованный Кто угодно может запустить сервер. @@ -506,8 +505,8 @@ Выключить Ваши ICE серверы WebRTC ICE серверы - Relay сервер защищает Ваш IP адрес, но может отслеживать продолжительность звонка. - Relay сервер используется только при необходимости. Другая сторона может видеть Ваш IP адрес. + Relay-сервер защищает Ваш IP-адрес, но может отслеживать продолжительность звонка. + Relay-сервер используется только при необходимости. Другая сторона может видеть Ваш IP-адрес. Откройте SimpleX Chat\nчтобы принять звонок Вы можете разрешить принимать звонки на экране блокировки через Настройки. @@ -537,7 +536,7 @@ Принять звонок %1$d пропущенных сообщений - ошибка хэш сообщения + ошибка хэша сообщения ошибка ID сообщения повторное сообщение Пропущенные сообщения @@ -549,7 +548,7 @@ Конфиденциальность Конфиденциальность Защитить экран приложения - Автоприем изображений + Автоприём изображений Отправлять картинки ссылок Резервная копия данных @@ -561,7 +560,7 @@ ЧАТЫ Инструменты разработчика Экспериментальные функции - SOCKS ПРОКСИ + SOCKS-ПРОКСИ ЗНАЧОК ТЕМЫ СООБЩЕНИЯ И ФАЙЛЫ @@ -679,18 +678,18 @@ Вступить Вступить инкогнито Вступление в группу - Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы. + Вы вступили в группу. Устанавливается соединение с пригласившим Вас членом группы. Выйти Выйти из группы Вы перестанете получать сообщения от этой группы. История чата будет сохранена. - Пригласить членов группы + Пригласить членов группы Группа неактивна Приглашение истекло! Приглашение в группу больше не действительно, оно было удалено отправителем. Группа не найдена! Эта группа больше не существует. Нельзя пригласить контакты! - Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие Вашего основного профиля, приглашать контакты не разрешено + Вы используете профиль инкогнито в этой группе. Для защиты Вашего основного профиля приглашать контакты запрещено. Вы отправили приглашение в группу Вы приглашены в группу @@ -741,7 +740,7 @@ соединяется Нет контактов для добавления - Роль члена группы + Роль нового члена группы Развернуть выбор роли Пригласить в группу Не приглашать членов @@ -751,7 +750,7 @@ Выбрано контактов: %d Контакты не выбраны Нельзя пригласить контакт! - Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль + Вы пытаетесь пригласить контакт, который знает Ваш профиль инкогнито, в группу, где Вы используете основной профиль. Пригласить членов группы %1$s ЧЛЕНОВ ГРУППЫ @@ -766,7 +765,7 @@ Создать ссылку Удалить ссылку? Удалить ссылку - Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. + Вы можете поделиться ссылкой или QR-кодом — любой сможет присоединиться к группе. Члены группы останутся, даже если вы позже удалите ссылку. Все члены группы, которые соединились через эту ссылку, останутся в группе. Ошибка при создании ссылки группы Ошибка при удалении ссылки группы @@ -778,7 +777,7 @@ Удалить члена группы Отправить сообщение - Член группы будет удален - это действие нельзя отменить! + Член группы будет удалён - это действие нельзя отменить. Удалить ЧЛЕН ГРУППЫ Роль @@ -824,13 +823,13 @@ Инкогнито Случайный профиль - Режим Инкогнито защищает Вашу конфиденциальность — для каждого контакта создается новый случайный профиль. + Режим Инкогнито защищает Вашу конфиденциальность — для каждого контакта создаётся новый случайный профиль. Это позволяет иметь много анонимных соединений без общих данных между ними в одном профиле пользователя. - Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом. + Когда Вы соединены с контактом инкогнито, тот же самый профиль инкогнито будет использоваться для групп с этим контактом. Системная Светлая - Темная + Тёмная Тема Сбросить цвета @@ -876,7 +875,7 @@ Разрешить необратимо удалять отправленные сообщения. (24 часа) Запретить необратимое удаление сообщений. Разрешить отправлять голосовые сообщения. - Запретить отправлять голосовые сообщений. + Запретить отправлять голосовые сообщения. Члены могут посылать прямые сообщения. Прямые сообщения между членами группы запрещены. Члены могут необратимо удалять отправленные сообщения. (24 часа) @@ -948,7 +947,7 @@ Макс. 40 секунд, доставляются мгновенно. Окончательное удаление сообщений Ваши контакты могут разрешить окончательное удаление сообщений. - Добавить серверы через QR код. + Добавить серверы через QR-код. Улучшенная безопасность Скрыть экран приложения. Исчезающие сообщения @@ -1005,7 +1004,7 @@ Черновик сообщения Много профилей чата Сохранить последний черновик, вместе с вложениями. - Защищенные имена файлов + Защищённые имена файлов Благодаря пользователям – добавьте переводы через Weblate! Чтобы защитить Ваш часовой пояс, файлы картинок и голосовых сообщений используют UTC. Французский интерфейс @@ -1014,12 +1013,12 @@ Отдельные транспортные сессии модерировано модерировано %s - Удалить сообщение участника\? + Удалить сообщение члена группы\? Модерировать Сообщение будет удалено для всех членов группы. - Сообщение будет помечено как удаленное для всех членов группы. + Сообщение будет помечено как удалённое для всех членов группы. Пожалуйста, свяжитесь с админом группы. - Вы не можете отправлять сообщения! + Вы \"читатель\" только чтение сообщений читатель Роль при вступлении @@ -1052,14 +1051,12 @@ Без звука Сделайте профиль конфиденциальным! Дополнительные улучшения скоро! - Теперь админы могут: -\n- удалять сообщения членов. -\n- приостанавливать членов (роль "наблюдатель") + Теперь админы могут: \n- удалять сообщения членов группы. \n- приостанавливать членов группы (роль наблюдатель) Защитите Ваши профили чата паролем! Раскрыть Поддержка bluetooth и другие улучшения. Сохранить приветственное сообщение\? - Установить сообщение для новых членов группы! + Установите приветственное сообщение для новых членов группы. Нажмите на профиль, чтобы переключиться на него. Благодаря пользователям - добавьте переводы через Weblate! Вы все равно получите звонки и уведомления в профилях без звука, когда они активные. @@ -1079,12 +1076,12 @@ Откатить версию и открыть чат Предупреждение: Вы можете потерять какие то данные! ID базы данных и опция Отдельные транспортные сессии. - Показать опции для девелоперов + Показать опции для разработчиков Удалить профиль чата Удалить профиль Пароль профиля Слишком много видео! - Запросил прием видео + Запросил приём видео Видео Видео отправлено Ожидание видео @@ -1096,31 +1093,31 @@ Раскрыть профиль Видео будет получено, когда Ваш контакт будет онлайн, пожалуйста, подождите или проверьте позже! Раскрыть профиль чата - Ошибка при загрузке SMP серверов - Ошибка при загрузке XFTP серверов - Ошибка при сохранении XFTP серверов - Проверьте, что адреса XFTP северов в правильном формате и не дублируются. + Ошибка при загрузке SMP-серверов + Ошибка при загрузке XFTP-серверов + Ошибка при сохранении XFTP-серверов + Проверьте, что адреса XFTP-серверов указаны в правильном формате и не дублируются. Сервер требует авторизации для загрузки, проверьте пароль. Сравнение файла Удалить файл Загрузка файла Загрузка файла - XFTP серверы - Ваши XFTP серверы - Использовать .onion хосты в Нет если SOCKS прокси их не поддерживает.]]> + XFTP-серверы + Ваши XFTP-серверы + Использовать .onion хосты в Нет если SOCKS-прокси их не поддерживает.]]> Ошибка аутентификации Нет кода доступа Ввод кода доступа Режим Блокировки SimpleX Системная аутентификация - Ошибка аутентификации; попробуйте еще раз. + Ошибка аутентификации; попробуйте ещё раз. Сразу Аутентифицировать Изменить код доступа Текущий Код Введите Код - Настройки SOCKS прокси - Использовать SOCKS прокси + Настройки SOCKS-прокси + Использовать SOCKS-прокси Хост Порт порт %d @@ -1131,8 +1128,8 @@ %1$d сообщений не удалось расшифровать. Ошибка расшифровки Блокировка SimpleX не включена! - Ошибка хэш сообщения - Хэш предыдущего сообщения отличается\" + Ошибка хэша сообщения + Хэш предыдущего сообщения отличается. Подтвердить код Неправильный код Заблокировать через @@ -1153,7 +1150,7 @@ Остановить отправку файла\? Ошибка ID сообщения Это может произойти, когда Вы или Ваш контакт используете старую копию базы данных. - Пожалуйста, сообщите об этой ошибке девелоперам. + Пожалуйста, сообщите об этой ошибке разработчикам. Неправильный ID предыдущего сообщения (меньше или равен предыдущему). \nЭто может произойти из-за ошибки программы, или когда соединение компроментировано. %1$d сообщений пропущено. @@ -1201,7 +1198,7 @@ Пользовательское время Отправить Пригласить друзей - Сохранить настройки автоприема + Сохранить настройки адреса SimpleX Изменить режим самоуничтожения Изменить код самоуничтожения Самоуничтожение @@ -1251,7 +1248,7 @@ Создать адрес SimpleX Поделиться с контактами Прекратить делиться адресом\? - Автоприем + Автоприём Введите приветственное сообщение... (опционально) Сохранить настройки\? Прекратить делиться @@ -1264,7 +1261,7 @@ Просмотр Поделиться адресом Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с %s. - Темная тема + Тёмная тема Импорт темы Ошибка импорта темы SimpleX @@ -1335,6 +1332,7 @@ Нет истории Отправка отчётов о доставке включена для %d контактов. Отправка отчётов о доставке будет включена для всех контактов во всех видимых профилях чата. + Установка для Вашего активного профиля Установки для Вашего активного профиля Отправка отчётов о доставке выключена для %d контактов. Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения! @@ -1361,6 +1359,7 @@ Включить (кроме исключений) Выключить отчёты о доставке\? ОТПРАВКА ОТЧЁТОВ О ДОСТАВКЕ + ЗАПРОСЫ НА СОЕДИНЕНИЕ ИЗ ГРУПП шифрование согласовано шифрование согласовано для %s шифрование работает @@ -1370,10 +1369,10 @@ %s в %s Починить соединение Починка не поддерживается контактом. - Починка не поддерживается членом группы. + Восстановление шифрования не поддерживается членом группы Пересогласовать шифрование Быстрый поиск чатов - Отчеты о доставке сообщений! + Отчёты о доставке сообщений! Еще несколько изменений Отчёты о доставке! Включить @@ -1393,8 +1392,8 @@ Нет отфильтрованных разговоров Пересогласовать Пересогласовать шифрование\? - Запретить слать файлы и медиа. - Соединиться Инкогнито + Запретить посылать файлы и медиа. + Соединиться Инкогнито Разрешить Открыть настройки приложения Выключить уведомления @@ -1420,7 +1419,7 @@ выключено Эта функция ещё не поддерживается. Проверьте в следующем релизе. Соединиться напрямую\? - Этому члену группы будет отправлен запрос на соединение. + Запрос на соединение будет отправлен этому члену группы. Отчёты о доставке включены для %d групп Показывать последние сообщения Скоро! @@ -1432,11 +1431,11 @@ Черновик сообщения Расход батареи приложением / Без ограничений в настройках приложения.]]> Использовать активный профиль - Использовать новый Инкогнито профиль + Использовать новый профиль инкогнито Расход батареи приложением / Без ограничений в настройках приложения.]]> База данных будет зашифрована, и пароль сохранен в настройках. Шифруйте сохраненные файлы и медиа - Обратите внимание: соединение с серверами файлов и сообщений устанавливаются через SOCKS прокси. Звонки и картинки ссылок используют прямое соединение.]]> + Обратите внимание: соединение с серверами файлов и сообщений устанавливаются через SOCKS-прокси. Звонки и картинки ссылок используют прямое соединение.]]> Шифровать локальные файлы Приложение для компьютера! 6 новых языков интерфейса @@ -1448,7 +1447,7 @@ Удалить пароль из настроек\? Использовать случайный пароль Сохранить пароль в настройках - Упрощенный режим Инкогнито + Упрощённый режим Инкогнито Установить пароль базы данных Установить пароль базы данных Открыть директорию базы данных @@ -1456,21 +1455,20 @@ Создайте новый профиль в приложении для компьютера. 💻 Пароль будет сохранён в настройках как простой текст после того, как вы его измените или перезапустите приложение. Установите режим Инкогнито при соединении. - - соединиться с каталогом групп (BETA)! -\n- отчеты о доставке (до 20 членов). -\n- быстрее и стабильнее. + - соединиться с каталогом групп (BETA)! \n- отчёты о доставке (до 20 членов группы). \n- быстрее и стабильнее. Пароль хранится в настройках, как открытый текст. Открыть Ошибка при создании контакта Послать прямое сообщение контакту Ошибка отправки приглашения - Послать прямое сообщение - соединен напрямую + Отправьте сообщение чтобы соединиться + запрос на соединение Раскрыть Блокируйте членов группы Повторить запрос на соединение? Ошибка нового соглашения о шифровании удалил(а) контакт + запрос на соединение из группы %1$s Ошибка Создайте группу, используя случайный профиль. Создать группу @@ -1484,7 +1482,7 @@ Свяжите мобильное и настольное приложения! 🔗 %d сообщений помечено удалёнными Группа уже существует! - Использовать с компьютера в мобильном приложении и сосканируйте QR код.]]> + Использовать с компьютера в мобильном приложении и сосканируйте QR-код.]]> Уже соединяется! Несовместимая версия (новое)]]> @@ -1494,9 +1492,9 @@ Обнаружение по локальной сети и %d других событий Соединиться через ссылку? - Инкогнито группы + Группы инкогнито Вступление в группу уже начато! - %1$d сообщений модерировано членом %2$s + %1$d сообщений отмодерировано членом %2$s %s был отключен]]> Быстрое вступление и надежная доставка сообщений. Соединиться с самим собой? @@ -1570,7 +1568,7 @@ Указан неверный путь к файлу. Сообщите о проблеме разработчикам приложения. %1$s!]]> Сверьте код с компьютером - Сканировать QR код с компьютера + Сканировать QR-код с компьютера Разблокировать Вы уже запросили соединение через этот адрес! Показывать консоль в новом окне @@ -1604,13 +1602,13 @@ Включить доступ к камере Нажмите, чтобы сканировать Создать группу: создать новую группу.]]> - Не отправлять историю новым членам. - Отправить до 100 последних сообщений новым членам. + Не отправлять историю новым членам группы. + Отправить до 100 последних сообщений новым членам группы. Все сообщения будут удалены - это нельзя отменить! Камера недоступна Код доступа в приложение Добавить контакт: создать новую ссылку-приглашение или подключиться через полученную ссылку.]]> - Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите ее обратно до запуска чата. + Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите её обратно до запуска чата. Соединение остановлено Создано Оставить @@ -1625,7 +1623,7 @@ Ошибка удаления заметки Венгерский и Турецкий интерфейс Искать или вставьте ссылку SimpleX - Этот QR код не является SimpleX-ccылкой. + Этот QR-код не является SimpleX-ccылкой. С зашифрованными файлами и медиа. С уменьшенным потреблением батареи. Оставить неиспользованное приглашение? @@ -1635,8 +1633,8 @@ Запустить чат? Личные заметки Доступ к истории - История не отправляется новым членам. - До 100 последних сообщений отправляются новым членам. + История не отправляется новым членам группы. + До 100 последних сообщений отправляются новым членам группы. Показывать внутренние ошибки Ошибка соединения с компьютером %s]]> @@ -1656,7 +1654,7 @@ Вставленный текст не является SimpleX-ссылкой. Создаётся ссылка… Нажмите, чтобы вставить ссылку - Ошибка QR кода + Ошибка QR-кода Поделиться одноразовой ссылкой-приглашением Повторить Ошибка приложения @@ -1677,11 +1675,11 @@ Очистить личные заметки? Новый чат Новое сообщение - Или отсканируйте QR код + Или отсканируйте QR-код Вы можете увидеть ссылку-приглашение снова открыв соединение. Показывать медленные вызовы API - Бывший член %1$s - Сохраненное сообщение + Член группы %1$s + Сохранённое сообщение неизвестно неизвестный статус %d сообщений заблокировано администратором @@ -1689,17 +1687,17 @@ %s разблокирован Вы разблокировали %s Разблокировать для всех - Заблокировать члена для всех? + Заблокировать члена группы для всех? заблокирован заблокировано администратором Заблокирован администратором Заблокировать для всех - Ошибка при блокировании члена для всех - Разблокировать члена для всех? + Ошибка при блокировании члена группы для всех + Разблокировать члена группы для всех? Вы заблокировали %s end-to-end шифрованием с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> - Чат защищен end-to-end шифрованием. - Чат защищен квантово-устойчивым end-to-end шифрованием. + Чат защищён end-to-end шифрованием. + Чат защищён квантово-устойчивым end-to-end шифрованием. Открыть экран миграции Миграция с другого устройства Установить пароль @@ -1707,7 +1705,7 @@ Приветственное сообщение слишком длинное Сообщение слишком большое Повторить загрузку - Вы можете попробовать еще раз. + Вы можете попробовать ещё раз. Загрузка архива Подготовка архива %s загружено @@ -1741,12 +1739,12 @@ Остановка чата Архивировать и загрузить Подтвердить загрузку - Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы. + Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP-серверы. Ошибка загрузки Повторить загрузку - Вы можете попробовать еще раз. + Вы можете попробовать ещё раз. Отменить миграцию - Мигрировать с другого устройства на новом устройстве и сосканируйте QR код.]]> + Мигрировать с другого устройства на новом устройстве и сосканируйте QR-код.]]> Создание ссылки на архив Удалить базу данных с этого устройства Завершить миграцию @@ -1755,7 +1753,7 @@ Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений. не должны использовать одну и ту же базу данных на двух устройствах.]]> Проверьте подключение к Интернету и повторите попытку - Подтвердите, что Вы помните пароль базы данных для ее миграции. + Подтвердите, что Вы помните пароль базы данных для её миграции. Проверка пароля базы данных Проверить пароль Ошибка подтверждения пароля: @@ -1769,7 +1767,7 @@ квантово-устойчивым end-to-end шифрованием с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> Мигрировать сюда Мигрировать на другое устройство - Мигрируйте на другое устройство через QR код. + Мигрируйте на другое устройство через QR-код. Или вставьте ссылку архива Звонки с картинкой-в-картинке квантово-устойчивое e2e шифрование @@ -1863,9 +1861,9 @@ Показать список чатов в новом окне Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов или когда SOCKS-прокси включен). Незащищённый - Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов. - Отправлять сообщения напрямую, когда IP адрес защищен, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. - Черная + Без Tor или VPN, Ваш IP-адрес будет доступен серверам файлов. + Отправлять сообщения напрямую, когда IP-адрес защищён, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + Чёрная Тёмный режим Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку. Режим цветов @@ -1881,23 +1879,22 @@ Сделайте ваши чаты разными! Информация об очереди сообщений Персидский интерфейс - Защитить IP адрес - Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. \nВключите в настройках Сети и серверов. + Защитить IP-адрес + Защитите ваш IP-адрес от серверов сообщений, выбранных Вашими контактами. \nВключите в настройках Сеть и серверы. Отправьте сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. Конфиденциальная доставка Использовать конфиденциальную доставку с неизвестными серверами. - Использовать конфиденциальную доставку с неизвестными серверами, когда IP адрес не защищен. - Когда IP защищен + Использовать конфиденциальную доставку с неизвестными серверами, когда IP-адрес не защищён. + Когда IP защищён Да - Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений. + Чтобы защитить Ваш IP-адрес, приложение использует Ваши SMP-серверы для конфиденциальной доставки сообщений. Изображения профилей Все режимы Тема приложения Сбросить на тему приложения Сбросить на тему пользователя Неизвестные серверы! - Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: -\n%1$s. + Без Tor или VPN, Ваш IP-адрес будет доступен этим серверам файлов: \n%1$s. Не использовать конфиденциальную маршрутизацию. Никогда Неизвестные серверы @@ -1925,7 +1922,7 @@ Рисунок обоев Фон обоев Применить к - Не удается отправить сообщение + Не удаётся отправить сообщение Бета Соединeно попытки @@ -1936,11 +1933,11 @@ Проверка на наличие обновлений Разрешить звонки? Звонки запрещены! - Не удается позвонить члену группы + Не удаётся позвонить члену группы Обновление скачано звонок - Не удается позвонить контакту - Не удается написать члену группы + Не удаётся позвонить контакту + Не удаётся написать члену группы Проверять обновления соединиться Адрес сервера назначения %1$s несовместим с настройками пересылающего сервера %2$s. @@ -1952,8 +1949,8 @@ Выбранные настройки чата запрещают это сообщение. Ошибка файла Сканировать / Вставить ссылку - Другие XFTP серверы - Настроенные XFTP серверы + Другие XFTP-серверы + Настроенные XFTP-серверы Загрузка %s (%s) Доступно обновление: %s Выключить @@ -1966,15 +1963,15 @@ Подробности Всего Активные соединения - Прием сообщений + Приём сообщений В ожидании Загружено Статистика серверов будет сброшена - это нельзя отменить! Всего отправлено Переподключить - SMP сервер + SMP-сервер Начиная с %s. - XFTP сервер + XFTP-сервер дубликаты истекло другое @@ -1987,7 +1984,7 @@ Блоков загружено Ошибки загрузки Размер - Ошибки приема + Ошибки приёма Принятые файлы Адрес сервера Ошибка сервера файлов: %1$s @@ -2014,7 +2011,7 @@ Нет отфильтрованных контактов Ваши контакты Архивированные контакты - Настроенные SMP серверы + Настроенные SMP-серверы Показать процент Слабое Среднее @@ -2047,10 +2044,10 @@ Сообщения будут помечены на удаление. Получатель(и) смогут посмотреть эти сообщения. Выбрать Сообщения будут удалены для всех членов группы. - Сообщения будут помечены как удаленные для всех членов группы. + Сообщения будут помечены как удалённые для всех членов группы. Контакт удален! Разговор удален! - Член неактивен + Член группы неактивен Прямого соединения пока нет, сообщение переслано или будет переслано админом. Ничего не выбрано открыть @@ -2058,7 +2055,7 @@ Выбрано %d Настройки Вы по-прежнему можете просмотреть разговор с %1$s в списке чатов. - Другие SMP серверы + Другие SMP-серверы Выключено Установлено успешно Установить обновление @@ -2072,7 +2069,7 @@ Продолжить Серверы файлов и медиа Серверы сообщений - SOCKS прокси + SOCKS-прокси Некоторые файл(ы) не были экспортированы Вы можете мигрировать экспортированную базу данных. Вы можете сохранить экспортированный архив. @@ -2101,10 +2098,10 @@ Вы не подключены к этим серверам. Для доставки сообщений на них используется конфиденциальная доставка. Соединяйтесь с друзьями быстрее Управляйте своей сетью - Защищает ваш IP адрес и соединения. + Защищает ваш IP-адрес и соединения. Открыть настройки серверов Полученные сообщения - Ошибки приема + Ошибки приёма Архивируйте контакты чтобы продолжить переписку. Отправлено напрямую Отправлено через прокси @@ -2141,7 +2138,7 @@ Сохранение %1$s сообщений Убедитесь, что конфигурация прокси правильная. Аутентификация прокси - Использовать случайные учетные данные + Использовать случайные учётные данные Режим системы Ошибка пересылки сообщений %1$d ошибок файлов:\n%2$s @@ -2166,11 +2163,11 @@ Ошибка переключения профиля Выберите профиль чата Поделиться профилем - Соединение было перемещено на %s, но при смене профиля произошла неожиданная ошибка. + Соединение было перемещено в профиль %s, но при переключении профиля произошла ошибка. Угол Сессия приложения - Новые учетные данные SOCKS будут использоваться при каждом запуске приложения. - Новые учетные данные SOCKS будут использоваться для каждого сервера. + Новые учётные данные SOCKS будут использоваться при каждом запуске приложения. + Новые учётные данные SOCKS будут использоваться для каждого сервера. Сервер Форма сообщений Хвост @@ -2187,23 +2184,23 @@ Переключайте профиль чата для одноразовых приглашений. Аудит SimpleX протоколов от Trail of Bits. Чтобы совершать звонки, разрешите использовать микрофон. Завершите вызов и попробуйте позвонить снова. - Не использовать учетные данные с прокси. + Не использовать учётные данные с прокси. Ошибка сохранения прокси Пароль - Использовать разные учетные данные прокси для каждого соединения. - Использовать разные учетные данные прокси для каждого профиля. + Использовать разные учётные данные прокси для каждого соединения. + Использовать разные учётные данные прокси для каждого профиля. Имя пользователя - Ваши учетные данные могут быть отправлены в незашифрованном виде. + Ваши учётные данные могут быть отправлены в незашифрованном виде. Удалить архив? Загруженный архив базы данных будет навсегда удален с серверов. Принятые условия Принять условия Нет серверов сообщений. - Нет серверов для приема сообщений. + Нет серверов для приёма сообщений. Ошибки в настройках серверов. Для профиля %s: Нет серверов файлов и медиа. - Нет серверов для приема файлов. + Нет серверов для приёма файлов. Нет серверов для отправки файлов. Недоставленные сообщения Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. @@ -2237,7 +2234,7 @@ Посмотреть измененные условия Устройства Xiaomi: пожалуйста, включите опцию Autostart в системных настройках для работы нотификаций.]]> Нет сообщения - Это сообщение было удалено или еще не получено. + Это сообщение было удалено или ещё не получено. Сообщение слишком большое! Пожалуйста, уменьшите размер сообщения и отправьте снова. Пожалуйста, уменьшите размер сообщения или уберите медиа и отправьте снова. @@ -2292,7 +2289,7 @@ Только владельцы разговора могут поменять предпочтения. Текст условий использования не может быть показан, вы можете посмотреть их через ссылку: Разговор - Член будет удален из разговора - это действие нельзя отменить! + Участник будет удалён из разговора - это действие нельзя отменить. Серверы по умолчанию Роль будет изменена на %s. Все участники разговора получат уведомление. Ваш профиль будет отправлен участникам разговора. @@ -2309,7 +2306,7 @@ Для получения Использовать для сообщений Размыть - Прямые сообщения между членами запрещены. + Прямые сообщения между членами группы запрещены. Бизнес разговоры - Открывает разговор на первом непрочитанном сообщении.\n- Перейти к цитируемому сообщению. Конфиденциальность для ваших покупателей. @@ -2323,11 +2320,11 @@ Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем. Ошибка сохранения серверов Условия будут приняты для включенных операторов через 30 дней. - Ошибка приема условий + Ошибка приёма условий Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности. Например, если ваш контакт получает сообщения через сервер SimpleX Chat, ваше приложение будет доставлять их через сервер Flux. - Прямые сообщения между членами запрещены в этом разговоре. + Прямые сообщения между участниками запрещены в этом разговоре. Группы Удалить Удалить список? @@ -2351,7 +2348,7 @@ Все чаты будут удалены из списка %s, а сам список удален Добавить список Примечания - Открыто с %s + Открыть в %s Создать список Добавить в список Изменить список @@ -2379,7 +2376,7 @@ Сообщения о нарушениях Непрочитанные упоминания Да - Упоминайте участников 👋 + Упоминайте членов группы 👋 Улучшенная приватность и безопасность Ускорено удаление групп. Ускорена отправка сообщений. @@ -2454,10 +2451,10 @@ отклонён Только отправитель и модераторы видят это Только вы и модераторы видят это - Разблокировать членов для всех? + Разблокировать членов группы для всех? Сообщения от этих членов группы будут показаны! Все новые сообщения от этих членов группы будут скрыты! - Заблокировать членов для всех? + Заблокировать членов группы для всех? Члены группы будут удалены - это действие нельзя отменить! Участники будут удалены из разговора - это действие нельзя отменить! модераторы @@ -2467,4 +2464,155 @@ Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. Настроить операторов серверов Политика конфиденциальности и условия использования. + все + Принять + Член группы хочет присоединиться. Принять? + группа удалена + удален из группы + %d чата(ов) + контакт не готов + контакт удален + не синхронизирован + запрос на вступление отклонён + Новый член группы хочет присоединиться. + Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление. + ожидает одобрения + Отклонить + Отклонить члена группы? + Ошибка при удалении чата + Полная ссылка + Ошибка при вступлении члена группы + Ссылка не поддерживается + Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку. + %d сообщений + Вы можете найти Ваши жалобы в Чате с админами. + Чат с админами + Чат с членом группы + выключено + Одобрять членов группы + Чаты с членами группы + Приём членов в группу + Одобрять членов для вступления в группу. + Нет чатов с членами группы + Принять как читателя + Принять в группу + Принять члена группы + одобрен админами + Жалоба отправлена модераторам + Вы вышли + нельзя отправлять + %d чатов с членами группы + контакт выключен + член группы имеет старую версию + Вы не можете отправлять сообщения! + Короткая ссылка + Сохранить настройки вступления? + Вы приняли этого члена группы + рассмотрение + Установить вступление в группу + Удалить чат с членом группы? + Удалить разговор + принят %1$s + Чат с админами + Вы приняты + 1 чат с членом группы + SimpleX ссылка канала + Обновить адрес + Принять запрос на соединение + Добавить сообщение + О себе: + Нельзя поменять профиль + end-to-end шифрованием.]]> + только после того как Ваш запрос будет принят.]]> + Чат с админами + Общайтесь с членами группы до того как принять их. + Соединиться + Соединяйтесь быстрее! 🚀 + контакт должен принять… + Ошибка изменения профиля + Ошибка при открытии чата + Ошибка при открытии группы + Ошибка отклонения запроса + Вступить в группу + Меньше трафик в мобильных сетях. + Загрузка профиля… + Отправляйте сообщения сразу после соединения. + Новая роль в группах: Модератор + Нет сессии конфиденциальной доставки + Открыть чат + Открыть новый чат + Открыть новую группу + Откройте чтобы принять + Откройте чтобы соединиться + Откройте чтобы вступить + Таймаут конфиденциальной доставки + Адрес будет коротким, и Ваш профиль будет добавлен в адрес. + Фоновый таймаут протокола + Отклонить запрос на соединение + Может удалять сообщения и блокировать членов группы. + запрос отправлен + Одобрять членов группы + Отправить запрос на соединение? + Отправить запрос + Отправить запрос без сообщения + Отправляйте Ваши конфиденциальные предложения группе. + Отправляется Вашему контакту после соединения. + Обновить ссылку группы? + Обновить + Обновить адрес? + Цель: + Фоновый таймаут TCP-соединения + Отправитель не будет уведомлён. + Член группы удалён - невозможно принять запрос + Чтобы использовать другой профиль после попытки соединения, удалите чат и используйте ссылку снова. + Приветственное сообщение + О Вас: + Ваш профиль + Описание слишком длинное + Использовать профиль инкогнито + 4 новых языков интерфейса + Принять запрос на соединение + Бизнес контакт + Каталонский, Индонезийский, Румынский и Вьетнамский - благодаря нашим пользователям! + Создайте Ваш адрес + Описание слишком длинное + Включите исчезающие сообщения по умолчанию. + Группа + Очищайте Ваши чаты + Добавьте описание и приветственное сообщение. + Поделиться старым адресом + Поделиться старой ссылкой + Поделитесь Вашим адресом + Короткий адрес SimpleX + Нажмите Соединиться + Нажмите Соединиться, чтобы отправить запрос + Нажмите Вступить в группу + Ссылка будет короткой, и профиль группы будет добавлен в ссылку. + Время удаления устанавливается только для новых контактов. + Обновите Ваш адрес + Обновить ссылку группы + Приветствуйте Ваши контакты 👋 + Ваш бизнес контакт + Ваш контакт + Ваша группа + Разрешить файлы и медиа, только если их разрешает Ваш контакт. + Разрешить Вашим контактам отправлять файлы и медиа. + Бот + Вы и Ваш контакт можете отправлять файлы и медиа. + Файлы и медиа запрещены в этом чате. + Только Вы можете отправлять файлы и медиа. + Только Ваш контакт может отправлять файлы и медиа. + Откройте чтобы использовать бот + Запретить отправлять файлы и медиа. + Нажмите Соединиться, чтобы использовать бот. + Вы должны быть соединены, чтобы отправлять команды. + Удалённые настройки + Открыть очищенную ссылку + Открыть полную ссылку + Удалять параметры отслеживания + Ошибка прочтения чата + Хэш в адресе пересылающего сервера не соответствует сертификату: %1$s. + Хэш в адресе сервера не соответствует сертификату: %1$s. + Ссылка SimpleX relay + Хэш в адресе сервера назначения не соответствует сертификату: %1$s. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 257daec596..ec8af9053e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -14,7 +14,7 @@ Görüntülü ara Gizlilik ve güvenlik Gizli - Konuşmalar + Sohbetler Bağlantı ile bağlan Sohbet profillerin Konuşma tercihleri @@ -87,7 +87,7 @@ WebRTC ICE sunucu adreslerinin doğru formatta olduğundan emin olun: Satırlara ayrılmış ve yinelenmemiş şekilde. Kaydet ARAYÜZ RENKLERİ - Otomatik-kabul ayarlarını kaydet + SimpleX adres ayarlarını kaydet Ayarlar kaydedilsin mi? Kaydet ve konuştuğun kişilere bildir Tercihleri kaydet\? @@ -288,7 +288,7 @@ Üyeyi çıkar Kaldır ARAMALAR - KONUŞMALAR + SOHBETLER SEN SOHBET VERİTABANI Kaldır @@ -301,7 +301,7 @@ sen: %1$s kaldırıldı ÜYE - Grup üyeleri kendiliğinden yok olan mesajlar gönderebilir. + Üyeler kendiliğinden yok olan mesajlar gönderebilir. Kendiliğinden yok olan mesaj gönderimini engelle. Yalnızca kişiniz sesli mesaj göndermeye izin veriyorsa sen de ver. Kişilerinin sesli mesaj göndermesine izin ver. @@ -647,11 +647,11 @@ Grup adını gir: Grup tam adı: Dosya ve medya - Grup üyeleri doğrudan mesaj gönderebilir. + Üyeler doğrudan mesaj gönderebilir. Üyeler, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) - Grup üyeleri sesli mesaj gönderebilirler. + Üyeler sesli mesaj gönderebilirler. Dosya ve medya yasaklanmıştır. - Grup üyeleri dosya ve medya paylaşabilir. + Üyeler dosya ve medya paylaşabilir. Grup bağlantıları Konuşmada devre dışı bırakıldığında bile Çabuk ve göndericinin çevrim içi olmasını beklemeden! @@ -696,7 +696,7 @@ Daha fazla bilgi edinin Sesli mesajlar - 5 dakikaya kadar sesli mesajlar. -\n- özel mesaj kaybolma süreleri ayarlama +\n- özel mesaj kaybolma süreleri ayarlama \n- mesag düzenleme geçmişi. hafta Kişi zaten mevcut @@ -709,7 +709,7 @@ Cihaz doğrulaması devre dışı. SimpleX Kilidi Kapatılıyor. Cihaz doğrulaması etkin değil. Cihaz doğrulamasını etkinleştirdikten sonra SimpleX Kilidini Ayarlar üzerinden açabilirsiniz. Şuna cevap olarak - %s olarak katıl + %s olarak katılın Geçersiz link! Geçersiz QR kodu Geçersiz sunucu adresi! @@ -840,10 +840,10 @@ Zaten %1$s e bağlısınız Doğrulanamadınız; lütfen tekrar deneyin. SimpleX Kilidini Ayarlar üzerinden açabilirsiniz. - gruba davet edildiniz + Gruba davetlisiniz Hiç sohbetiniz yok Gözlemcisiniz - Mesajlar gönderemezsiniz! + sen gözlemcisin Güvenlik kodunu görüntüle Sesli mesaj gönderebilmeniz için kişinizin de sesli mesaj göndermesine izin vermeniz gerekir. XFTP sunucuları @@ -1201,7 +1201,7 @@ Mobilden tara Bağlantıları onayla Mesaj paylaş… - Önceki mesajın hash\'i farklı.\" + Önceki mesajın özeti veya karması farklı. SimpleX Kilit aktif değil! SimpleX Kilit Doğrudan bağlanılsın mı? @@ -1235,7 +1235,7 @@ Masaüstüne bağlan Doğrudan mesaj gönder Bu adres üzerinden zaten bağlantı talebinde bulundunuz! - doğrudan mesaj gönder + bağlanmak için gönder Gönderen kişi dosya aktarımını iptal etti. Mesajları yalnızca siz geri döndürülemez şekilde silebilirsiniz (kişiniz bunları silinmek üzere işaretleyebilir). (24 saat içinde) %1$s’ye bağlanıyorsunuz.]]> @@ -1732,7 +1732,7 @@ SimpleX bağlantılarına izin verilmiyor Dosyalar ve medyaya izin verilmiyor Sesli mesajlara izin verilmiyor - Grup üyeleri SimpleX bağlantıları gönderebilir. + Üyeler SimpleX bağlantıları gönderebilir. iletildi Ağ bağlantısı SimpleX bağlantıları göndermesine izin ver. @@ -2032,7 +2032,7 @@ Yanlış anahtar veya bilinmeyen dosya yığın adresi - büyük olasılıkla dosya silinmiştir. Ayarlar Profil paylaş - Bağlantınız %s\'ye taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu. + Bağlantınız %s\'e taşındı, ancak profil geçişinde bir hata gerçekleşti. Proxy kimlik doğrulaması Rastgele kimlik bilgileri kullan Her bağlantı için farklı proxy kimlik bilgileri kullan. @@ -2223,4 +2223,290 @@ Onar Bağlantı onarılsın mı? Sunucuyu güncellerken hata oluştu + Mesaj sunucuları yok. + Sunucu yapılandırmasında hatalar. + Yeni sunucu + yöneticiler + Bahsedildiğinizde haberdar olun. + Uygunsuz içerik + Uygunsuz profil + Liste adı... + Gizliliğe nasıl yardım ediyor + Liste adı ve emoji tüm listeler için farklı olmalıdır. + Herkesi sustur + Ağ merkezsizleştirme + Daha iyi meta veri gizliliği için Ağ ve sunucular ayarlarında Flux\'u etkinleştirin. + Mesaj silme özelliğini devre dışı bırak + Bu sohbetteki mesajlar asla silinmeyecektir. + Üyeyi kabul ederken hata oluştu + Liste + %d üyelerle sohbetler + 1 üye ile sohbet + %d sohbet(ler) + Tam bağlantı + Ağ operatörleri + Yeni üye gruba katılmak istiyor. + Yöneticilerle sohbetler + Üye ile sohbet + Gizli yönlendirme için + Üye kabulü + Kabul Et + Yöneticilerle sohbetler + Üyeyi kabul et + Mesaj yok + Gruplar + Sohbete davet et + Sohbetten çık + Üye sohbetten çıkarılacaktır - bu geri alınamaz! + Ağ operatörü + Mevcut koşullar metni yüklenemedi, koşulları bu bağlantı üzerinden inceleyebilirsiniz: + %s listesinde sohbet yok. + Hayır + %1$s kabul edildi + seni kabul etti + Koşullar şu tarihte kabul edilecektir: %s. + hepsi + Üyelerle sohbetler + Arkaplan servisi yok + Kabul Et + SimpleX Chat\'i kullanarak şunları kabul etmiş olursunuz:\n- genel gruplarda sadece yasal içerik göndermeyi.\n- diğer kullanıcılara saygı göstermeyi - spam yapmamayı. + Sunucu operatörlerini yapılandırma + Sohbetten çıkılsın mı? + yönetici + Bütün sunucular + Şunlar için sohbet profilini sil + Önemli mesajları kaçırmayın. + Bu üyelerden gelen mesajlar gösterilecektir! + Kabul edilen koşullar: %s. + %s , kullanım koşullarını kabul edin.]]> + Üyeler mesajları moderatörlere bildirebilir. + Üye ile birlikte sohbet silinsin mi? + Üyeli sohbetler yok + Üye olarak kabul et + Üye ve sohbet silinirken hata oluştu + Sohbetler yok + Sohbetler bulunamadı + %d mesajlar + Üye raporları + grup silindi + üyenin eski uygulama sürümü var + Şifreleme yeniden anlaşması devam ediyor. + Bağlantı şifreleme yeniden anlaşması gerektirir. + Geliştirilmiş sohbet navigasyonu + Üyelerden bahsedin 👋 + Gözlemci olarak kabul et + Üyeler sohbetten çıkarılacaktır - bu geri alınamaz! + kişi devre dışı bırakıldı + Sohbeti sil + Yöneticilerin gruplarını yönetmelerine yardım edin. + kişi hazır değil + kişi silindi + Üyeler gruptan çıkarılacaktır - bu geri alınamaz! + Üye gruba katılacak, üye kabul edilsin mi? + Özel mesaj yönlendirmesi için sunucu yok. + mesajlar gönderilemiyor + Medya ve dosya sunucuları yok. + Anahtar deposundaki parola okunamıyor. Bu, uygulamayla uyumsuz bir sistem güncellemesinden sonra gerçekleşmiş olabilir. Eğer durum bu değilse, lütfen geliştiricilerle iletişime geçin. + reddedildi + %s sunucuları + Güncellenen koşullar + Sadece siz ve moderatörler görebilir. + Sadece gönderen ve moderatörler görebilir. + Daha sonra oluşturmak için menüden SimpleX adresi oluştur seçeneğine dokunun. + Operatör + Dosyalar için kullanın + Sohbetlerde mesajların geçerlilik süresini ayarlayın. + İstenmeyen + Dosya göndermek için sunucu yok. + Dosyaları alacak sunucu yok. + Teslim edilmeyen mesajlar + Bağlantınızın değiştirilmesini önlemek için, iletişim güvenlik kodlarını karşılaştırabilirsiniz. + gruptan çıkarıldı + Notlar + senkronize değil + Özel medya dosya adları. + bağlanmak için talep edildi + Tek seferlik bağlantıyı bir arkadaşınızla paylaşın + Mesaj gönderemezsiniz! + uyarı + %s Kullanın + -İlk okunmamış mesaja sohbeti aç.\n- Alıntılanan mesajlara git. + Güncellenen koşulları görüntüleyin + Veya arşiv dosyasını içe aktarın + İstenmeyen + Uygulama, her konuşmada farklı operatörler kullanarak gizliliğinizi korur. + Rol %s olarak değiştirilecek. Sohbetteki herkes bilgilendirilecek. + Okunmamış bahsetmeler + Koşulları gözden geçirin + Sohbet profiliniz sohbet üyelerine gönderilecektir. + Sunucularınız + Açık koşullar + Bu işlem geri alınamaz - seçilen tarihten önce bu sohbette gönderilen ve alınan mesajlar silinecektir. + Bağlantının kimlerle paylaşıldığını hatırlamak için bağlantı adını ayarlayabilirsiniz. + SimpleX adresini sosyal medyada paylaşın. + Web sitesi + Desteklenmeyen bağlantı bağlantısı + Rapor + SimpleX adresi ve tek kullanımlık bağlantılar herhangi bir mesajlaşma uygulaması üzerinden güvenle paylaşılabilir. + Kısa bağlantı + Giriş ayarlarını kaydetmek ister misiniz? + Özel sohbetler, gruplar ve kişileriniz sunucu operatörleri tarafından erişilemez. + Birden fazla operatör etkinleştirildiğinde, hiçbirinin kimin kiminle iletişim kurduğunu öğrenmek için meta verisi yoktur. + Kullanılacak ağ operatörlerini seçin. + Güncelleme + Ağ ve sunucular ayarlarında operatörleri yapılandırabilirsiniz. + Bağlantıyı aç + Anahtar deposundaki parola okunamıyor, lütfen elle girin. Bu, uygulamayla uyumsuz bir sistem güncellemesinden sonra meydana gelmiş olabilir. Eğer durum bu değilse, lütfen geliştiricilerle iletişime geçin. + Bu üyeyi kabul ettiniz. + Lütfen grup moderatörlerinin gruba katılma isteğinizi incelemesini bekleyin. + Açık değişiklikler + Koşulları görüntüle + Üye kabulü + Uygulamadaki ikinci önceden ayarlanmış operatör! + Kısa bağlantı ekle + Gizlilik politikası ve kullanım koşulları. + Daha sonra incele + Sunucu operatörleri + SimpleX Chat ve Flux, Flux tarafından işletilen sunucuların uygulamaya dahil edilmesi konusunda bir anlaşma yaptı. + Sohbet listesinden bağlantıları aç + Evet + Web bağlantısını açmak ister misiniz? + beklemede + onay bekliyor + inceleme + Üyeleri kaldırmak mı? + Tüm üyeler için engellemeyi kaldırmak mı? + Sunucu protokolü değiştirildi. + Sunucuları kullanın + Operatör sunucusu + Sunucu operatöre %s eklendi. + Sunucu operatörü değişti. + Moderatörlere mesaj gönderilmesini yasaklayın. + Üyeyi reddetmek mi? + Özel raporlar gönder + %s ile aç + Şeffaflık + Ulaşılabilir sohbet araç çubuğu + Bu sohbetten mesaj almayı durduracaksınız. Sohbet geçmişi korunacaktır. + Alıcı + Müşterilerinizin gizliliği. + reddedildi + Sohbet adını ayarla… + Her mesajda en fazla %1$s üyeye atıfta bulunabilirsiniz! + İletileri alacak sunucu yok. + Bu mesaj silindi veya henüz alınmadı. + Lütfen mesaj boyutunu küçültün ve tekrar gönderin. + Lütfen mesaj boyutunu küçültün veya medyayı kaldırın ve tekrar gönderin. + Üye profilini bildir: yalnızca grup moderatörleri görebilir. + Spam bildir: Yalnızca grup moderatörleri bunu görebilir. + İhlali bildir: Yalnızca grup moderatörleri bunu görebilir. + Mesajı kopyalayıp boyutunu küçülterek gönderebilirsiniz. + Rapor içeriği: Yalnızca grup moderatörleri görebilir. + Diğerlerini bildir: Yalnızca grup moderatörleri bunu görebilir. + Moderatörlere gönderilen rapor + Raporlarınızı yöneticilerle sohbet bölümünde görüntüleyebilirsiniz. + Ayarlar aracılığıyla sunucuları yapılandırabilirsiniz. + Uzaktan kumandalı cep telefonları + kapalı + İnceleme üyeleri + Kabul etmeden önce üyeleri inceleyin (kapıyı çalın). + Bildirimler ve pil + inceleme bekliyor + Kapalı + Önceden ayarlanmış sunucular + Önceden ayarlanmış sunucular için yalnızca TCP bağlantı noktası 443\'ü kullanın. + Sohbet sahipleri tercihleri değiştirebilir. + Sohbetleri listeler halinde düzenleyin + Önceden ayarlanmış sunucular + Mesajlar için kullanın + Mevcut sohbet profilinizin yeni dosyaları için sunucular + Bu grupta mesajların bildirilmesi yasaktır. + Reddet + Bağlantı, teslim edilemeyen mesajların sınırına ulaştı, bağlantınız çevrimdışı olabilir. + Bu bağlantı için daha yeni bir uygulama sürümü gereklidir. Lütfen uygulamayı güncelleyin veya irtibat kişinizden uyumlu bir bağlantı göndermesini isteyin. + Rapor nedeni? + Rapor sizin için arşivlenecektir. + Okunmamış sohbet yok + Rapor: %s + Raporlar + katılma talebi reddedildi + yöneticiler tarafından incelenmiştir + sen ayrıldın + Adresi herkese açık olarak paylaş + Port belirtilmediğinde TCP portu %1$s kullanın. + Veya özel olarak paylaşmak için + SimpleX adresi mi yoksa tek kullanımlık bağlantı mı? + Listeyi kaydet + Göndermek için + SimpleX kanal bağlantısı + Mesajlaşma için TCP bağlantı noktası + Web bağlantı noktasını kullan + İletişim isteğini kabul et + Mesaj ekle + Profil değiştirilemiyor + uçtan uca şifreleme ile korunmaktadır.]]> + sonra mesaj gönderebileceksiniz.]]> + Yöneticilerle sohbet edin + Üyeler katılmadan önce onlarla sohbet edin. + Bağlan + Daha hızlı bağlanın! 🚀 + iletişim kabul etmelidir… + Kullanıcısını değiştirirken hata oluştu + Sohbeti açarken sorun oluştu + Grup açarken hata oluştu + İletişim isteği reddedildi + Gruba katıl + Mobil ağlarda daha az trafik. + Bağlan\'a dokunduğunuz anda anında mesaj gönderin. + Yeni grup rolü: Moderatör + Özel yönlendirme oturumu yok + Sohbeti aç + Yeni sohbet aç + Yeni grup aç + Kabul etmeye açık + Bağlanmak için açık + Katılmak için açık + Özel yönlendirme zaman aşımı + Profil, adres aracılığıyla paylaşılacaktır. + Protokol arka plan zaman aşımı + İletişim isteğini reddet + Mesajları siler ve üyeleri engeller. + istek gönderilir + İnceleme grubu üyeleri + İletişim isteği gönder? + Talep gönder + Mesaj olmadan istek gönder + Özel geri bildirimlerinizi gruplara gönderin. + Bağlantı kurulduktan sonra kişinize gönderilir. + Grup profilini bağlantı yoluyla paylaş + Profil paylaş + Profilinizi adres yoluyla paylaşın + TCP bağlantısı arka plan zaman aşımı + Gönderen bilgilendirilmeyecektir. + Bağlantı denemesinden sonra başka bir profil kullanmak için sohbeti silin ve bağlantıyı tekrar kullanın. + Hoş geldiniz mesajı + Profiliniz + Profil yükleniyor… + Bio: + Biyografiniz: + Kısa açıklama: + Sohbet etmek için Bağlan\'a dokunun + Bağlan\'a dokunarak isteği gönderin + İletişim isteğini kabul et + İrtibat kişiniz + Gruba katıl\'a dokunun + Grubunuz + Grup + İş bağlantısı + İş bağlantınız + Biyografi çok uzun + Açıklama çok büyük + Kaybolma süresi yalnızca yeni kişiler için ayarlanır. + Gizli profil kullan + Botu kullanmak için açın + Botu kullanmak için Bağlan tuşuna basın. + Bot + Komutlar gönderebilmek için bağlanmanış olmanız gereklidir. + Üye silinmiş - isteği kabul edemeyecek + Grup linkini güncelle diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 549cb01b63..ddd54b717f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -200,7 +200,7 @@ редаговано помилка відправки непрочитане - приєднатися як %s + Приєднуйтесь як %s Скасувати попередній перегляд зображення Скасувати попередній перегляд файлу Очікування на зображення @@ -329,7 +329,7 @@ Торкніться, щоб розпочати новий чат Чат із розробниками У вас немає чатів - Ви не можете відправляти повідомлення! + ви спостерігач Будь ласка, зв\'яжіться з адміністратором групи. Файл буде отримано, коли ваш контакт буде в мережі, будь ласка, зачекайте або перевірте пізніше! Відео відправлене @@ -459,7 +459,7 @@ Створити без зашифрування e2e контакт має зашифрування e2e - Хеш попереднього повідомлення інший.\" + Хеш попереднього повідомлення інший. Підтвердити пароль Новий пароль Перезапустити @@ -571,7 +571,7 @@ Вітаємо, %1$s! Вітаємо! Цей текст доступний у налаштуваннях - вас запрошено в групу + Запрошуємо вас до групи Поділитися повідомленням… Поділитися медіа… Поділитися файлом… @@ -586,7 +586,7 @@ Відео Ваш контакт відправив файл, розмір якого більший, ніж поточно підтримуваний максимальний розмір (%1$s). Поточно максимально підтримуваний розмір файлу - %1$s. - Адреса отримувача буде змінена на інший сервер. Зміна адреси завершиться після того, як відправник з\'явиться в мережі. + Адреса отримання буде змінена на інший сервер. Зміна адреси буде завершена після того, як відправник з\'явиться в мережі. Перевірити код безпеки Надіслати повідомлення Записати голосове повідомлення @@ -1031,7 +1031,7 @@ Зупинити поділ Введіть текст привітання... (необов\'язково) Зберегти налаштування\? - Зберегти налаштування автоприйому + Зберегти налаштування адреси SimpleX Привіт! \nПриєднуйтесь до мене через SimpleX Chat: %s Запросити друзів @@ -1426,7 +1426,7 @@ \n- швидше та надійніше. Ключова фраза зберігається в налаштуваннях як звичайний текст. Ви вже подали запит на підключення за цією адресою! - надіслати приватне повідомлення + відправити для підключення Показувати консоль в новому вікні Усі нові повідомлення від %s будуть приховані! підключив(лась) безпосередньо @@ -2058,7 +2058,7 @@ Видалити архів? Поділитися профілем Завантажений архів бази даних буде остаточно видалено з серверів. - Підключення було перенесено до %s, але під час перенаправлення на профіль сталася непередбачена помилка. + Ваше з\'єднання було переміщено на %s, але при перемиканні профілю сталася помилка. Режим системи Не використовуйте облікові дані з проксі. Аутентифікація проксі @@ -2088,7 +2088,7 @@ Повідомлення були видалені після того, як ви їх вибрали. Помилка при пересиланні повідомлень Звук вимкнено - Помилка ініціалізації WebView. Переконайтеся, що WebView встановлено, і його підтримувана архітектура — arm64. \nПомилка: %s + Помилка під час ініціалізації WebView. Переконайтеся, що WebView встановлено і що його архітектура підтримується arm64.\nПомилка: %s Хвіст Кут Форма повідомлення @@ -2378,7 +2378,6 @@ Використовуючи SimpleX Chat, ви погоджуєтесь на:\n- надсилати тільки легальний контент у публічних групах.\n- поважати інших користувачів – без спаму. Налаштувати операторів сервера Політика конфіденційності та умови використання - Використовувати короткі посилання (BETA) Це посилання вимагає новішої версії додатку. Будь ласка, оновіть додаток або попросіть вашого контакту надіслати сумісне посилання. Повне посилання Коротке посилання @@ -2388,4 +2387,129 @@ Ні Типові сервери Використовуйте TCP порт 443 лише для попередньо налаштованих серверів. + контакт не готовий + Помилка при прийомі учасника + Чат з учасником + Прийом учасника + усі + Чати з учасниками + Чат з адміністраторами + контакт вимкнено + Чат з адміністраторами + Прийняти + 1 чат з учасником + %d чат(ів) + %d чатів з учасниками + %d повідомлень + не можна надсилати + контакт видалено + групу видалено + прийнято %1$s + прийняв(ла) вас + Прийняти як учасника + Прийняти як спостерігача + Прийняти учасника + Видалити чат + Видалити чат з учасником? + Помилка видалення чату з учасником + Встановити прийом учасників + Зберегти налаштування прийому? + Будь ласка, зачекайте, поки модератори групи розглянуть ваш запит на приєднання до групи. + ви прийняли цього учасника + Схвалювати учасників + вимкнено + Схвалювати учасників для вступу до групи. + Адреса оновлення + не синхронізовано + схвалено адміністраторами + очікує на схвалення + перегляд + учасник використовує застарілу версію + видалено з групи + Повідомлення надіслано модераторам + запит на приєднання відхилено + Ви не можете надсилати повідомлення! + Ви можете переглянути свої звіти у чаті з адміністраторами. + Новий учасник хоче приєднатися до групи. + Немає чатів з учасниками + Учасник приєднається до групи, прийняти учасника? + Відхилити + Відхилити учасника? + ви вийшли + 4 нові мови інтерфейсу + Прийняти запит на контакт + Прийняти запит на контакт + Додати повідомлення + Біо: + Біографія занадто велика + Бізнес-зв\'язок + Не вдається змінити профіль + Каталонська, індонезійська, румунська та в\'єтнамська - завдяки нашим користувачам! + наскрізним шифруванням.]]> + лише після того, як ваш запит буде прийнятий.]]> + Чат з адміністраторами + Спілкуйтеся з учасниками до того, як вони приєднаються. + Підключіться + Підключайтеся швидше! 🚀 + контакт повинен прийняти… + Створіть свою адресу + Опис занадто великий + Увімкнути зникаючі повідомлення за замовчуванням. + Помилка зміни профілю + Помилка відкриття чату + Помилка відкриття групи + Помилка відхилення запиту на контакт + Група + Приєднуйтесь до групи + Підтримуйте чистоту в чатах + Менше трафіку в мобільних мережах. + Завантаження профілю… + Миттєве повідомлення, щойно ви натиснете \"Підключитися\". + Нова роль у групі: Модератор + Немає приватного сеансу маршрутизації + Відкритий чат + Відкрити новий чат + Відкрити нову групу + Відкрити для прийняття + Відкрито для підключення + Відкрито для приєднання + Тайм-аут приватної маршрутизації + Фоновий тайм-аут протоколу + Відхилити запит на контакт + Видаляє повідомлення та блокує користувачів. + запит відправлено + Учасники групи оглядів + Надіслати запит на контакт? + Надіслати запит + Надіслати запит без повідомлення + Надсилайте свої приватні відгуки до груп. + Відправлено вашому контакту після з\'єднання. + Налаштуйте біографію профілю та вітальне повідомлення. + Поділіться старою адресою + Поділіться старим посиланням + Поділіться своєю адресою + Короткий опис: + Коротка адреса SimpleX + Натисніть Підключитися до чату + Натисніть Підключитися, щоб відправити запит + Натисніть Приєднатися до групи + Таймаут TCP-з\'єднання bg + Адреса буде короткою, і ваш профіль буде доступний за цією адресою. + Посилання буде коротким, а профіль групи буде поширюватися за посиланням. + Відправник НЕ буде повідомлений. + Час зникнення встановлюється тільки для нових контактів. + Щоб використовувати інший профіль після спроби з\'єднання, видаліть чат і скористайтеся посиланням знову. + Оновіть свою адресу + Оновлення + Змінити адресу? + Оновити посилання на групу + Оновити посилання на групу? + Використовуйте профіль інкогніто + Вітальне повідомлення + Вітаємо ваші контакти 👋 + Твоя біографія: + Ваш діловий контакт + Ваш контакт + Ваша група + Ваш профіль diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 3cbc54f652..b71350ea50 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -1944,7 +1944,7 @@ Ứng dụng tìm nhận tin nhắn mới một cách định kỳ - nó tiêu tốn một vài phần trăm pin mỗi ngày. Ứng dụng không sử dụng thông báo đẩy - dữ liệu ở thiết bị của bạn không được gửi đi tới máy chủ nào. Mã mà bạn đã quét không phải là một mã QR dẫn SimpleX. Chủ đề - Mã băm của tin nhắn trước có sự khác biệt.\" + Mã băm của tin nhắn trước có sự khác biệt. ID của tin nhắn tiếp theo là không chính xác (nhỏ hơn hoặc bằng với cái trước).\nViệc này có thể xảy ra do một vài lỗi hoặc khi kết nối bị xâm phạm. Nỗ lực đổi mật khẩu cơ sở dữ liệu đã không được hoàn thành. Tên thiết bị sẽ được chia sẻ với thiết bị di động đã được kết nối. @@ -2243,7 +2243,7 @@ Bạn có thể chia sẻ địa chỉ của mình dưới dạng một đường dẫn hoặc mã QR - bất kỳ ai cũng có thể kết nối với bạn. Bạn có thể xem đường dẫn mời lần nữa trong chi tiết kết nối. Bạn kiểm soát cuộc trò chuyện của mình! - Bạn không thể gửi tin nhắn! + bạn là quan sát viên Bạn có thể chia sẻ địa chỉ này với các liên hệ của mình để họ kết nối với %s. Bạn cần cho phép liên hệ của mình gửi tin nhắn thoại để có thể gửi cho họ. Bạn có thể di chuyển cơ sở dữ liệu đã được xuất. @@ -2355,11 +2355,59 @@ Đường dẫn này yêu cầu một phiên bản ứng dụng mới hơn. Vui lòng nâng cấp ứng dụng hoặc yêu cầu liên hệ của một gửi cho một đường dẫn tương thích. Đường dẫn kênh SimpleX Đường dẫn kết nối không được hỗ trợ - Sử dụng đường dẫn ngắn (BETA) Toàn bộ đường dẫn Đường dẫn ngắn Tắt Các máy chủ cài sẵn Chỉ sử dụng cổng TCP 443 cho các máy chủ cài sẵn. Tất cả máy chủ + Lỗi chấp nhận thành viên + %d cuộc trò chuyện với thành viên + %d tin nhắn + đã chấp nhận %1$s + đã chấp nhận bạn + Vui lòng đợi các kiểm duyệt viên nhóm xem xét yêu cầu tham gia nhóm của bạn. + Thành viên mới muốn tham gia nhóm. + tất cả + Thu nạp thành viên + tắt + Xem xét các thành viên + Thêm đường dẫn ngắn + Trò chuyện với các quản trị viên + Trò chuyện với thành viên + Chấp nhận thành viên + Chấp nhận là thành viên + Thành viên sẽ tham gia nhóm, chấp nhận thành viên? + Chấp nhận là quan sát viên + đã được xem xét bởi các quản trị viên + Chấp nhận + đang chờ xem xét + xem xét + Không có cuộc trò chuyện nào với thành viên + Các cuộc trò chuyện với thành viên + 1 cuộc trò chuyện với 1 thành viên + %d cuộc trò chuyện + liên hệ đã bị xóa + liên hệ chưa sẵn sàng + chưa được đồng bộ hóa + Báo cáo đã được gửi cho các kiểm duyệt viên + không thể gửi tin nhắn + liên hệ đã bị vô hiệu hóa + nhóm đã bị xóa + đã xóa khỏi nhóm + yêu cầu tham gia đã bị từ chối + thành viên có phiên bản cũ + Xóa cuộc trò chuyện + Xóa cuộc trò chuyện với thành viên? + Trò chuyện với các quản trị viên + Từ chối + Từ chối thành viên? + Lỗi xóa cuộc trò chuyện với thành viên + bạn đã chấp nhận thành viên này + Xem xét các thành viên trước khi chấp nhận (knocking). + Lưu cài đặt thu nạp? + Đặt thu nạp thành viên + Bạn không thể gửi tin nhắn! + Bạn có thể xem các báo cáo của mình trong Cuộc trò chuyện với các quản trị viên. + bạn đã rời đi diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 28a5f6f50d..d6686f358d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -183,7 +183,7 @@ 点击以加入隐身聊天 你的聊天资料将被发送给群成员 你正在尝试邀请与你共享隐身个人资料的联系人加入你使用主要个人资料的群 - 隐身模式通过为每个联系人使用新的随机配置文件来保护你的隐私。 + 隐身模式通过为每个联系人使用新的随机个人资料来保护你的隐私。 你正在为该群使用隐身个人资料——为防止共享你的主要个人资料,不允许邀请联系人 通过一次性链接隐身 只有群主可以启用语音信息。 @@ -460,7 +460,7 @@ 无效的服务器地址! 邀请成员 离开群 - 仅本地配置文件数据 + 仅本地个人资料数据 即时通知 即时通知! 使用你的凭据登录 @@ -853,7 +853,7 @@ SimpleX 团队 %1$s 名成员 - 你将在组主设备上线时连接到该群,请稍等或稍后再检查! + 你将在群主设备上线时连接到该群,请稍等或稍后再检查! 当你启动应用或在应用程序驻留后台超过30 秒后,你将需要进行身份验证。 连接到 SimpleX Chat 开发者提出任何问题并接收更新 。]]> 你已接受连接 @@ -861,7 +861,7 @@ %1$d 条已跳过消息 %ds 更新内容 - 你被邀请加入群 + 你受邀加入群 你没有聊天记录 等待图像中 语音消息 @@ -885,7 +885,7 @@ 你将 %s 的角色更改为 %s 你将自己的角色更改为 %s 你已更改地址 - 你可以共享链接或二维码——任何人都可以加入该群。如果你稍后将其删除,你不会失去该组的成员。 + 你可以共享链接或二维码——任何人都可以加入该群。如果你稍后将其删除,你不会失去该群的成员。 间接(%1$s) 在移动应用程序中打开按钮。]]> SimpleX @@ -905,7 +905,7 @@ %d 天 %dw 你被邀请加入群。 加入以与群成员联系。 - 你加入了这个群。连接到邀请组成员。 + 你加入了这个群。连接到邀请群成员。 你更改了 %s 的地址 你已离开 已选择 %d 名联系人 @@ -929,7 +929,7 @@ 观察员 你是观察者 更新群链接错误 - 你无法发送消息! + 你是观察员 初始角色 请联系群管理员。 系统 @@ -939,7 +939,7 @@ 隐藏 将个人资料设置为私密! 静音 - 保存并更新组配置文件 + 保存并更新群资料 不再显示 不活跃时静音! 语音和视频通话 @@ -969,8 +969,8 @@ 感谢用户——通过 Weblate 做出贡献! 解除静音 欢迎消息 - 当静音配置文件处于活动状态时,你仍会收到来自静音配置文件的电话和通知。 - 你可以隐藏或静音用户配置文件——长按以显示菜单。 + 当静音个人资料处于活动状态时,你仍会收到来自静音个人资料的电话和通知。 + 你可以隐藏或静音用户个人资料——长按以显示菜单。 欢迎消息 确认数据库升级 实验性 @@ -1173,7 +1173,7 @@ 与你的联系人保持连接。 与联系人分享 邀请朋友 - 保存自动接受设置 + 保存 SimpleX 地址设置 保存设置? 停止分享 你好! @@ -1268,7 +1268,7 @@ 你的个人资料 %1$s 将被共享。 将为所有联系人启用送达回执功能。 打开应用程序设置 - 为所有组启用 + 为所有群启用 %s、%s 和 %d 其他成员已连接 已为 %d 联系人启用送达回执功能 已同意 %s 的加密 @@ -1277,7 +1277,7 @@ 使用新的隐身个人资料 过滤未读和收藏的聊天记录。 已更改安全密码 - 将为所有可见聊天配置文件中的所有联系人启用送达回执功能。 + 将为所有可见聊天个人资料中的所有联系人启用送达回执功能。 应用电池使用情况 / 无限制。]]> %s 在 %s 禁用回执? @@ -1285,12 +1285,12 @@ 可以在联系人和群设置中覆盖它们。 对所有联系人关闭 随机密码以明文形式存储在设置中。 \n你可以稍后更改。 - 已禁用 %d 组的送达回执功能 + 已禁用 %d 群的送达回执功能 需要为 %s 重新协商加密 SimpleX 无法在后台运行。只有在应用程序运行时,你才会收到通知。 启用(保留覆盖) 即将更新数据库加密密码并将其存储在设置中。 - 使用当前配置文件 + 使用当前个人资料 从设置中删除密码? 同意加密 启用回执? @@ -1309,12 +1309,12 @@ 为群禁用回执吗? %s、%s 和 %s 已连接 修复群成员不支持的问题 - 已为 %d 组启用送达回执功能 + 已为 %d 群启用送达回执功能 重新协商 禁用(保留覆盖) 设置数据库密码 已禁用 %d 联系人的送达回执功能 - 启用(保留组覆盖) + 启用(保留群覆盖) 应用电池使用情况 / 无限制。]]> 送达回执已禁用 打开数据库文件夹 @@ -1333,7 +1333,7 @@ 没有选中的聊天 可以加密 重新协商加密 - 禁用(保留组覆盖) + 禁用(保留群覆盖) 为群启用回执吗? 修复联系人不支持的问题 对 %s 加密正常 @@ -1342,10 +1342,10 @@ 禁用通知 回复 不启用 - 连接请求将发送给该组成员。 + 连接请求将发送给该群成员。 密码以明文形式存储在设置中。 同步连接时出错 - 这些设置适用于你当前的配置文件 + 这些设置适用于你当前的个人资料 允许为 %s 重新协商加密 为所有人启用 需要重新协商加密 @@ -1365,8 +1365,8 @@ 打开 创建成员联系人时出错 发送私信来连接 - 发送私信 - 已直连 + 发送私信来连接 + 已请求连接 展开 重复连接请求吗? 已删除联系人 @@ -1557,7 +1557,7 @@ 显示名无效。请另选一个名称。 慢函数 显示缓慢的 API 调用 - 过往成员 %1$s + 成员 %1$s 未知 未知状态 开发者选项 @@ -1852,7 +1852,7 @@ 显示百分比 不活跃 缩放 - 所有配置文件 + 所有个人资料 文件 没有信息,试试重新加载 服务器信息 @@ -1930,7 +1930,7 @@ 订阅被忽略 已配置的 SMP 服务器 已配置的 XFTP 服务器 - 当前配置文件 + 当前个人资料 传输会话 已上传 已停用 @@ -2046,20 +2046,20 @@ 上传的数据库存档将永久性从服务器被删除。 确保代理配置正确 消息将被删除 - 此操作无法撤销! - 你的连接被移动到 %s,但在将你重定向到配置文件时发生了意料之外的错误。 + 你的连接被移动到 %s,但在切换个人资料时发生了错误。 代理不使用身份验证凭据 - 切换配置文件出错 + 切换个人资料出错 代理身份验证 删除存档? - 选择聊天配置文件 + 选择聊天个人资料 保存代理出错 密码 每个连接使用不同的代理身份验证凭据。 - 每个配置文件使用不同的代理身份验证。 + 每一个人资料使用不同的代理身份验证。 你的凭据可能以未经加密的方式被发送。 使用随机凭据 用户名 - 分享配置文件 + 分享资料 转发消息出错 在你选中消息后这些消息已被删除。 %1$d 个文件错误:\n%2$s @@ -2095,7 +2095,7 @@ 一次转发最多20条消息。 Trail of Bits 审核了 SimpleX 协议。 通话期间切换音频和视频。 - 对一次性邀请切换聊天配置文件。 + 对一次性邀请切换聊天个人资料。 更佳的通话 允许自行删除或管理员移除最多200条消息。 保存服务器出错 @@ -2272,20 +2272,20 @@ 存档 删除举报 举报 - 举报其他:仅moderators会看到。 - 举报成员个人资料:仅moderators会看到。 - 举报违规:仅moderators会看到。 + 举报其他:仅协管会看到。 + 举报成员个人资料:仅协管会看到。 + 举报违规:仅协管会看到。 存档举报 - 举报内容:仅moderators会看到。 - 举报垃圾信息:仅moderators会看到。 - moderators + 举报内容:仅协管会看到。 + 举报垃圾信息:仅协管会看到。 + 协管 另一个理由 已存档的举报 违反社区指导方针 不当内容 不当个人资料 - 仅发送人和moderators能看到 - 只有你和moderators能看到 + 仅发送人和协管能看到 + 只有你和协管能看到 垃圾信息 存档举报? 举报理由? @@ -2325,13 +2325,13 @@ 存档所有举报? 存档 %d 份举报? 存档举报 - 所有 moderators + 所有 协管 仅自己 举报:%s - 禁止向 moderators 举报消息。 + 禁止向 协管 举报消息。 此群禁止消息举报。 - 成员可以向 moderators 举报消息。 - 允许向 moderators 举报消息。 + 成员可以向 协管 举报消息。 + 允许向 协管 举报消息。 提及成员👋 更好的群性能 更好的隐私和安全 @@ -2339,7 +2339,7 @@ 更快地删除群。 更快发送消息。 被提及时收到通知。 - 帮助管理员管理群组。 + 帮助管理员管理群。 将聊天组织到列表 私密媒体文件名。 发送私下举报 @@ -2358,15 +2358,14 @@ 将显示来自这些成员的消息! 删除成员吗? 为所有其他成员解封这些成员吗? - moderators + 协管 将从聊天中移除这些成员 — 此操作无法撤销! 隐私政策和使用条款。 接受 使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。 - 服务器运营方无法访问私密聊天、群组和你的联系人。 + 服务器运营方无法访问私密聊天、群和你的联系人。 配置服务器运营方 不支持的连接链接 - 使用短链接(测试) SimpleX 频道链接 短链接 此链接需要更新的应用版本。请升级应用或请求你的联系人发送相容的链接。 @@ -2375,4 +2374,150 @@ 关闭 预设服务器 仅预设服务器使用 TCP 协议 443 端口。 + 接受成员出错 + 举报已发送至 协管 + %d 个聊天 + 和成员的 %d 个聊天 + %d 条消息 + 接受了 %1$s + 接受了你 + 你接受了该成员 + 新成员要加入本群。 + 审核 + 待审核 + 全部 + 成员准入 + 关闭 + 删除 + 接受 + 和成员聊天 + 和管理员聊天 + 没有和成员的聊天 + 接受为成员 + 接受成员 + 成员将加入本群,接受成员吗? + 由管理员审核 + 设置成员入群准许 + 和成员聊天 + 和管理员聊天 + 准许入群前审核成员(knocking)。 + 请等待群的 moderator 审核你加入该群的请求。 + 审核成员 + 保存入群设置? + 你可以在和管理员和聊天中查看你的举报。 + 接受为观察员 + 和一名成员的一个聊天 + 无法发送消息 + 你离开了 + 删除和成员的聊天出错 + 你无法发送消息! + 禁用了联系人 + 群被删除了 + 从群被删除了 + 加入请求被拒绝 + 删除聊天 + 删除和成员的聊天吗? + 未同步 + 成员有旧版本 + 删除了联系人 + 联系人未就绪 + 拒绝成员? + 升级地址 + 接受联络请求 + 添加消息 + 端到端加密.]]> + 仅在你的请求被接收后.]]> + 连接 + 联系人应当接受… + 更改户出错 + 打开聊天时出错 + 打开群时出错 + 拒绝联络请求出错 + 加群 + 打开聊天 + 打开新聊天 + 打开新群 + 打开以接受 + 打开以连接 + 打开以加入 + 地址不会长,将通过该简短地址分享个人资料。 + 拒绝联络请求 + 发送了请求 + 发送联络请求? + 发送请求 + 发送无消息请求 + 连接后发送给你的联系人。 + 升级群链接? + 升级 + 升级地址? + 不会通知发送者。 + 欢迎消息 + 你的个人资料 + 无法更改个人资料 + 要在连接尝试后使用不同的个人资料,请删除聊天并再次使用该链接。 + 和管理员聊天 + 在成员加入前和这些人聊天 + 更快地连接!🚀 + 消耗更少的移动网络数据。 + 轻按连接后即刻发消息。 + 新的群角色:协管 + 无私密路由会话 + 私密路由超时 + 协议后台超时 + 删除消息并封禁成员。 + 审核群成员 + 向群发送私密反馈。 + TCP 连接后台超时 + 正加载个人资料… + 自我介绍: + 简短描述: + 自我介绍: + 自我介绍过大 + 描述过大 + 接受联络请求 + 企业连接 + + 轻按连接进行聊天 + 轻按连接来发送请求 + 轻按加入群 + 你的企业联系人 + 你的联系人 + 你的群 + 只为新联系人设置了消失时间。 + 4 种新的界面语言 + 加泰罗尼亚语、印尼语、罗马尼亚语和越南语——感谢我们的用户! + 创建地址 + 默认启用定时消失消息。 + 保持聊天洁净 + 设置自我介绍和欢迎消息。 + 分享旧地址 + 分享旧链接 + 分享地址 + SimpleX 短地址 + 链接不会长,群资料会通过短链接分享。 + 更新你的地址 + 升级群链接 + 使用隐身个人资料 + 欢迎联系人👋 + 来自%1$s群的连接请求 + 此设置用于当前个人资料 + 来自群的联络请求 + 成员被删除——无法接受请求 + 只有你的联系人允许的情况下才允许文件和媒体。 + 允许你的联系人发送文件和媒体。 + 机器人 + 你和你的联系人都可发送文件和媒体。 + 此聊天禁止文件和媒体。 + 只有你可以发送文件和媒体。 + 只有你的联系人可以发送文件和媒体。 + 打开来使用机器人 + 禁止发送文件和媒体。 + 轻按“连接”使用机器人 + 你必须已连接才能发送命令。 + 已废弃的选项 + 打开干净链接 + 打开完整链接 + 删除链接跟踪 + SimpleX 中继链接 + 将和成员的聊天标记为已读时出错 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 8ae414de00..ee0c785e9f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -5,7 +5,7 @@ 關於 SimpleX 接受 接受 - 1天 + 1 天 1 個月 接受 關於 SimpleX Chat @@ -23,7 +23,7 @@ 全名: 使用更多電量!程式始終在背景中運行 – 通知會立即顯示。]]> 對電量友善。程式每10分鐘檢查一次訊息。你可能會錯過電話或警急訊息。]]> - 回應通話請求 + 回應通話 清除 允許向群組內的成員傳送私訊。 %d 秒 @@ -31,32 +31,32 @@ 自動接受聯絡人請求 允許傳送自動銷毀的訊息。 - 無法初始化數據庫 - 一直開啟 + 無法初始化資料庫 + 總是開啟 關閉 SimpleX 鎖定 啟用 SimpleX 鎖定 複製 回覆 分享 - 附件 + 附加 和開發人員對話 - 你的對話 + 聊天 分享檔案… - 分享訊息 + 分享訊息… 取消圖片預覽 允許使用語音訊息? 取消 取消實況訊息 - 選擇檔案 + 檔案 相機 - 從圖片庫選擇圖片 - 接受匿名聊天模式 + 從圖片庫 + 接受匿名 所有訊息記錄會刪除 - 這不能還原!這些訊息只會在你裝置中刪除。 清除 - 要清除對話記錄? + 清除聊天? 取消連結預覽 清除 - 清除對話記錄 + 清除聊天 分享一次性連結 傳送問題和想法給開發者 如何使用 @@ -81,30 +81,30 @@ 通話已經結束了! 關閉語音 開啟語音 - 通話已經取消了 + 通話已結束 通話中 開啟喇叭 通話 - 樣式 + 外觀 語音通話(沒有端對端加密) 語音通話 - 語音 & 視訊通話 + 語音和視訊通話 視訊通話 粗體 自動接收圖片 備份應用程式資料 應用程式圖示 - 已匯入對話數據庫 + 已匯入對話資料庫 Android 金鑰庫是用於安全地儲存密碼 - 確保通知推送服務的運作。 請注意:如果你忘記了密碼你將不能再次復原或修改密碼。]]> 當你重新啟動應用程式或修改密碼後, Android 金鑰庫將用來安全地儲存密碼 - 將允許接收訊息通知。 - 聊天室已停止運作 + 聊天已停止 只有這個群組的負責人才能修改群組內的設定。 - 修改 - 新增新的個人檔案 + 變更 + 新增個人檔案 所有對話和對話記錄會刪除 - 這不能還原! - 匿名聊天模式 - 經常 + 匿名 + 總是 開啟 你的設定 允許你的聯絡人傳送語音訊息。 @@ -115,13 +115,13 @@ 管理員可以建立加入群組的連結。 使用二維碼掃描以新增伺服器。 聊天室運行中 - 對話數據庫 - 聊天室已停止運作 + 聊天資料庫 + 聊天已停止 停止 - 已刪除數據庫的對話內容 - 修改數據庫密碼? + 已刪除資料庫的對話內容 + 修改資料庫密碼? 確定要退出群組? - 退出 + 離開 無法邀請聯絡人! 已連接 退出 @@ -133,11 +133,11 @@ 群組全名: 對話設定 關閉 - + 自動銷毀訊息 - 群組內的成員可以私訊群組內的成員。 + 成員可以傳送私訊。 %d 分鐘 - %d 月 + %d 個月 通話結束 %1$s 取消檔案預覽 無法接收檔案 @@ -146,8 +146,8 @@ 每個聊天室的設定。]]> 返回 省電模式運行中,關閉了背景通知服務和定期更新接收訊息。你可以在通知設定內重新啟用。 - 匿名聊天模式 - 預設值 (%s) + 匿名模式 + 預設(%s) 群組設定 聯絡人設定 分享媒體… @@ -175,9 +175,9 @@ 你的設定 新增到另一個裝置上 檢查輸入的伺服器地址,然後再試一次。 - 終端機對話 + 聊天終端機 於 Github 給個星星 - 隱身模式透過為每個聯絡人使用新的隨機設定檔來保護您的隱私。 + 隱身模式透過為每個聯絡人使用新的隨機設定檔來保護你的隱私。 這樣是允許每一個對話中擁有不同的顯示名稱,並且沒有任何的個人資料可用於分享或有機會外洩。 只有你的聯絡人允許的情況下,才允許自動銷毀訊息。 允許你的聯絡人傳送自動銷毀的訊息。 @@ -187,8 +187,8 @@ 允許傳送語音訊息。 多久後刪除 群組內所有成員會保持連接。 - 自訂顏色 - 已移除 + 輔色 + 已被審查 SimpleX 群組連結 私人檔案名稱 加入成員(s)時出錯 @@ -205,9 +205,9 @@ 未知的訊息格式 無效的訊息格式 - 實況 - 無效對話 - 無效數據 + 直播 + 無效的聊天 + 無效的數據 連接 %1$d 已建立連接 已邀請連接 @@ -222,7 +222,7 @@ 傳送訊息時出錯 聯絡人已存在 你已經連接到 %1$s。 - 使用個人檔案(預設值)或使用連接(測試版)。 + 按聊天個人檔案(預設)或按連接(測試版)。 減少電量使用 更多改進即將推出! 意大利語言界面 @@ -233,7 +233,7 @@ 透過一次性連結連接? 加入群組? 你的個人檔案將傳送給你接收此連結的聯絡人。 - 你將加入此連結內的群組並且連接到此群組成為群組內的成員。 + 你將連接至此群組內的所有成員。 連接 錯誤 連接中 @@ -241,12 +241,12 @@ 嘗試連接至用於接收此聯絡人訊息的伺服器 (錯誤:%1$s)。 正在嘗試連接到用於接收此聯絡人訊息伺服器。 已刪除 - 已標記為刪除 - 在瀏覽器中開啟連結可能會有私隱疑慮和不確定性。不受 SimpleX 信任的連結會顯示紅色。 + 已標記為已刪除 + 在瀏覽器中開啟連結可能會有隱私疑慮和不確定性。不受 SimpleX 信任的連結會顯示紅色。 儲存 SMP 伺服器時出錯 請確保 SMP 伺服器連結是正確的格式,每行也分隔且不重複。 更新網路配置時出錯 - 聯絡人載入失敗 + 聊天載入失敗 多個聯絡人載入失敗 請更新應用程式或聯絡開發人員。 連接超時 @@ -275,20 +275,20 @@ 即時通知 即時通知! 已禁用即時通知功能! - 數據庫目前沒有正常運作。點擊查看更多 + 資料庫目前沒有正常運作。點擊查看更多 SimpleX Chat 通話來電 通知服務 顯示預覽 通知預覽 如果你解鎖程式三十秒後在後台再次啟動或返回應用程式,你會需要進行多一次認證。 - 已解鎖 + 解鎖 使用你的憑據登入 使用終端機開啟對話 傳送訊息時出錯 大概是你的聯絡人已經刪除了和你的對話並且已經沒有和你有連接。 只為我刪除 - 為所有人刪除 - 已修改 + 為所有人 + 已編輯 已傳送 未經授權傳送 傳送失敗 @@ -320,8 +320,8 @@ 安全佇列 刪除佇列 斷開連接 - 禁用電量優化 為了 SimpleX 在下一個對話中。否則,將禁用通知功能。]]> - 在接收通知之前,請你輸入數據庫的密碼 + 允許它以接收實時通知。]]> + 在接收通知之前,請你輸入資料庫的密碼 應用程式會定期取得新的訊息 — 它每天會消耗百分之幾的電量。應用程式將不使用推送通知 — 你裝置中的數據不會傳送至伺服器。 SimpleX Chat 服務 正在接收訊息… @@ -332,7 +332,7 @@ 顯示聯絡人名稱和訊息內容 隱藏聯絡人名稱和訊息內容 隱藏聯絡人: - 有新的訊息 + 新訊息 已連接 SimpleX 鎖定 為了保護你的個人訊息,開啟 SimpleX 鎖定。 @@ -340,7 +340,7 @@ 開啟 已開啟SimpleX 鎖定 你的裝置沒有啟用螢幕鎖定。你可以通過設定內啟用螢幕鎖定,當你啟用後就可以使用 SimpleX 鎖定。 - 修改 + 編輯 刪除 展露 刪除訊息? @@ -355,11 +355,11 @@ 下載檔案需要傳送者上線的時候才能下載檔案,請等待對方上線! 語音訊息 傳送實況的訊息 - 實況訊息! + 直播訊息! 傳送實況訊息 - 這會即時顯示你在輸入中的文字 (掃描或使用剪貼薄貼上) 權限被拒絕! - SimpleX 有一個後台通知服務 – 它每天使用百分之幾的電量。]]> + SimpleX 在背景運作而不是使用推送通知。]]> 定期通知 每十分鐘會檢查一次訊息,最快可設為每分鐘檢查一次 訊息文字 @@ -386,10 +386,10 @@ 驗證安全碼 傳送訊息 刪除個人檔案時出錯 - 停止對話 + 停止聊天 圖片不能解碼,嘗試其他圖片或聯絡開發人員。 檔案 - 大型檔案 + 大型檔案! 等待檔案中 確定 重設 @@ -397,7 +397,7 @@ 沒有詳細資料 一次性邀請連結 已複製至你的剪貼薄 - 透過連結 / 二維碼去連接 + 透過連結/QR 圖碼連接 掃描二維碼 (僅由群組成員儲存) 想和你對話! @@ -411,17 +411,17 @@ 使用 .onion 主機 Onion 主機不會啟用。 連接 - 刪除聯絡地址 + 刪除地址 顏色 如何使用你的伺服器 使用連結連接 掃描二維碼。]]> 設定 這個二維碼不是一個連結! - 當可行的時候 + 當可用時 核心版本:v%s simplexmq: v%s (%2s) - 建立聯絡地址 + 建立地址 編輯圖片 斜體 已拒絕通話 @@ -446,17 +446,17 @@ 標記為未讀 你的聯絡人需要上線才能連接成功。 \n你可以取消此連接和刪除此聯絡人(或者你可以稍後使用新的連結再試一次)。 - 二維碼 + QR 圖碼 個人檔案頭像 預覧連結圖片 SimpleX 地址 幫助 SimpleX 團隊 電郵 - 查看更多 + 更多 顯示二維碼 - 無效的二維碼 - 無效連結! + 無效的 QR 圖碼 + 無效的連結! 這個連結不是一個有效的連接連結! 已傳送連接請求 當群組的建立人上線,你便會成功連接至群組,請耐心等待! @@ -476,10 +476,10 @@ 安全碼 Markdown 幫助 在訊息中使用 Markdown 語法 - 人手輸入伺服器地址 + 手動輸入伺服器 使用伺服器 用於新的連接 - 無效的伺服器地址 + 無效的伺服器地址! 此伺服器用於你目前的個人檔案 使用 SimpleX Chat 伺服器? 你的 SMP 伺服器 @@ -491,7 +491,7 @@ 需要 Onion 主機會在可用時啟用。 - 刪除聯絡地址? + 刪除地址? 你的個人檔案只會儲存於你的裝置和只會分享給你的聯絡人。 SimpleX 伺服器並不會看到你的個人檔案。 儲存並通知你的聯絡人 儲存並通知你的多個聯絡人 @@ -501,12 +501,12 @@ 如何使用 Markdown 語法 你可以使用 Markdown 語法以更清楚標明訊息: 刪除線 - 你錯過了電話 + 已錯過通話 接收到回應… 連接中… - 通話完結 + 已結束 連接 - 網路 & 伺服器 + 網路和伺服器 進階設定 使用 SOCKS 代理伺服器 儲存並通知群組內的聯絡人 @@ -533,7 +533,7 @@ %s 並未驗證 你的 SimpleX 聯絡地址 你的個人檔案 - 數據庫密碼及匯出 + 資料庫密碼和匯出 傳送電郵 SimpleX 鎖定 SMP 伺服器 @@ -545,30 +545,29 @@ 有一些伺服器測試失敗: 掃描伺服器的二維碼 你的伺服器 - 連接時需要使用 Onion 主機。 -\n請注意:如果沒有 .onion 地址,您將無法連接到伺服器。 - 對話檔案 + 連接時需要使用 Onion 主機。 \n請注意:如果沒有 .onion 地址,你將無法連接到伺服器。 + 聊天個人檔案 透過群組連結 透過群組連結使用匿名聊天模式 一個使用了匿名聊天模式的人透過連結加入了群組 透過使用一次性連結匿名聊天模式連接 即時 定期的 - 關閉 - 你錯過了通話 + 停用 + 已錯過通話 開啟 開啟 SimpleX Chat 以接受通話 已拒絕通話 顯示 連接通話中 - 檔案及媒體檔案 + 檔案和媒體 重新啟動應用程式以建立新的對話檔案。 刪除所有檔案及媒體檔案? 刪除所有你的個人對話資料的檔案 %d 檔案(s) 的總共大小為 %s 訊息 永不 - 沒有接收到或傳送到的檔案 + 沒有已接收或已傳送的檔案 目前的密碼… 加密 修改設定時出錯 @@ -584,11 +583,11 @@ 已修改你的身份為 %s 連接中(已接受) 退出 - 負責人 + 擁有者 已移除 完成 連接中 - 創建人 + 建立者 新的成員身份 %1$s 位成員 修改群組內的設定 @@ -599,9 +598,9 @@ 成員的身份會修改為 "%s"。所有在群組內的成員都接收到通知。 成員的身份會修改為 "%s"。該成員將接收到新的邀請。 網路狀態 - 重置為預設值 + 重設為預設值 當你與某人分享已啟用匿名聊天模式的個人檔案時,此個人檔案將用於他們邀請你參加的群組。 - 黑暗 + 深色 為所有人刪除 語音訊息 已提供 %s: %2s @@ -613,7 +612,7 @@ 訊息草稿 保留最後一則帶附件的訊息草稿。 傳輸隔離 - 匯入對話數據庫? + 匯入對話資料庫? 請放置你的密碼於安全的地方,如果你遺失了密碼將不可能再次存取它。 金鑰庫錯誤 沒有聯絡人可以選擇 @@ -621,15 +620,15 @@ 刪除連結 完全去中心化 - 只有成員能看到。 禁止傳送自動銷毀的訊息。 - %d 個小時 + %d 小時 更新內容 帶有可選擇的歡迎訊息。 - 刪除數據庫時出錯 + 刪除資料庫時出錯 匯入 - %s 秒(s) - 加密數據庫時出錯 + %s 秒 + 加密資料庫時出錯 在金鑰庫儲存密碼 - 還原數據庫的備份 + 還原資料庫的備份 群組為不活躍狀態 邀請連結過時! 刪除群組? @@ -641,25 +640,25 @@ 語音訊息於這個聊天室是禁用的。 允許你的聯絡人可以完全刪除訊息。 沒有用戶識別符。 - 新一代的私密訊息平台 + 未來的訊息平台 去中心化的 - 人們只能在你分享了連結後,才能和你連接。 - 重新定義私隱 + 你決定誰可以連接。 + 重新定義隱私 建立你的個人檔案 這是如何運作 - 你可以之後透過設定修改。 + 它如何影響電池 私下連接 任何人都可以託管伺服器。 - 無視 + 忽略 語音通話來電 貼上你收到的連結 端對端加密 沒有端對端加密 關閉喇叭 訊息和檔案 - 私隱 & 安全性 + 隱私和安全性 主題 - 法語界面 + 法語介面 端對端加密的語音通話 拒絕 錯誤的訊息 ID @@ -670,32 +669,32 @@ 幫助 設定 幫助 SIMPLEX CHAT - 對話 + 聊天 開發者工具 SOCKS 代理伺服器 - 重新啟動應用程式以匯入對話數據庫。 + 重新啟動應用程式以匯入對話資料庫。 刪除所有檔案 啟用自動銷毀訊息? 刪除訊息 在多久後刪除訊息 請輸入正確的目前密碼。 - 已受加密的數據庫密碼是使用隨機性的文字,你可以修改它。 - 數據庫將加密。 - 數據庫將加密並且密碼會儲存於金鑰庫。 - 數據庫錯誤 - 不能讀取金鑰庫以儲存數據庫密碼 + 已受加密的資料庫密碼是使用隨機性的文字,你可以修改它。 + 資料庫將加密。 + 資料庫將加密並且密碼會儲存於金鑰庫。 + 資料庫錯誤 + 不能讀取金鑰庫以儲存資料庫密碼 錯誤:%s 檔案:%s - 需要數據庫的密碼以開啟對話。 + 需要資料庫的密碼以開啟對話。 輸入密碼… 輸入正確的密碼。 開啟對話 儲存密碼和開啟對話 - 你未完成修改數據庫密碼的程序。 - 密碼不存在於金鑰庫,請手動輸入它,有這種情況你可能是使用了備份用的工具。如果不是請聯絡開發人員。 + 你未完成修改資料庫密碼的程序。 + Keystore 中找不到密碼,請手動輸入它。如果你使用了備份工具還原應用程式的資料,則可能會發生這種情況。如果不是這種情況,請聯絡開發人員。 還原 - 還原數據庫的備份? - 還原數據庫時出錯 + 還原資料庫的備份? + 還原資料庫時出錯 加入 確定要加入群組? 加入匿名聊天模式 @@ -713,7 +712,7 @@ 刪除群組邀請連結時出錯 為終端機 本機名稱 - 數據庫 ID + 資料庫 ID 身份 傳送私人訊息 成員將被移除於此群組 - 這不能還原! @@ -734,17 +733,17 @@ 更新網路設定? 更新 更新設定會將客戶端重新連接到所有的伺服器。 - 刪除對話資料? - 刪除對話資料給 + 刪除聊天個人檔案? + 刪除聊天個人檔案給 檔案和伺服器連接 只有本機檔案 你的隨機個人檔案 系統 - 明亮 + 淺色 重設顏色 關閉 已接收,已禁用 - 設定為1天 + 設定為 1 天 聯絡人可以標記訊息為已刪除;你仍可以看到那些訊息。 禁止傳送語音訊息。 只有你的聯絡人可以傳送自動銷毀的訊息。 @@ -752,26 +751,26 @@ 不可逆地刪除訊息於這個聊天室內是禁用的。 只有你可以傳送語音訊息。 私訊群組內的成員於這個群組內是禁用的。 - 群組內的成員可以不可逆地刪除訊息。(24小時) + 成員可以不可逆地刪除訊息。(24 小時) 語音訊息 改善伺服器配置 當你切換至最近應用程式版面時,無法預覽程式畫面。 運行對話 - 數據庫密碼 - 匯出數據庫 - 匯入數據庫 - 新的數據庫存檔 - 舊的數據庫存檔 - 刪除數據庫 + 資料庫密碼 + 匯出資料庫 + 匯入資料庫 + 新的資料庫封存 + 舊的資料庫封存 + 刪除資料庫 開啟對話時出錯 停止對話? 設定密碼以匯出 停止對話時出錯 - 已受加密的數據庫是使用一個隨機性的文字。請在修改前將它匯出。 - 匯出數據庫時出錯 - 匯入數據庫時出錯 - 受加密的數據庫密碼會再次更新。 - 加密數據庫? + 已受加密的資料庫是使用一個隨機性的文字。請在修改前將它匯出。 + 匯出資料庫時出錯 + 匯入資料庫時出錯 + 受加密的資料庫密碼會再次更新。 + 加密資料庫? 邀請至群組 %1$s 邀請成員 群組找不到! @@ -779,18 +778,18 @@ 已刪除群組 群組已經刪除 已邀請 - 已透過連結邀請了你進群組 + 已透過你的群組連結邀請 聯絡人允許 %ds 私人通知 GitHub內查看更多。]]> 視訊通話來電 - 掛斷電話來電 + 掛斷 點對點 對話已經過端對端加密 對話沒有經過端對端加密 - 數據庫已加密! - 已加密數據庫 + 資料庫已加密! + 已加密資料庫 群組資料已經更新 成員 你:%1$s @@ -808,8 +807,8 @@ 禁止傳送自動銷毀的訊息。 禁止不可逆的訊息刪除。 禁止傳送語音訊息。 - 群組內的成員可以傳送自動銷毀的訊息。 - 自動銷毀訊息於這個群組內是禁用的。 + 成員可以傳送自動銷毀的訊息。 + 自動銷毀訊息已停用。 已提供 %s 儲存群組檔案時出錯 主題 @@ -823,9 +822,9 @@ 只有你的聊絡人可以不可逆的刪除訊息(你可以將它標記為刪除)。(24小時) 只有你的聯絡人可以傳送語音訊息。 禁止私訊群組內的成員。 - 不可逆地刪除訊息於這個群組內是禁用的。 - 群組內的成員可以傳送語音訊息。 - 語音訊息於這個群組內是禁用的。 + 不可逆地刪除訊息已停用。 + 成員可以傳送語音訊息。 + 語音訊息已停用。 %d 個月 %dm %dmth @@ -847,36 +846,32 @@ 驗證你與聯絡人的安全碼。 感謝用戶 - 使用 Weblate 的翻譯貢獻! 正在修改聯絡地址為 %s … - 受加密的數據庫密碼會再次更新和儲存於金鑰庫。 + 受加密的資料庫密碼會再次更新和儲存於金鑰庫。 SimpleX 是怎樣運作 - 當發生: -\n1. 訊息將在傳送至客戶端後兩天或在伺服器內三十天時過時。 -\n2. 訊息解密失敗,因為你或你的聯絡人用了舊的數據庫備份 -\n3. 連接被破壞。 - 兩層的端對端加密。]]> + 當發生: \n1. 訊息將在傳送至客戶端後兩天或在伺服器內三十天時過時。 \n2. 訊息解密失敗,因為你或你的聯絡人用了舊的資料庫備份 \n3. 連接被破壞。 + 只有客戶端裝置儲存個人檔案、聯絡人、群組,和訊息。 請放置你的密碼於安全的地方,如果你遺失了密碼,將不可能修改你的密碼。 - 停止聊天室以匯出對話,匯入或刪除對話數據庫。當聊天室停止後你將不能接收或傳送訊息。 + 停止聊天室以匯出對話,匯入或刪除對話資料庫。當聊天室停止後你將不能接收或傳送訊息。 你正在使用匿名聊天模式進入此群組 - 為了避免分享你的真實個人檔案,邀請聯絡人是不允許的。 你傳送了一個群組連結 你移除了 %1$s - 你退出了群組 + 你已退出 你修改了聯絡地址為 %s 連接中(邀請介紹階段) - 你目前的對話數據庫會刪除並且以你匯入的對話數據庫頂替上。 -\n這操作不能還原 - 你目前的個人檔案,聯絡人,訊息和檔案將不可逆地遺失。 + 你目前的對話資料庫會刪除並且以你匯入的對話資料庫頂替上。 \n這操作不能還原 - 你目前的個人檔案,聯絡人,訊息和檔案將不可逆地遺失。 透過聯絡人的邀請連結連接 透過一次性連結連接 傳輸隔離 更新傳輸隔離模式? - 為了保護隱私,而不像是其他平台般需要提取和存儲用戶的 IDs 資料, SimpleX 平台有自家佇列的標識符,這對於你的每個聯絡人也是獨一無二的。 + 為了保護你的隱私,SimpleX 對你的每個聯絡人使用不同的 ID。 當應用程式是運行中 透過設定啟用於上鎖畫面顯示來電通知。 這操作不能還原 - 你目前的個人檔案,聯絡人,訊息和檔案將不可逆地遺失。 - 你必須在裝置上使用最新版本的對話數據庫,否則你可能會停止接收某些聯絡人的訊息。 + 你必須在裝置上使用最新版本的對話資料庫,否則你可能會停止接收某些聯絡人的訊息。 這操作不能還原 - 所有已經接收和傳送的檔案和媒體檔案將刪除。低解析度圖片將保留。 這設置適用於你目前的個人檔案 這操作無法撤銷 - 早於所選擇的時間發送和接收的訊息將被刪除。這可能需要幾分鐘的時間。 - 更新數據庫密碼 + 更新資料庫密碼 當每次啟動應用程式後你會需要輸入密碼 - 這不是儲存於你的個人裝置上。 你被邀請加入至群組 你將停止接收來自此群組的訊息。群組內的記錄會保留。 @@ -894,7 +889,7 @@ 開啟視訊 翻轉相機 待確認通話 - 你的私隱 + 你的隱私 你的通話 經由分程傳遞連接 在上鎖畫面顯示來電通知: @@ -903,25 +898,25 @@ 你錯過了多個訊息 實驗性功能 - 你的對話數據庫並未加密 - 設置密碼保護它。 - 數據庫密碼與儲存在金鑰庫中的密碼不同。 + 你的對話資料庫並未加密 - 設置密碼保護它。 + 資料庫密碼與儲存在金鑰庫中的密碼不同。 未知的錯誤 - 還原數據庫備份後請輸入舊密碼。這個操作是不能撤銷的! - 你可以透過 應用程式的設定或透過數據庫 去重新啟動應用程式來開啟對話。 + 還原資料庫備份後請輸入舊密碼。這個操作是不能撤銷的! + 你可以透過 應用程式的設定或透過資料庫 去重新啟動應用程式來開啟對話。 你已經被邀请加入至群組。加入後可與群組內的成員對話。 你已加入至群組 已確認聯絡人 - 你的對話數據庫 - 刪除對話資料? - 錯誤的數據庫密碼 - 未知的數據庫錯誤:%s + 你的對話資料庫 + 刪除聊天個人檔案? + 錯誤的資料庫密碼 + 未知的資料庫錯誤:%s 密碼錯誤! 你已經加入至群組。正在連接至群組內的成員。 這群組已經不存在。 更新群組檔案 你修改了 %s 的身份為 %s 連接中(介紹階段) - 使用對話 + 使用聊天 透過轉送 關閉視訊 你修改了自己的身份為 %s @@ -929,11 +924,11 @@ 由 %s 移除 將為所有成員刪除該訊息。 刪除成員訊息? - 主持 + 審查 該訊息將對所有成員標記為已移除。 - 你不能傳送訊息! - 你是觀察者 - 觀察者 + 你是觀察員 + 你是觀察員 + 觀察員 更新群組連接時出錯 請聯絡群組管理員。 初始角色 @@ -965,7 +960,7 @@ 儲存用戶密碼時出錯 更新用戶隱私時出錯 中繼伺服器僅在必要時使用。 另一方可以觀察到你的 IP 地址。 - 輸入密碼以搜尋! + 在搜尋中輸入密碼 群組歡迎訊息 隱藏的對話資料 不再顯示 @@ -980,35 +975,35 @@ 當靜音配置檔案處於活動狀態時,你仍會接收來自靜音配置檔案的通話和通知。 取消隱藏 影片將在你的聯絡人在線時接收,請你等等或者稍後再檢查! - 確認數據庫更新 - 數據庫版本不相容 - 數據庫降級 - 數據庫升級 + 確認資料庫更新 + 資料庫版本不相容 + 資料庫降級 + 資料庫升級 無效的遷移確認 - 數據庫現行版本比應用程式新,但是無法降級遷出:%s - 在應用程式/數據庫的不同遷移:%s/%s + 資料庫現行版本比應用程式新,但是無法降級遷出:%s + 在應用程式/資料庫的不同遷移:%s/%s 遷移:%s 警告:你可能會遺失一些數據! - 圖片將在你的聯絡人完成上傳後接收。 - 檔案將在你的聯絡人完成上傳後接收。 + 圖片將在你的聯絡人完成上載後接收。 + 檔案將在你的聯絡人完成上載後接收。 實驗性 升級和開啟對話 顯示開發者選項 - 刪除資料 + 刪除個人檔案 取消隱藏聊天資料 取消隱藏個人檔案 - 刪除對話資料 + 刪除聊天個人檔案 詢問以接收影片 同一時間只能傳送十段影片 太多影片! 影片 已傳送影片 等待影片中 - 影片將在你的聯絡人完成上傳後接收。 + 影片將在你的聯絡人完成上載後接收。 等待影片中 隱藏: 顯示: - 數據庫 IDs 和傳輸隔離選項。 + 資料庫 IDs 和傳輸隔離選項。 降級和開啟對話 個人檔案密碼 儲存 XFTP 伺服器時出錯 @@ -1016,7 +1011,7 @@ 加載 SMP 伺服器時出錯 加載 XFTP 伺服器時出錯 建立檔案 - 伺服器需要認證後才能上傳,檢查密碼 + 伺服器需要認證後才能上載,檢查密碼 對比檔案 刪除檔案 下載檔案 @@ -1060,7 +1055,7 @@ 停止 停止接收檔案? 錯誤的訊息 ID - 當你或你的連結在用舊的數據庫備份時會發生。 + 當你或你的連結在用舊的資料庫備份時會發生。 上一則訊息的雜奏則是不同的。 應用程式密碼 迅速以及不用等待發送者在線! @@ -1082,7 +1077,7 @@ " \n在 v5.1中可用" 允許你的聯絡人與你進行通話。 - 上傳檔案 + 上載檔案 XFTP 伺服器 系統認證 你未能通過認證;請再試一次。 @@ -1090,14 +1085,14 @@ 系統 此ID的下一則訊息是錯誤(小於或等於上一則的)。 \n當一些錯誤出現或你的連結被破壞時會發生。 - %1$d 訊息解密失敗。 + %1$d 條訊息解密失敗。 使用SOCKS 代理伺服器 你的 XFTP 伺服器 %1$d 條訊息已跳過。 影片和檔案和最大上限為1gb 影片 - 呈交 - 開啟數據庫中… + 提交 + 開啟資料庫中… 查看更多 SimpleX 聯絡地址 一次性連結 @@ -1130,15 +1125,15 @@ 已傳送訊息 標題 關於 SimpleX 的聯絡地址 - 額外的強調色 + 額外的輔色 外加的輔助 - 聯絡地址 - 後台 + 地址 + 背景 建立一個聯絡地址讓其他用戶與你連接。 自定義主題 黑暗主題 設定聯絡地址時出錯 - 開啟多個個人檔案 + 變更聊天個人檔案 內查看更多關於用戶指南的資料。]]> 自毁 啟用自毀密碼 @@ -1152,7 +1147,7 @@ 如果你在開啟應用程式時輸入此密碼,所有應用程式資料將被不可逆轉地刪除! 啟用自毀 應用程式密碼已替換為自毀密碼。 - 已建立一個具有所提供名稱的空白對話檔案,而應用程式將照常開啟。 + 已建立一個具有所提供名稱的空白聊天個人檔案,而應用程式將照常開啟。 已刪除所有的應用程式數據。 設定密碼 你可以與聯絡人分享此地址,讓他們使用 %s 進行連接。 @@ -1164,10 +1159,10 @@ 當有人向你發出連接請求,你可以接受或拒絕請求。 你的聯絡人會保持連接。 你的所有聯絡人會保持連接。更新了的個人檔案將傳送給你的聯絡人。 - 將聯絡地址新增到你的個人檔案中,以便你的聯絡人與其他人分享你的聯絡地址。更新了的個人檔案將傳送給你的聯絡人。 + 新增地址至你的個人檔案,以便你的聯絡人可以與其他人分享。更新了的個人檔案將傳送給你的聯絡人。 讓我們在 SimpleX Chat中聊天 確保檔案具有正確的 YAML 語法。匯出主題以獲得主題檔案結構的範例。 - 訊息互動 + 訊息反應 允許你的聯絡人新增訊息互動。 星期 @@ -1178,10 +1173,10 @@ 自定義時間 傳送 自動銷毀訊息 - 已移除在:%s + 已移除於:%s (目前的) - 允許訊息互動。 - 更佳的訊息 + 允許訊息反應。 + 更好的訊息 自定義和分享顏色主題 自定義主題 小時 @@ -1189,19 +1184,19 @@ 分鐘 個月 - 自定義 + 自訂 已接收在 已刪除在 - 已傳送在 - 數據庫 ID:%d + 已傳送於 + 資料庫 ID:%d 已移除在 - 1分鐘 - 30秒 - 5分鐘 - 已刪除在:%s + 1 分鐘 + 30 秒 + 5 分鐘 + 已刪除於:%s 禁止訊息互動。 - 群組內的成員可以新增訊息互動。 - 訊息互動於這個群組內是禁用的。 + 成員可以新增訊息反應。 + 訊息反應已停用。 加載詳細資料時出錯 傳送自動銷毀訊息 %s (目前的) @@ -1216,35 +1211,35 @@ 所有數據會在輸入後刪除。 沒有文字 紀錄已更新在 - 已傳送在:%s - 已接收在:%s + 已傳送於:%s + 已接收於:%s 紀錄已更新在:%s 只有你可以新增訊息互動。 銷毀於 - 訊息互動 + 訊息反應 感謝用戶-透過 Weblate 做出貢獻! 你和你的聯絡人可以新增訊息互動。 - 導入過程中出現了一些非致命錯誤 - 你可以到綜端機對話以獲取更多詳細資訊。 + 匯入過程中出現了一些非致命錯誤: 只有你的聯絡人允許的情況下,才允許訊息互動。 - 長達5分鐘的語音訊息。 \n- 自定義銷毀時間。 \n- 編輯紀錄。 搜尋 - 已關閉 + 關閉 確認來自未知伺服器的檔案。 超出額度 - 收件人未收到先前傳送的訊息 應用程式資料轉移 - 應用 - 請在轉移之前確認你還記得數據庫密碼 + 套用 + 請在轉移之前確認你還記得資料庫密碼 被管理員封鎖 進階設定 封鎖群組成員 活躍連接 中止 - 和其他 %d 事件 + 和其他 %d 個事件 封鎖成員? - 6種全新的介面語言 - 藍芽 + 6 種全新的介面語言 + 藍牙 %2$s 審核了 %1$d 條訊息 已封鎖 將停止地址更改。將使用舊聯絡地址。 @@ -1254,12 +1249,12 @@ 封鎖 應用程式主題 管理員 - 模糊以增強隱私 + 模糊以增強隱私。 所有成員 管理員可以為所有人封鎖一名成員 無法傳送訊息 為所有成員封鎖此成員? - + 黑色 中止更改地址? 無法傳送訊息給群組成員 所有顏色模式 @@ -1267,14 +1262,14 @@ 應用程式密碼 應用程式 聊天顏色 - 聊天已停止。如果你已經在另一台設備使用過此資料庫,你應該在啟動聊天前將數據庫傳輸回來。 + 聊天已停止。如果你已經在另一台設備使用過此資料庫,你應該在啟動聊天前將資料庫傳輸回來。 即將推出! 軟體更新以下載 儲存聯絡人以便稍後聊天 相機 選擇一個檔案 - 存檔並上傳 - 你的所有聯絡人、對話和檔案將被安全加密並切塊上傳到你設定的 XFTP 中繼 + 封存並上載 + 你的所有聯絡人、對話和檔案將被安全加密並切塊上載到你設定的 XFTP 中繼 正在儲存資料庫 取消遷移 與 %s 協調加密中… @@ -1284,12 +1279,12 @@ 所有訊息都將被刪除 - 這無法復原 請注意:訊息和檔案中繼通過 SOCKS 代理連接。通話和傳送連預覽使用直接連接。]]> 封鎖全部 - 改進群組功能 + 更好的群組 行動網路 封鎖成員 - 警告:此存檔將被刪除。]]> + 警告:此封存將被刪除。]]> 清除私密筆記? - 添加聯絡人 + 新增聯絡人 總是 協調加密中… 允許傳送檔案和媒體 @@ -1303,23 +1298,23 @@ 作者 已封鎖 被管理員封鎖 - 額外的強調色2 + 額外的輔色 2 阿拉伯語、保加利亞語、芬蘭語、希伯來語、泰國語和烏克蘭語——感謝使用者們與Weblate 已加入群組! 確認網路設定 嘗試 已確認 確認錯誤 - 完成 + 已完成 區塊已刪除 - 區塊已上傳 + 區塊已上載 區塊已下載 添加聯絡人: 來創建新的邀請連結,或通過你收到的連結進行連接。]]> 建立群組: 建立新的群組。]]> 錯誤的桌面地址 已轉移聊天 從另一部設備轉移 並掃描QR code。]]> - 請注意: 作為安全保護措施,在兩部設備上使用同一數據庫會破壞解密來自你聯絡人的訊息。]]> + 請注意: 作為安全保護措施,在兩部設備上使用同一資料庫會破壞解密來自你聯絡人的訊息。]]> 確定刪除聯絡人? 檢查更新 無法與聯絡人通話 @@ -1327,7 +1322,7 @@ 無法與群組成員通話 應用程式將為新的本機檔案(影片除外)加密。 檢查你的網路連接並重試 - 所有配置文件 + 所有個人檔案 已設定的 SMP 伺服器 聊天主題 通話 @@ -1343,23 +1338,23 @@ Webview 初始化失敗。更新你的系統到新版本。請聯繫開發者。 \n錯誤:%s 已刪除聯絡人 - %d 個群事件 + %d 個群組事件 訊息太大 訊息傳送警告 錯誤:%1$s 開發者選項 與 %s 的加密需要重協商 %s 不活躍]]> - 最喜歡 + 最愛 訊息成功送達! 檔案和媒體 連線停止 %s的連接不穩定]]> 聯絡人 適合 - 群組成員可以傳送檔案和媒體。 - 連結行動裝置 - 此群組禁止檔案和媒體 + 成員可以傳送檔案和媒體。 + 已連結行動裝置 + 檔案和媒體已停用。 結束通話 刪除 %d 條訊息嗎? 訊息草稿 @@ -1373,11 +1368,11 @@ 啟用(保留組覆蓋) 淺色 淺色模式 - 群組成員可傳送 SimpleX 連結。 + 成員可以傳送 SimpleX 連結。 深色 詳情 訊息接收 - 無效連結 + 無效的連結 %d 條訊息被標記為刪除 %d 條訊息已攔截 轉發伺服器地址不相容網路設定:%1$s。 @@ -1391,7 +1386,7 @@ 如果成員變得活躍,可能會在之後傳送訊息。 刪除了聯絡人! 聯絡人將被刪除 - 無法復原此操作 - + 已停用 安裝成功 建立 @@ -1409,7 +1404,7 @@ 新的聊天主題 連線和伺服器狀態 控制你的網路 - 從GitHub下載最新版本。 + 從 GitHub 下載最新版本。 啟用 新的聊天體驗 🎉 新的媒體選項 @@ -1418,11 +1413,11 @@ 連接到桌面 連線終止 連接到桌面 - 上傳存檔出錯 - 文件被刪除或鏈接無效 - 導入失敗 - 頭戴式耳機 - 耳機 + 上載封存出錯 + 檔案已被刪除或連結無效 + 匯入失敗 + 耳機 + 聽筒 對所有聯絡人關閉 深色模式 啟用已讀回條時出錯! @@ -1439,7 +1434,7 @@ 檔案錯誤 備用訊息路由 如果你或你的目標伺服器不支持私密路由,將不直接傳送訊息。 - 建立個人資料 + 建立個人檔案 從另一台裝置轉移 成員姓名從 %1$s 改為了 %2$s 同意加密 @@ -1450,7 +1445,7 @@ \n- 還有更多! 匈牙利語和土耳其語用戶界面 轉移完成 - 從此裝置刪除數據庫 + 從此裝置刪除資料庫 傳送 下載 轉發 @@ -1460,7 +1455,7 @@ 轉發訊息… 建立連結中… 保留 - 聯絡人姓名從 %1$s 改為了 %2$s + 聯絡人名稱從 %1$s 變更為 %2$s 新訊息 邀請 停用(保留覆蓋) @@ -1487,7 +1482,7 @@ 轉發伺服器:%1$s \n錯誤:%2$s 轉發伺服器 %1$s 連結目標伺服器 %2$s 失敗。請稍後嘗試。 - 導入存檔中 + 正在匯入封存 改進訊息傳送 安裝更新 它保護你的 IP 位址和連線。 @@ -1495,13 +1490,13 @@ %s 的版本。請檢察兩台裝置安裝的是否版本相同]]> 更可靠的網路連接 發現和加入群組 - 設備 + 裝置 新行動裝置 保存設定出錯 導出的檔案不存在 導出資料庫時出錯 - 確認上傳 - 正在建立存檔連結 + 確認上載 + 正在建立封存連結 加密OK 將顯示來自 %s 的訊息! 送達回執! @@ -1512,12 +1507,12 @@ 加密重協商失敗 將更新資料庫密碼並儲存在設定中。 使用隨機身分建立群組 - 加入速度更快、訊息更可靠。 + 更快的加入速度,訊息更可靠。 匿名群組 - 連接行動裝置 + 連結行動裝置 回復 和 %1$s 連接? - 在桌面應用裡建立新的帳號。💻 + 在桌面應用程式建立新的個人檔案。💻 輸入此裝置名稱… 已連結到行動裝置 可通過局域網發現 @@ -1525,10 +1520,10 @@ 嚴重錯誤 內部錯誤 驗證密碼短語出錯: - 顯示名稱無效! + 無效的顯示名稱! 無效的檔案路徑 過濾未讀和收藏的聊天記錄。 - 斷開連結 + 斷開連接 斷開桌面連結? 中止地址更改時出錯 顯示通知出錯,請聯繫開發者。 @@ -1550,12 +1545,12 @@ 已關閉送達回執! %s 斷開連接]]> %s 未找到]]> - 存檔下載中 + 封存下載中 轉移中 下載失敗 為所有組啟用 需要重協商加密 - 關閉 + 已停用 修復連結 修復聯絡人不支援的問題 修復 @@ -1571,14 +1566,14 @@ 顯示內容出錯 顯示訊息出錯 錯誤 - 無後台通話 - 建立聊天資料 + 無背景通話 + 建立聊天個人檔案 已同意 %s 的加密 允許重新協商與 %s 的加密 與 %s 的加密OK 桌面設備 - 連接桌面選項 - 連接桌面 + 已連結桌面選項 + 已連結桌面 直接連線中 邀請 建立群組 @@ -1621,7 +1616,7 @@ 已下載的檔案 下載出錯 功能執行所花費的時間過長:%1$d 秒:%2$s - 無效名稱 + 無效的名稱! 正確名字為 %s? 複製錯誤 正在連接到桌面 @@ -1634,14 +1629,14 @@ 通過連結連接? 加入你的群組嗎? 轉移到此處 - 無效連結 + 無效的連結 在另一部設備上完成轉移 - 下載存檔錯誤 + 下載封存錯誤 轉移到另一部裝置 必須停止聊天才能繼續。 保留對話 不通知刪除 - 無效的QR code + 無效的 QR 圖碼 保留未使用的邀請嗎? 停用回執? 啟用回執? @@ -1651,7 +1646,7 @@ 為儲存的檔案和媒體加密 連接到你自己? 完成轉移 - 目前配置文件 + 目前個人檔案 檔案 重連伺服器出錯 重連伺服器出錯 @@ -1662,14 +1657,14 @@ 資料庫將被加密,密碼將儲存在設定中 刪除成員的 %d 條訊息嗎? 成員非活躍 - 輸入訊息 + 訊息 訊息將被標記為刪除。收信人可以揭示這些訊息。 連接 訊息 停用 下載更新中,請不要關閉應用 下載 %s(%s) - 從不 + 永不 中等 媒體和檔案伺服器 訊息伺服器 @@ -1684,7 +1679,7 @@ 為群組停用回執? 過往的成員 %1$s 私密訊息路由 🚀 - 貼上存檔連結 + 貼上封存連結 從桌面使用並掃描QR code。]]> 無傳送資訊 或者顯示此碼 @@ -1694,11 +1689,11 @@ 請稍後再試。 沒有過濾的聯絡人 打開檔案位置 - 打開 + 開啟 私密筆記 波斯語用戶界面 從聊天列表播放。 - 正在準備上傳 + 正在準備上載 擁有者 不相容! 請將它報告給開發者: @@ -1708,21 +1703,21 @@ 從已連接行動裝置加載檔案時請稍候片刻 或者掃描QR code 禁止傳送 SimpleX 連結 - 沒有選擇聊天 + 沒有已選擇的聊天 打開設定 這人資料主題 個人資料圖片 同一時刻只有一台裝置可工作 保護 IP 地址 其他 - 無網路連接 + 沒有網路連接 無歷史記錄 無過濾聊天 請將它報告給開發者: \n%s 打開應用程式設定 開啟轉移畫面 - 通知將停止,直到您重啟應用程式 + 通知將停止,直到你重啟應用程式 禁止傳送檔案和媒體。 打開群組 只有群組所有者才能啟用檔案和媒體。 @@ -1740,7 +1735,7 @@ 請檢查行動裝置和桌面設備連接到的是同一個本地網絡,且桌面防火牆允許連接。 \n請和開發者分享任何其他問題。 在防火牆中打開端口 - 或貼上存檔連結 + 或貼上封存連結 正在準備下載 只刪除對話 貼上連結 @@ -1754,7 +1749,7 @@ 私密路由出錯 尚無直接連接,訊息由管理員轉發。 什麼也沒選中 - 打開 + 開啟 私密路由 打開資料庫文件夾 私密訊息路由 @@ -1766,28 +1761,368 @@ 接收到的訊息 接收錯誤 重新連接所有已連接的伺服器來強制傳送訊息。這會使用額外流量。 - 重連伺服器強制傳送訊息。這會使用額外流量。 + 重新連接伺服器以強制傳送訊息。這會使用額外流量。 並行接收 收件人看不到這條訊息來自誰。 刪除了資料圖片 - 可使用的聊天工具箱 - 可存取的聊天工具欄 + 單手模式的應用程式工具列 + 單手模式的聊天工具列 最近歷史和改進的目錄機器人。 每 KB 協議超時 - 保護您的真實 IP 地址。不讓你聯絡人選擇的訊息中繼看到它。 -\n在*網絡&伺服器*設定中開啓。 - 隨機密碼以明文形式儲存在設定中。 -\n您可以稍後更改。 + 保護你的真實 IP 地址,以不讓你的聯絡人所選擇的訊息中繼看到它。 \n在*網路和伺服器*設定中啟用。 + 隨機密碼以明文形式儲存在設定中。 \n你可以稍後更改。 傳送回條已禁用 代理伺服器 - 重連伺服器? + 重新連接伺服器? 抗量子端到端加密 收到的回覆 - 重連所有伺服器 - 重連 + 重新連接所有伺服器 + 重新連接 代理 隨機 - 更新 + 重新整理 接收總計 稍後提醒 + 新增朋友 + 營運者伺服器 + 關閉? + 此裝置 + 審核員 + %1$d 個檔案下載失敗。 + 聊天已存在! + 沒有訊息伺服器。 + %1$d 個檔案錯誤:\n%2$s + 設定聊天名稱… + 靜音全部 + 刪除 + + + 關閉 + 此裝置名稱 + 關於營運者 + 預設(%s) + 聯絡人 + 你的聯絡人 + 聊天資料庫 + 詢問 + + 重設顏色 + 已傳送回覆 + 審核員 + 拒絕 + 重設 + 已傳送總數 + 清單 + 設定 + 清單名稱… + 單手模式的聊天工具列 + %1$d 個檔案仍然正在下載。 + %1$s 條訊息未被轉發 + %1$d 個檔案已刪除。 + 下載 + 筆記 + 尾部 + 重新啟動 + 應用程式工具列 + 伺服器地址 + 營運者 + 你已退出 + 顯示百分比 + 伺服器 + 接受 + + %s:%s + 聊天 + 你的伺服器 + 縮放 + 模糊 + %1$d 個檔案未被下載。 + 1 年 + 接受 + 已訂閱 + SimpleX 頻道連結 + %s、%s,和 %d 名成員 + 待處理 + 解除連結 + 最愛 + 給所有審核員 + 舉報 + 濫發 + 搜尋 + 影片 + 取消最愛 + + 喇叭 + 解除封鎖 + 網站 + 透明度 + 重複 + 編輯 + 重新同步 + 選擇 + 使用者名稱 + 密碼 + 更新 + 繼續 + 已拒絕 + 系統 + 關閉 + 全部 + 已受保護 + 全部 + 商業 + 角落 + 群組 + 報告 + 統計 + 比例 + 重試 + 穩定 + 審核 + 未知 + 關閉 + 已儲存 + 封存 + 已拒絕 + 大小 + WiFi + 修復 + 已儲存 + 濫發 + 已上載 + 未受保護 + 沒有私密訊息路由伺服器。 + 沒有訊息接收伺服器。 + 沒有媒體和檔案伺服器。 + 其他 %1$d 個檔案錯誤。 + 已新增媒體和檔案伺服器 + %s 已封存報告 + 沒有檔案傳送伺服器。 + 沒有檔案接收伺服器。 + 封存報告 + 此聊天受到抗量子端對端加密保護。 + 使用目前個人檔案 + 使用新的匿名個人檔案 + 更好的通話 + 地址設定 + 已新增訊息伺服器 + 你分享了一個無效的檔案路徑。請將此問題報告給應用程式開發者。 + 如果沒有 Tor 或 VPN,你的 IP 位址將對以下 XFTP 中繼可見:\n%1$s。 + 檢視已崩潰 + 新增短連結 + 接受了 %1$s + 接受了你 + 新增團隊成員 + 接受成員 + 新增清單 + SimpleX 無法在背景執行。你只有在應用程式執行時才會收到通知。 + 應用程式工作階段 + 已接受邀請 + 此顯示名稱無效。請選擇另一個名稱。 + 新增至清單 + 地址還是一次性連結? + 應用程式總是在背景執行 + 已接受的條款 + 接受為成員 + 接受為觀察員 + 全部伺服器 + 允許向審核員舉報訊息。 + 封存全部報告? + 封存 %d 份報告? + 封存報告? + 封存報告 + 1 個舉報 + 無背景服務 + 短連結 + 已封存的報告 + 只有你和審核員能夠檢視 + 只有傳送者和審核員能夠檢視 + 已儲存自 %s + 此聊天受到端對端加密保護。 + 另一個原因 + 不當的個人檔案 + 不當的內容 + 違反社群準則 + 接受條款 + 重複連接請求? + 移除封存? + 已直接傳送 + 傳送錯誤 + 連接安全性 + 變更排序 + 訊息形狀 + 驗證連接 + 視訊通話 + 刪除舉報 + 使用 %s + 重新同步加密? + 已儲存自 + 連接被封鎖 + 建立清單 + 轉發訊息… + 建立一次性連結 + 網路營運者 + 解除連結桌上電腦? + 重複上載 + 設定資料庫密碼 + %s 已上載 + 已更新個人檔案 + 開啟連結 + 設定資料庫密碼 + 工作階段代碼 + 已傳送訊息 + 正在停止聊天 + 伺服器資訊 + %d 條訊息 + %d 個聊天 + 設定密碼短語 + 待審核 + 未知伺服器! + 伺服器營運者 + 已解除封鎖 %s + 安全碼已變更 + 未知狀態 + 傳送回條 + 移除成員? + 重新同步加密 + 使用伺服器 + TCP 連接 + 用於傳送 + SimpleX 連結 + 審核成員 + 刪除聊天 + 拒絕成員? + 重新啟動聊天 + %s 已下載 + 重複下載 + 重複匯入 + 上載失敗 + 驗證密碼短語 + 已選取 %d + SOCKS 代理 + 解除封鎖成員 + 使用隨機密碼 + %s 已連接 + 已儲存訊息 + 更安全的群組 + XFTP 伺服器 + 變更清單 + 新伺服器 + 儲存清單 + 刪除清單? + 解除封鎖成員? + 網路營運者 + 系統模式 + 驗證連接 + 開始聊天? + %s 在 %s + 移除成員? + 移除成員 + 移除圖片 + 傳送私人訊息 + 聯絡人已刪除 + 未同步 + 聯絡人已停用 + 稍後審核 + 分享個人檔案 + 完整連結 + 已上載檔案 + 上載錯誤 + 正在上載封存 + 開始聊天 + SMP 伺服器 + 傳輸工作階段 + 修復連接? + 總計 + 未知伺服器 + 舉報理由? + %s 和 %s + 減少電量使用。 + 你的個人檔案 %1$s 將會被分享。 + 你無法傳送訊息! + 減少電量使用。 + 你已解除封鎖 %s + 你已接受此成員 + 停用刪除訊息 + 商業地址 + 變更自動燒毀訊息? + 與成員聊天 + 與管理員們聊天 + 刪除聊天 + 刪除聊天? + 與成員們聊天 + 商業聊天 + 更好的安全性 ✅ + 與管理員們聊天 + 無法傳送訊息 + 可自訂訊息形狀。 + 更好的使用體驗 + 內容違反使用條款 + 停用自動刪除訊息? + 刪除或審查最多 200 條訊息。 + 於網路和伺服器設定中啟用 Flux 以獲得更好的元資料隱私。 + 配置伺服器營運者 + 使用條款 + 更好的隱私和安全性 + 你可以再試一次。 + 每 10 分鐘檢查訊息 + 聯絡人未就緒 + 你可以在外觀設定中變更它。 + 從你的裝置刪除聊天訊息。 + 更好的群組性能 + 你已封鎖 %s + 連接未就緒。 + 更好的訊息日期。 + 為了更好的元資料隱私。 + 你可以再試一次。 + 使用匿名個人檔案 + 開啟聊天 + 連接 + 升級 + 群組 + 可見的紀錄 + 慢速函數 + 有線以太網 + 桌布背景 + 桌布輔色 + 訂閱已忽略 + 訂閱錯誤 + 代理身份驗證 + 已靜音 + 未送達的訊息 + 沒有訊息 + 預設伺服器 + 審閱使用條款 + %s 個伺服器 + 檢視使用條款 + 接收 + 開啟使用條款 + 開啟變更 + 網路去中心化 + 離開聊天? + 離開聊天 + 啟用日誌 + 沒有聊天 + 成員舉報 + 僅自己 + 舉報:%s + 待批准 + 已更新使用條款 + 預設伺服器 + 加入群組 + 增加訊息 + 傳送請求 + 你的個人資料 + 歡迎訊息 + 升級地址? + 正在載入個人資料… + 你的簡介: + 簡短描述: + 你的聯絡人 + 你的群組 + 商業連接 + 顯示最近的訊息 + 簡化的匿名模式 + 點擊以連接 + 從桌面使用 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index dfffb826f5..136a883035 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -171,7 +171,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { // Shows toast in insertion order with preferred delay per toast. New one will be shown once previous one expires LaunchedEffect(toast, toasts.size) { delay(toast.second) - simplexWindowState.toasts.removeFirst() + simplexWindowState.toasts.removeFirstOrNull() } } var windowFocused by remember { simplexWindowState.windowFocused } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 53f3301507..d0ba082adf 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -133,9 +133,9 @@ actual fun ImageBitmap.hasAlpha(): Boolean { return false } -actual fun ImageBitmap.addLogo(): ImageBitmap { - val radius = (width * 0.16f).toInt() - val logoSize = (width * 0.24).toInt() +actual fun ImageBitmap.addLogo(size: Float): ImageBitmap { + val radius = (width * size).toInt() + val logoSize = (width * size * 1.5).toInt() val logo: BufferedImage = MR.images.icon_foreground_common.image val original = toAwtImage() val withLogo = BufferedImage(width, height, original.type) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt index 7a2a1dff0a..696e0efde8 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -93,7 +93,7 @@ actual fun LazyColumnWithScrollBar( } val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) { - LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content = content) ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, additionalTopBar, chatBottomBar) } } @@ -138,7 +138,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( // (only first visible row is useful because LazyColumn doesn't have absolute scroll position, only relative to row) val scrollBarDraggingState = remember { mutableStateOf(false) } Box(contentAlignment = containerAlignment) { - LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content = content) Box(if (maxHeight?.value != null) Modifier.height(maxHeight.value).fillMaxWidth() else Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/SimplexService.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/SimplexService.desktop.kt new file mode 100644 index 0000000000..d29871f842 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/SimplexService.desktop.kt @@ -0,0 +1,3 @@ +package chat.simplex.common.platform + +actual fun getWakeLock(timeout: Long): (() -> Unit) = {} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt index e3b0642547..9be10a584b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt @@ -195,9 +195,11 @@ fun WebRTCController(callCommand: SnapshotStateList, onResponse: ( delay(100) } while (callCommand.isNotEmpty()) { - val cmd = callCommand.removeFirst() + val cmd = callCommand.removeFirstOrNull() Log.d(TAG, "WebRTCController LaunchedEffect executing $cmd") - processCommand(cmd) + if (cmd != null) { + processCommand(cmd) + } } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt index cd206c8e4e..e6f782d816 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt @@ -32,7 +32,7 @@ actual fun ReactionIcon(text: String, fontSize: TextUnit) { @Composable actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserLauncher, showMenu: MutableState) { - ItemAction(stringResource(MR.strings.save_verb), painterResource(if (cItem.file?.fileSource?.cryptoArgs == null) MR.images.ic_download else MR.images.ic_lock_open_right), onClick = { + ItemAction(stringResource(MR.strings.save_verb), painterResource(MR.images.ic_download), onClick = { val saveIfExists = { when (cItem.content.msgContent) { is MsgContent.MCImage, is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withLongRunningApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt index 3855835ab6..52e845b422 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -63,7 +63,7 @@ actual fun UserPickerUsersSection( ProfileImage(size = 55.dp, image = user.profile.image, color = iconColor) if (u.unreadCount > 0 && !user.activeUser) { - unreadBadge(u.unreadCount, user.showNtfs, true) + userUnreadBadge(u.unreadCount, user.showNtfs, true) } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.desktop.kt index 03c8e51c55..f1103bc516 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/CustomTimePicker.desktop.kt @@ -31,6 +31,7 @@ actual fun CustomTimePicker( mutableStateOf(res) } val values = remember(unit.value) { + // TODO replace with firstOrNull val limit = timeUnitsLimits.first { it.timeUnit == unit.value } val res = ArrayList>() for (i in limit.minValue..limit.maxValue) { diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index e39ba48a0b..60ff535e88 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -1,9 +1,10 @@ +import org.gradle.internal.extensions.stdlib.toDefaultLowerCase import org.jetbrains.compose.desktop.application.dsl.TargetFormat -import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly plugins { kotlin("multiplatform") id("org.jetbrains.compose") + id("org.jetbrains.kotlin.plugin.compose") id("io.github.tomtzook.gradle-cmake") version "1.2.2" } @@ -89,7 +90,7 @@ compose { } } } - val os = System.getProperty("os.name", "generic").toLowerCaseAsciiOnly() + val os = System.getProperty("os.name", "generic").toDefaultLowerCase() if (os.contains("mac") || os.contains("win")) { packageName = "SimpleX" } else { diff --git a/apps/multiplatform/desktop/src/jvmMain/resources/distribute/chat.simplex.app.appdata.xml b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/chat.simplex.app.appdata.xml index 9f9b2d6d20..2254c6d5bf 100644 --- a/apps/multiplatform/desktop/src/jvmMain/resources/distribute/chat.simplex.app.appdata.xml +++ b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/chat.simplex.app.appdata.xml @@ -52,7 +52,7 @@ https://github.com/simplex-chat/simplex-chat/issues https://github.com/simplex-chat/simplex-chat#help-translating-simplex-chat https://simplex.chat/connect-team - https://github.com/simplex-chat/simplex-chat#help-us-with-donations + https://github.com/simplex-chat/simplex-chat#please-support-us-with-your-donations diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index a51b70e52b..b4f41b8221 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,17 +24,17 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.7 -android.version_code=298 +android.version_name=6.4.5 +android.version_code=319 android.bundle=false -desktop.version_name=6.3.7 -desktop.version_code=108 +desktop.version_name=6.4.5 +desktop.version_code=120 -kotlin.version=1.9.23 -gradle.plugin.version=8.2.0 -compose.version=1.7.0 +kotlin.version=2.1.20 +gradle.plugin.version=8.7.0 +compose.version=1.8.2 # Choose sqlite or postgres backend database.backend=sqlite diff --git a/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties b/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties index 4e4a6a3f29..6183001f7b 100644 --- a/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties +++ b/apps/multiplatform/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Feb 14 14:23:51 GMT 2022 +#Fri Mar 21 20:38:56 ICT 2025 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/apps/multiplatform/settings.gradle.kts b/apps/multiplatform/settings.gradle.kts index ba047edf1e..40446f1958 100644 --- a/apps/multiplatform/settings.gradle.kts +++ b/apps/multiplatform/settings.gradle.kts @@ -12,6 +12,7 @@ pluginManagement { id("com.android.application").version(extra["gradle.plugin.version"] as String) id("com.android.library").version(extra["gradle.plugin.version"] as String) id("org.jetbrains.compose").version(extra["compose.version"] as String) + id("org.jetbrains.kotlin.plugin.compose").version(extra["kotlin.version"] as String) id("org.jetbrains.kotlin.plugin.serialization").version(extra["kotlin.version"] as String) } } diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs index 8107b664c4..d9f091a13a 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs @@ -11,10 +11,11 @@ import Data.Text (Text) import Options.Applicative import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (updateStr, versionNumber, versionString) -import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, coreChatOptsP) +import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, CreateBotOpts (..), coreChatOptsP) data BroadcastBotOpts = BroadcastBotOpts { coreOptions :: CoreChatOpts, + botDisplayName :: Text, publishers :: [KnownContact], welcomeMessage :: Text, prohibitedMessage :: Text @@ -29,6 +30,12 @@ defaultProhibitedMessage ps = "Sorry, only these users can broadcast messages: " broadcastBotOpts :: FilePath -> FilePath -> Parser BroadcastBotOpts broadcastBotOpts appDir defaultDbName = do coreOptions <- coreChatOptsP appDir defaultDbName + botDisplayName <- + strOption + ( long "display-name" + <> metavar "DISPLAY_NAME" + <> help "The display name of the broadcast bot" + ) publishers <- option parseKnownContacts @@ -55,6 +62,7 @@ broadcastBotOpts appDir defaultDbName = do pure BroadcastBotOpts { coreOptions, + botDisplayName, publishers, welcomeMessage = fromMaybe (defaultWelcomeMessage publishers) welcomeMessage_, prohibitedMessage = fromMaybe (defaultProhibitedMessage publishers) prohibitedMessage_ @@ -72,7 +80,7 @@ getBroadcastBotOpts appDir defaultDbName = versionAndUpdate = versionStr <> "\n" <> updateStr mkChatOpts :: BroadcastBotOpts -> ChatOpts -mkChatOpts BroadcastBotOpts {coreOptions} = +mkChatOpts BroadcastBotOpts {coreOptions, botDisplayName} = ChatOpts { coreOptions, chatCmd = "", @@ -86,5 +94,6 @@ mkChatOpts BroadcastBotOpts {coreOptions} = autoAcceptFileSize = 0, muteNotifications = True, markRead = False, + createBot = Just CreateBotOpts {botDisplayName, allowFiles = False}, maintenance = False } diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index 0906d14536..b84bf8b579 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -13,12 +13,10 @@ module Server where import Control.Monad -import Control.Monad.Except import Control.Monad.Reader import Data.Aeson (FromJSON, ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.TH as JQ -import Data.Bifunctor (first) import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) import GHC.Generics (Generic) @@ -51,13 +49,15 @@ $(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "Obj") ''ObjChatCmdError) $(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "Obj") ''ObjChatError) +-- this encoding preserves the websocket API format when ChatError was sent either with type: "chatError" or type: "chatCmdError" instance ToJSON (CSRBody ChatResponse) where - toJSON = toJSON . first ObjChatCmdError . csrBody - toEncoding = toEncoding . first ObjChatCmdError . csrBody + toJSON = either (toJSON . ObjChatCmdError) toJSON . csrBody + toEncoding = either (toEncoding . ObjChatCmdError) toEncoding . csrBody +-- this encoding preserves the websocket API format when ChatError was sent either with type: "chatError" or type: "chatCmdError" instance ToJSON (CSRBody ChatEvent) where - toJSON = toJSON . first ObjChatError . csrBody - toEncoding = toEncoding . first ObjChatError . csrBody + toJSON = either (toJSON . ObjChatError) toJSON . csrBody + toEncoding = either (toEncoding . ObjChatError) toEncoding . csrBody data AChatSrvResponse = forall r. ToJSON (ChatSrvResponse r) => ACR (ChatSrvResponse r) @@ -127,7 +127,7 @@ runChatServer ChatServerConfig {chatPort, clientQSize} cc = do where sendError corrId e = atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId, resp = CSRBody $ chatCmdError e} processCommand (corrId, cmd) = - response <$> runReaderT (runExceptT $ processChatCommand cmd) cc + response <$> runReaderT (execChatCommand' cmd 0) cc where response r = ChatSrvResponse {corrId = Just corrId, resp = CSRBody r} clientDisconnected _ = pure () diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index faaccbd2bf..8ae7f60b3d 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -66,7 +66,7 @@ crDirectoryEvent :: Either ChatError ChatEvent -> Maybe DirectoryEvent crDirectoryEvent = \case Right evt -> crDirectoryEvent_ evt Left e -> case e of - ChatErrorAgent {agentError = BROKER _ NETWORK} -> Nothing + ChatErrorAgent {agentError = BROKER _ (NETWORK _)} -> Nothing ChatErrorAgent {agentError = BROKER _ TIMEOUT} -> Nothing _ -> Just $ DELogChatResponse $ "chat error: " <> tshow e @@ -79,7 +79,7 @@ crDirectoryEvent_ = \case CEvtJoinedGroupMember {groupInfo, member = m} | pending m -> Just $ DEPendingMember groupInfo m | otherwise -> Nothing - CEvtNewChatItems {chatItems = AChatItem _ _ (GroupChat g) ci : _} -> case ci of + CEvtNewChatItems {chatItems = AChatItem _ _ (GroupChat g _scopeInfo) ci : _} -> case ci of ChatItem {chatDir = CIGroupRcv m, content = CIRcvMsgContent (MCText t)} | pending m -> Just $ DEPendingMemberMsg g m (chatItemId' ci) t _ -> Nothing CEvtMemberRole {groupInfo, member, toRole} @@ -124,13 +124,13 @@ data DirectoryCmdTag (r :: DirectoryRole) where DCDeleteGroup_ :: DirectoryCmdTag 'DRUser DCMemberRole_ :: DirectoryCmdTag 'DRUser DCGroupFilter_ :: DirectoryCmdTag 'DRUser + DCShowUpgradeGroupLink_ :: DirectoryCmdTag 'DRUser DCApproveGroup_ :: DirectoryCmdTag 'DRAdmin DCRejectGroup_ :: DirectoryCmdTag 'DRAdmin DCSuspendGroup_ :: DirectoryCmdTag 'DRAdmin DCResumeGroup_ :: DirectoryCmdTag 'DRAdmin DCListLastGroups_ :: DirectoryCmdTag 'DRAdmin DCListPendingGroups_ :: DirectoryCmdTag 'DRAdmin - DCShowGroupLink_ :: DirectoryCmdTag 'DRAdmin DCSendToGroupOwner_ :: DirectoryCmdTag 'DRAdmin DCInviteOwnerToGroup_ :: DirectoryCmdTag 'DRAdmin -- DCAddBlockedWord_ :: DirectoryCmdTag 'DRAdmin @@ -156,13 +156,13 @@ data DirectoryCmd (r :: DirectoryRole) where DCDeleteGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser DCMemberRole :: UserGroupRegId -> Maybe GroupName -> Maybe GroupMemberRole -> DirectoryCmd 'DRUser DCGroupFilter :: UserGroupRegId -> Maybe GroupName -> Maybe DirectoryMemberAcceptance -> DirectoryCmd 'DRUser + DCShowUpgradeGroupLink :: GroupId -> Maybe GroupName -> DirectoryCmd 'DRUser DCApproveGroup :: {groupId :: GroupId, displayName :: GroupName, groupApprovalId :: GroupApprovalId} -> DirectoryCmd 'DRAdmin DCRejectGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin DCSuspendGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin DCResumeGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin DCListLastGroups :: Int -> DirectoryCmd 'DRAdmin DCListPendingGroups :: Int -> DirectoryCmd 'DRAdmin - DCShowGroupLink :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin DCSendToGroupOwner :: GroupId -> GroupName -> Text -> DirectoryCmd 'DRAdmin DCInviteOwnerToGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin -- DCAddBlockedWord :: Text -> DirectoryCmd 'DRAdmin @@ -200,13 +200,13 @@ directoryCmdP = "delete" -> u DCDeleteGroup_ "role" -> u DCMemberRole_ "filter" -> u DCGroupFilter_ + "link" -> u DCShowUpgradeGroupLink_ "approve" -> au DCApproveGroup_ "reject" -> au DCRejectGroup_ "suspend" -> au DCSuspendGroup_ "resume" -> au DCResumeGroup_ "last" -> au DCListLastGroups_ "pending" -> au DCListPendingGroups_ - "link" -> au DCShowGroupLink_ "owner" -> au DCSendToGroupOwner_ "invite" -> au DCInviteOwnerToGroup_ -- "block_word" -> au DCAddBlockedWord_ @@ -266,6 +266,7 @@ directoryCmdP = "=all" $> PCAll <|> ("=noimage" <|> "=no_image" <|> "=no-image") $> PCNoImage <|> pure PCAll + DCShowUpgradeGroupLink_ -> gc_ DCShowUpgradeGroupLink DCApproveGroup_ -> do (groupId, displayName) <- gc (,) groupApprovalId <- A.space *> A.decimal @@ -275,7 +276,6 @@ directoryCmdP = DCResumeGroup_ -> gc DCResumeGroup DCListLastGroups_ -> DCListLastGroups <$> (A.space *> A.decimal <|> pure 10) DCListPendingGroups_ -> DCListPendingGroups <$> (A.space *> A.decimal <|> pure 10) - DCShowGroupLink_ -> gc DCShowGroupLink DCSendToGroupOwner_ -> do (groupId, displayName) <- gc (,) msg <- A.space *> A.takeText @@ -299,17 +299,17 @@ directoryCmdTag = \case DCRecentGroups -> "new" DCSubmitGroup _ -> "submit" DCConfirmDuplicateGroup {} -> "confirm" - DCListUserGroups -> "list" + DCListUserGroups -> "list" DCDeleteGroup {} -> "delete" DCApproveGroup {} -> "approve" DCMemberRole {} -> "role" DCGroupFilter {} -> "filter" + DCShowUpgradeGroupLink {} -> "link" DCRejectGroup {} -> "reject" DCSuspendGroup {} -> "suspend" DCResumeGroup {} -> "resume" DCListLastGroups _ -> "last" DCListPendingGroups _ -> "pending" - DCShowGroupLink {} -> "link" DCSendToGroupOwner {} -> "owner" DCInviteOwnerToGroup {} -> "invite" -- DCAddBlockedWord _ -> "block_word" diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index 2c26905e79..7ad3512fe9 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -16,7 +16,7 @@ import qualified Data.Text as T import Options.Applicative import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (updateStr, versionNumber, versionString) -import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, coreChatOptsP) +import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, CreateBotOpts (..), coreChatOptsP) data DirectoryOpts = DirectoryOpts { coreOptions :: CoreChatOpts, @@ -116,10 +116,10 @@ directoryOpts appDir defaultDbName = do strOption ( long "service-name" <> metavar "SERVICE_NAME" - <> help "The display name of the directory service bot, without *'s and spaces (SimpleX-Directory)" - <> value "SimpleX-Directory" + <> help "The display name of the directory service bot, without *'s and spaces (SimpleX Directory)" + <> value "SimpleX Directory" ) - runCLI <- + runCLI <- switch ( long "run-cli" <> help "Run directory service as CLI" @@ -155,7 +155,7 @@ getDirectoryOpts appDir defaultDbName = versionAndUpdate = versionStr <> "\n" <> updateStr mkChatOpts :: DirectoryOpts -> ChatOpts -mkChatOpts DirectoryOpts {coreOptions} = +mkChatOpts DirectoryOpts {coreOptions, serviceName} = ChatOpts { coreOptions, chatCmd = "", @@ -169,5 +169,6 @@ mkChatOpts DirectoryOpts {coreOptions} = autoAcceptFileSize = 0, muteNotifications = True, markRead = False, + createBot = Just CreateBotOpts {botDisplayName = serviceName, allowFiles = False}, maintenance = False } diff --git a/apps/simplex-directory-service/src/Directory/Search.hs b/apps/simplex-directory-service/src/Directory/Search.hs index 822182b053..5b0d650444 100644 --- a/apps/simplex-directory-service/src/Directory/Search.hs +++ b/apps/simplex-directory-service/src/Directory/Search.hs @@ -19,14 +19,14 @@ data SearchRequest = SearchRequest data SearchType = STAll | STRecent | STSearch Text -takeTop :: Int -> [(GroupInfo, GroupSummary)] -> [(GroupInfo, GroupSummary)] -takeTop n = take n . sortOn (Down . currentMembers . snd) +takeTop :: Int -> [GroupInfoSummary] -> [GroupInfoSummary] +takeTop n = take n . sortOn (\(GIS _ GroupSummary {currentMembers}) -> Down currentMembers) -takeRecent :: Int -> [(GroupInfo, GroupSummary)] -> [(GroupInfo, GroupSummary)] -takeRecent n = take n . sortOn (Down . (\GroupInfo {createdAt} -> createdAt) . fst) +takeRecent :: Int -> [GroupInfoSummary] -> [GroupInfoSummary] +takeRecent n = take n . sortOn (\(GIS GroupInfo {createdAt} _) -> Down createdAt) -groupIds :: [(GroupInfo, GroupSummary)] -> Set GroupId -groupIds = S.fromList . map (\(GroupInfo {groupId}, _) -> groupId) +groupIds :: [GroupInfoSummary] -> Set GroupId +groupIds = S.fromList . map (\(GIS GroupInfo {groupId} _) -> groupId) -filterNotSent :: Set GroupId -> [(GroupInfo, GroupSummary)] -> [(GroupInfo, GroupSummary)] -filterNotSent sentGroups = filter (\(GroupInfo {groupId}, _) -> groupId `S.notMember` sentGroups) +filterNotSent :: Set GroupId -> [GroupInfoSummary] -> [GroupInfoSummary] +filterNotSent sentGroups = filter (\(GIS GroupInfo {groupId} _) -> groupId `S.notMember` sentGroups) diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 4517ee9c5b..f95b04dee1 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -8,6 +8,7 @@ {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module Directory.Service ( welcomeGetOpts, @@ -26,11 +27,10 @@ import Control.Logger.Simple import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class -import Data.Int (Int64) import Data.List (find, intercalate) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Map.Strict as M -import Data.Maybe (fromMaybe, isJust, maybeToList) +import Data.Maybe (fromMaybe, isJust, isNothing, maybeToList) import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) @@ -52,6 +52,7 @@ import Simplex.Chat.Markdown (FormattedText (..), Format (..), parseMaybeMarkdow import Simplex.Chat.Messages import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgContent (..)) +import Simplex.Chat.Store (GroupLink (..)) import Simplex.Chat.Store.Direct (getContact) import Simplex.Chat.Store.Groups (getGroupInfo, getGroupLink, getGroupSummary, setGroupCustomData) import Simplex.Chat.Store.Profiles (GroupLinkInfo (..), getGroupLinkInfo) @@ -59,6 +60,7 @@ import Simplex.Chat.Store.Shared (StoreError (..)) import Simplex.Chat.Terminal (terminalChatConfig) import Simplex.Chat.Terminal.Main (simplexChatCLI') import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatError, serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnectionLink (..), CreatedConnLink (..)) @@ -70,9 +72,15 @@ import Simplex.Messaging.TMap (TMap) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Util (safeDecodeUtf8, tshow, ($>>=), (<$$>)) import System.Directory (getAppUserDataDirectory) +import System.Exit (exitFailure) import System.Process (readProcess) -data GroupProfileUpdate = GPNoServiceLink | GPServiceLinkAdded | GPServiceLinkRemoved | GPHasServiceLink | GPServiceLinkError +data GroupProfileUpdate + = GPNoServiceLink + | GPServiceLinkAdded {linkNow :: Text} + | GPServiceLinkRemoved + | GPHasServiceLink {linkBefore :: Text, linkNow :: Text} + | GPServiceLinkError data DuplicateGroup = DGUnique -- display name or full name is unique @@ -139,7 +147,7 @@ directoryServiceCLI st opts = do env <- newServiceState opts eventQ <- newTQueueIO let eventHook cc resp = atomically $ resp <$ writeTQueue eventQ (cc, resp) - chatHooks = defaultChatHooks {eventHook = Just eventHook, acceptMember = Just $ acceptMemberHook opts env} + chatHooks = defaultChatHooks {postStartHook = Just postStartHook, eventHook = Just eventHook, acceptMember = Just $ acceptMemberHook opts env} race_ (simplexChatCLI' terminalChatConfig {chatHooks} (mkChatOpts opts) Nothing) (processEvents eventQ env) @@ -148,6 +156,34 @@ directoryServiceCLI st opts = do (cc, resp) <- atomically $ readTQueue eventQ u_ <- readTVarIO (currentUser cc) forM_ u_ $ \user -> directoryServiceEvent st opts env user cc resp + postStartHook cc = + readTVarIO (currentUser cc) >>= \case + Nothing -> putStrLn "No current user" >> exitFailure + Just User {userId, profile = p@LocalProfile {preferences}} -> do + let cmds = fromMaybe [] $ preferences >>= commands_ + unless (cmds == directoryCommands) $ do + let prefs = (fromMaybe emptyChatPrefs preferences) {files = Just FilesPreference {allow = FANo}, commands = Just directoryCommands} :: Preferences + p' = (fromLocalProfile p) {displayName = serviceName opts, peerType = Just CPTBot, preferences = Just prefs} :: Profile + liftIO $ sendChatCmd cc (APIUpdateProfile userId p') >>= \case + Right CRUserProfileUpdated {} -> putStrLn "Updated directory commands" + Right r -> putStrLn ("Error: unexpected response " <> show r) >> exitFailure + Left e -> putStrLn ("Error: " <> show e) >> exitFailure + +directoryCommands :: [ChatBotCommand] +directoryCommands = + [ CBCCommand "new" "New groups" Nothing, + CBCCommand "help" "How to submit your group" Nothing, + CBCCommand "list" "Your own groups" Nothing, + CBCMenu + "Group settings" + [ CBCCommand "role" "View new member role" idParam, + CBCCommand "filter" "Anti-spam filter" idParam, + CBCCommand "link" "View and upgrade group link" idParam, + CBCCommand "delete" "Remove a group from directory" (Just ":''") + ] + ] + where + idParam = Just "" directoryService :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> ChatController -> IO () directoryService st opts@DirectoryOpts {testing} env user cc = do @@ -167,7 +203,7 @@ acceptMemberHook when (useMemberFilter img $ rejectNames a) checkName pure $ if - | useMemberFilter img (passCaptcha a) -> (GAPending, GRMember) + | useMemberFilter img (passCaptcha a) -> (GAPendingApproval, GRMember) | useMemberFilter img (makeObserver a) -> (GAAccepted, GRObserver) | otherwise -> (GAAccepted, memberRole) where @@ -189,7 +225,7 @@ useMemberFilter img_ = \case readBlockedWordsConfig :: DirectoryOpts -> IO BlockedWordsConfig readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, nameSpellingFile, blockedExtensionRules, testing} = do - extensionRules <- maybe (pure []) (fmap read . readFile) blockedExtensionRules + extensionRules <- maybe (pure []) (fmap read . readFile) blockedExtensionRules spelling <- maybe (pure M.empty) (fmap (M.fromList . read) . readFile) nameSpellingFile blockedFragments <- S.fromList <$> maybe (pure []) (fmap T.lines . T.readFile) blockedFragmentsFile bws <- maybe (pure []) (fmap lines . readFile) blockedWordsFile @@ -223,6 +259,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName SDRSuperUser -> deSuperUserCommand ct ciId cmd DELogChatResponse r -> logInfo r where + groupLinkText (CCLink cReq sLnk_) = maybe (strEncodeTxt $ simplexChatContact cReq) strEncodeTxt sLnk_ withAdminUsers action = void . forkIO $ do forM_ superUsers $ \KnownContact {contactId} -> action contactId forM_ adminUsers $ \KnownContact {contactId} -> action contactId @@ -234,19 +271,24 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName getGroupReg st groupId >>= \case Just gr -> action gr Nothing -> logError $ "Error: " <> err <> ", group: " <> localDisplayName <> ", can't find group registration ID " <> tshow groupId - groupInfoText GroupProfile {displayName = n, fullName = fn, description = d} = - n <> (if n == fn || T.null fn then "" else " (" <> fn <> ")") <> maybe "" ("\nWelcome message:\n" <>) d + groupInfoText p@GroupProfile {description = d} = groupNameDescr p <> maybe "" ("\nWelcome message:\n" <>) d + groupNameDescr GroupProfile {displayName = n, fullName = fn, shortDescr = sd_} = + n <> maybe "" (\d' -> " (" <> d' <> ")") descr + where + descr + | n == fn || T.null fn = if sd_ == Just "" then Nothing else sd_ + | otherwise = Just fn userGroupReference gr GroupInfo {groupProfile = GroupProfile {displayName}} = userGroupReference' gr displayName userGroupReference' GroupReg {userGroupRegId} displayName = groupReference' userGroupRegId displayName groupReference GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = groupReference' groupId displayName groupReference' groupId displayName = "ID " <> tshow groupId <> " (" <> displayName <> ")" - groupAlreadyListed GroupInfo {groupProfile = GroupProfile {displayName, fullName}} = - "The group " <> displayName <> " (" <> fullName <> ") is already listed in the directory, please choose another name." + groupAlreadyListed GroupInfo {groupProfile = p} = + "The group " <> groupNameDescr p <> " is already listed in the directory, please choose another name." - getGroups :: Text -> IO (Maybe [(GroupInfo, GroupSummary)]) + getGroups :: Text -> IO (Maybe [GroupInfoSummary]) getGroups = getGroups_ . Just - getGroups_ :: Maybe Text -> IO (Maybe [(GroupInfo, GroupSummary)]) + getGroups_ :: Maybe Text -> IO (Maybe [GroupInfoSummary]) getGroups_ search_ = sendChatCmd cc (APIListGroups userId Nothing $ T.unpack <$> search_) >>= \case Right CRGroupsList {groups} -> pure $ Just groups @@ -256,7 +298,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName getDuplicateGroup GroupInfo {groupId, groupProfile = GroupProfile {displayName, fullName}} = getGroups fullName >>= mapM duplicateGroup where - sameGroupNotRemoved (g@GroupInfo {groupId = gId, groupProfile = GroupProfile {displayName = n, fullName = fn}}, _) = + sameGroupNotRemoved (GIS g@GroupInfo {groupId = gId, groupProfile = GroupProfile {displayName = n, fullName = fn}} _) = gId /= groupId && n == displayName && fn == fullName && not (memberRemoved $ membership g) duplicateGroup [] = pure DGUnique duplicateGroup groups = do @@ -265,13 +307,13 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName then pure DGUnique else do (lgs, rgs) <- atomically $ (,) <$> readTVar (listedGroups st) <*> readTVar (reservedGroups st) - let reserved = any (\(GroupInfo {groupId = gId}, _) -> gId `S.member` lgs || gId `S.member` rgs) gs + let reserved = any (\(GIS GroupInfo {groupId = gId} _) -> gId `S.member` lgs || gId `S.member` rgs) gs if reserved then pure DGReserved else do removed <- foldM (\r -> fmap (r &&) . isGroupRemoved) True gs pure $ if removed then DGUnique else DGRegistered - isGroupRemoved (GroupInfo {groupId = gId}, _) = + isGroupRemoved (GIS GroupInfo {groupId = gId} _) = getGroupReg st gId >>= \case Just GroupReg {groupRegStatus} -> groupRemoved <$> readTVarIO groupRegStatus Nothing -> pure True @@ -288,14 +330,14 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName deContactConnected ct = when (contactDirect ct) $ do logInfo $ (viewContactName ct) <> " connected" sendMessage cc ct $ - ("Welcome to " <> serviceName <> " service!\n") - <> "Send a search string to find groups or */help* to learn how to add groups to directory.\n\n\ - \For example, send _privacy_ to find groups about privacy.\n\ - \Or send */all* or */new* to list groups.\n\n\ - \Content and privacy policy: https://simplex.chat/docs/directory.html" + ("Welcome to " <> serviceName <> "!\n\n") + <> "🔍 Send search string to find groups - try _security_.\n\ + \/help - how to submit your group.\n\ + \/new - recent groups.\n\n\ + \[Directory rules](https://simplex.chat/docs/directory.html)." deGroupInvitation :: Contact -> GroupInfo -> GroupMemberRole -> GroupMemberRole -> IO () - deGroupInvitation ct g@GroupInfo {groupProfile = GroupProfile {displayName, fullName}} fromMemberRole memberRole = do + deGroupInvitation ct g@GroupInfo {groupProfile = p@GroupProfile {displayName}} fromMemberRole memberRole = do logInfo $ "invited to group " <> viewGroupName g <> " by " <> viewContactName ct case badRolesMsg $ groupRolesStatus fromMemberRole memberRole of Just msg -> sendMessage cc ct msg @@ -308,7 +350,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName where askConfirmation = do ugrId <- addGroupReg st ct g GRSPendingConfirmation - sendMessage cc ct $ "The group " <> displayName <> " (" <> fullName <> ") is already submitted to the directory.\nTo confirm the registration, please send:" + sendMessage cc ct $ "The group " <> groupNameDescr p <> " is already submitted to the directory.\nTo confirm the registration, please send:" sendMessage cc ct $ "/confirm " <> tshow ugrId <> ":" <> viewName displayName badRolesMsg :: GroupRolesStatus -> Maybe Text @@ -348,15 +390,15 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName setGroupRegOwner st gr owner let GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = g notifyOwner gr $ "Joined the group " <> displayName <> ", creating the link…" - sendChatCmd cc (APICreateGroupLink groupId GRMember False) >>= \case - Right CRGroupLinkCreated {connLinkContact = CCLink gLink _} -> do + sendChatCmd cc (APICreateGroupLink groupId GRMember) >>= \case + Right CRGroupLinkCreated {groupLink = GroupLink {connLinkContact = gLink}} -> do setGroupStatus st gr GRSPendingUpdate notifyOwner gr "Created the public link to join the group via this directory service that is always online.\n\n\ \Please add it to the group welcome message.\n\ \For example, add:" - notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact gLink) + notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> groupLinkText gLink Left (ChatError e) -> case e of CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin." CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group." @@ -381,28 +423,28 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName groupProfileUpdate >>= \case GPNoServiceLink -> notifyOwner gr $ "The profile updated for " <> userGroupRef <> byMember <> ", but the group link is not added to the welcome message." - GPServiceLinkAdded -> groupLinkAdded gr byMember + GPServiceLinkAdded _ -> groupLinkAdded gr byMember GPServiceLinkRemoved -> notifyOwner gr $ "The group link of " <> userGroupRef <> " is removed from the welcome message" <> byMember <> ", please add it." - GPHasServiceLink -> groupLinkAdded gr byMember + GPHasServiceLink {} -> groupLinkAdded gr byMember GPServiceLinkError -> do notifyOwner gr $ ("Error: " <> serviceName <> " has no group link for " <> userGroupRef) <> " after profile was updated" <> byMember <> ". Please report the error to the developers." logError $ "Error: no group link for " <> userGroupRef - GRSPendingApproval n -> processProfileChange gr byMember $ n + 1 - GRSActive -> processProfileChange gr byMember 1 - GRSSuspended -> processProfileChange gr byMember 1 - GRSSuspendedBadRoles -> processProfileChange gr byMember 1 + GRSPendingApproval n -> processProfileChange gr byMember False $ n + 1 + GRSActive -> processProfileChange gr byMember True 1 + GRSSuspended -> processProfileChange gr byMember False 1 + GRSSuspendedBadRoles -> processProfileChange gr byMember False 1 GRSRemoved -> pure () where GroupInfo {groupId, groupProfile = p} = fromGroup GroupInfo {groupProfile = p'} = toGroup sameProfile - GroupProfile {displayName = n, fullName = fn, image = i, description = d} - GroupProfile {displayName = n', fullName = fn', image = i', description = d'} = - n == n' && fn == fn' && i == i' && d == d' + GroupProfile {displayName = n, fullName = fn, shortDescr = sd, image = i, description = d} + GroupProfile {displayName = n', fullName = fn', shortDescr = sd', image = i', description = d'} = + n == n' && fn == fn' && i == i' && sd == sd' && (T.words <$> d) == (T.words <$> d') groupLinkAdded gr byMember = do getDuplicateGroup toGroup >>= \case Nothing -> notifyOwner gr "Error: getDuplicateGroup. Please notify the developers." @@ -414,53 +456,65 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName ("Thank you! The group link for " <> userGroupReference gr toGroup <> " is added to the welcome message" <> byMember) <> ".\nYou will be notified once the group is added to the directory - it may take up to 48 hours." checkRolesSendToApprove gr gaId - processProfileChange gr byMember n' = do - setGroupStatus st gr GRSPendingUpdate + processProfileChange gr byMember isActive n' = do let userGroupRef = userGroupReference gr toGroup groupRef = groupReference toGroup groupProfileUpdate >>= \case GPNoServiceLink -> do + setGroupStatus st gr GRSPendingUpdate notifyOwner gr $ ("The group profile is updated for " <> userGroupRef <> byMember <> ", but no link is added to the welcome message.\n\n") <> "The group will remain hidden from the directory until the group link is added and the group is re-approved." GPServiceLinkRemoved -> do + setGroupStatus st gr GRSPendingUpdate notifyOwner gr $ ("The group link for " <> userGroupRef <> " is removed from the welcome message" <> byMember) <> ".\n\nThe group is hidden from the directory until the group link is added and the group is re-approved." notifyAdminUsers $ "The group link is removed from " <> groupRef <> ", de-listed." - GPServiceLinkAdded -> do + GPServiceLinkAdded _ -> do setGroupStatus st gr $ GRSPendingApproval n' notifyOwner gr $ ("The group link is added to " <> userGroupRef <> byMember) <> "!\nIt is hidden from the directory until approved." notifyAdminUsers $ "The group link is added to " <> groupRef <> byMember <> "." checkRolesSendToApprove gr n' - GPHasServiceLink -> do - setGroupStatus st gr $ GRSPendingApproval n' - notifyOwner gr $ - ("The group " <> userGroupRef <> " is updated" <> byMember) - <> "!\nIt is hidden from the directory until approved." - notifyAdminUsers $ "The group " <> groupRef <> " is updated" <> byMember <> "." - checkRolesSendToApprove gr n' + GPHasServiceLink {linkBefore, linkNow} + | isActive && onlyLinkChanged p p' -> do + notifyOwner gr $ + ("The group " <> userGroupRef <> " is updated" <> byMember) + <> "!\nThe group is listed in directory." + notifyAdminUsers $ "The group " <> groupRef <> " is updated" <> byMember <> " - only link or whitespace changes.\nThe group remained listed in directory." + | otherwise -> do + setGroupStatus st gr $ GRSPendingApproval n' + notifyOwner gr $ + ("The group " <> userGroupRef <> " is updated" <> byMember) + <> "!\nIt is hidden from the directory until approved." + notifyAdminUsers $ "The group " <> groupRef <> " is updated" <> byMember <> "." + checkRolesSendToApprove gr n' + where + onlyLinkChanged + GroupProfile {displayName = dn, fullName = fn, shortDescr = sd, image = i, description = d} + GroupProfile {displayName = dn', fullName = fn', shortDescr = sd', image = i', description = d'} = + dn == dn' && fn == fn' && i == i' && sd == sd' && (T.words . T.replace linkBefore "" <$> d) == (T.words . T.replace linkNow "" <$> d') GPServiceLinkError -> logError $ "Error: no group link for " <> groupRef <> " pending approval." groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) where profileUpdate = \case - Right CRGroupLink {connLinkContact = CCLink cr sl_} -> - let hadLinkBefore = profileHasGroupLink fromGroup - hasLinkNow = profileHasGroupLink toGroup - profileHasGroupLink GroupInfo {groupProfile = gp} = - maybe False (any ftHasLink) $ parseMaybeMarkdownList =<< description gp + Right CRGroupLink {groupLink = GroupLink {connLinkContact = CCLink cr sl_}} -> + let linkBefore_ = profileGroupLinkText fromGroup + linkNow_ = profileGroupLinkText toGroup + profileGroupLinkText GroupInfo {groupProfile = gp} = + maybe Nothing (fmap (\(FormattedText _ t) -> t) . find ftHasLink) $ parseMaybeMarkdownList =<< description gp ftHasLink = \case FormattedText (Just SimplexLink {simplexUri = ACL SCMContact cLink}) _ -> case cLink of CLFull cr' -> sameConnReqContact cr' cr CLShort sl' -> maybe False (sameShortLinkContact sl') sl_ _ -> False - in if - | hadLinkBefore && hasLinkNow -> GPHasServiceLink - | hadLinkBefore -> GPServiceLinkRemoved - | hasLinkNow -> GPServiceLinkAdded - | otherwise -> GPNoServiceLink + in case (linkBefore_, linkNow_) of + (Just linkBefore, Just linkNow) -> GPHasServiceLink linkBefore linkNow + (Just _, Nothing) -> GPServiceLinkRemoved + (Nothing, Just linkNow) -> GPServiceLinkAdded linkNow + (Nothing, Nothing) -> GPNoServiceLink _ -> GPServiceLinkError checkRolesSendToApprove gr gaId = do (badRolesMsg <$$> getGroupRolesStatus toGroup gr) >>= \case @@ -489,12 +543,12 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Nothing -> pure textMsg Just script -> content <$> readProcess script [s] "" where - textMsg = MCText $ T.pack s + textMsg = MCText $ T.pack s content r = case T.lines $ T.pack r of [] -> textMsg "" : _ -> textMsg img : _ -> MCImage "" $ ImageData img - sendCaptcha mc = sendComposedMessages_ cc (SRGroup groupId $ Just gmId) [(quotedId, MCText noticeText), (Nothing, mc)] + sendCaptcha mc = sendComposedMessages_ cc (SRGroup groupId $ Just $ GCSMemberSupport (Just gmId)) [(quotedId, MCText noticeText), (Nothing, mc)] gmId = groupMemberId' m approvePendingMember :: DirectoryMemberAcceptance -> GroupInfo -> GroupMember -> IO () @@ -503,9 +557,11 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let role = if useMemberFilter image (makeObserver a) then GRObserver else maybe GRMember (\GroupLinkInfo {memberRole} -> memberRole) gli_ gmId = groupMemberId' m sendChatCmd cc (APIAcceptMember groupId gmId role) >>= \case - Right CRJoinedGroupMember {} -> do + Right CRMemberAccepted {member} -> do atomically $ TM.delete gmId $ pendingCaptchas env - logInfo $ "Member " <> viewName displayName <> " accepted, group " <> tshow groupId <> ":" <> viewGroupName g + if memberStatus member == GSMemPendingReview + then logInfo $ "Member " <> viewName displayName <> " accepted and pending review, group " <> tshow groupId <> ":" <> viewGroupName g + else logInfo $ "Member " <> viewName displayName <> " accepted, group " <> tshow groupId <> ":" <> viewGroupName g r -> logError $ "unexpected accept member response: " <> tshow r dePendingMemberMsg :: GroupInfo -> GroupMember -> ChatItemId -> Text -> IO () @@ -516,7 +572,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Just PendingCaptcha {captchaText, sentAt, attempts} | ts `diffUTCTime` sentAt > captchaTTL -> sendMemberCaptcha g m (Just ciId) captchaExpired $ attempts - 1 | matchCaptchaStr captchaText msgText -> do - sendComposedMessages_ cc (SRGroup groupId $ Just $ groupMemberId' m) [(Just ciId, MCText $ "Correct, you joined the group " <> n)] + sendComposedMessages_ cc (SRGroup groupId $ Just $ GCSMemberSupport (Just $ groupMemberId' m)) [(Just ciId, MCText $ "Correct, you joined the group " <> n)] approvePendingMember a g m | attempts >= maxCaptchaAttempts -> rejectPendingMember tooManyAttempts | otherwise -> sendMemberCaptcha g m (Just ciId) (wrongCaptcha attempts) attempts @@ -526,7 +582,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName a = groupMemberAcceptance g rejectPendingMember rjctNotice = do let gmId = groupMemberId' m - sendComposedMessages cc (SRGroup groupId $ Just gmId) [MCText rjctNotice] + sendComposedMessages cc (SRGroup groupId $ Just $ GCSMemberSupport (Just gmId)) [MCText rjctNotice] sendChatCmd cc (APIRemoveMembers groupId [gmId] False) >>= \case Right (CRUserDeletedMembers _ _ (_ : _) _) -> do atomically $ TM.delete gmId $ pendingCaptchas env @@ -649,24 +705,22 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName deUserCommand ct ciId = \case DCHelp DHSRegistration -> sendMessage cc ct $ - "You must be the owner to add the group to the directory:\n\ - \1. Invite " + "You must be the group owner to add it to the directory:\n\n\ + \1️⃣ *Invite* " <> serviceName - <> " bot to your group as *admin* (you can send `/list` to see all groups you submitted).\n\ - \2. " - <> serviceName - <> " bot will create a public group link for the new members to join even when you are offline.\n\ - \3. You will then need to add this link to the group welcome message.\n\ - \4. Once the link is added, service admins will approve the group (it can take up to 48 hours), and everybody will be able to find it in directory.\n\n\ - \Start from inviting the bot to your group as admin - it will guide you through the process." + <> " bot to your group as *admin* - it will create a link for new members to join.\n\ + \2️⃣ *Add* this link to the group's welcome message.\n\ + \3️⃣ We *review* your group. Once *approved*, anybody can find it.\n\n\ + \_We usually approve within a day, except holidays_. [More details](https://simplex.chat/docs/directory.html#adding-groups-to-the-directory)." DCHelp DHSCommands -> sendMessage cc ct $ - "*/help commands* - receive this help message.\n\ - \*/help* - how to register your group to be added to directory.\n\ - \*/list* - list the groups you registered.\n\ - \*/delete :* - remove the group you submitted from directory, with _ID_ and _name_ as shown by */list* command.\n\ - \*/role * - view and set default member role for your group.\n\ - \*/filter * - view and set spam filter settings for group.\n\n\ + "/'help commands' - receive this help message.\n\ + \/help - how to register your group to be added to directory.\n\ + \/list - list the groups you registered.\n\ + \`/role ` - view and set default member role for your group.\n\ + \`/filter ` - view and set spam filter settings for group.\n\ + \`/link ` - view and upgrade group link.\n\ + \`/delete :` - remove the group you submitted from directory, with _ID_ and _name_ as shown by /list command.\n\n\ \To search for groups, send the search text." DCSearchGroup s -> withFoundListedGroups (Just s) $ sendSearchResults s DCSearchNext -> @@ -699,8 +753,9 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName DCListUserGroups -> getUserGroupRegs st (contactId' ct) >>= \grs -> do sendReply $ tshow (length grs) <> " registered group(s)" - void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {userGroupRegId} -> - sendGroupInfo ct gr userGroupRegId Nothing + void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {dbGroupId, userGroupRegId} -> + let useGroupId = if isAdmin then dbGroupId else userGroupRegId + in sendGroupInfo ct gr useGroupId Nothing DCDeleteGroup gId gName -> (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ \GroupInfo {groupProfile = GroupProfile {displayName}} gr -> do delGroupReg st gr @@ -710,12 +765,12 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g case mRole_ of Nothing -> - getGroupLinkRole cc user g >>= \case - Just (_, CCLink gLink _, mRole) -> do - let anotherRole = case mRole of GRObserver -> GRMember; _ -> GRObserver + getGroupLink' cc user g >>= \case + Just GroupLink {connLinkContact = gLink, acceptMemberRole} -> do + let anotherRole = case acceptMemberRole of GRObserver -> GRMember; _ -> GRObserver sendReply $ - initialRole n mRole - <> ("Send */role " <> tshow gId <> " " <> strEncodeTxt anotherRole <> "* to change it.\n\n") + initialRole n acceptMemberRole + <> ("Send /'role " <> tshow gId <> " " <> strEncodeTxt anotherRole <> "' to change it.\n\n") <> onlyViaLink gLink Nothing -> sendReply $ "Error: failed reading the initial member role for the group " <> n Just mRole -> do @@ -724,7 +779,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Nothing -> sendReply $ "Error: the initial member role for the group " <> n <> " was NOT upgated." where initialRole n mRole = "The initial member role for the group " <> n <> " is set to *" <> strEncodeTxt mRole <> "*\n" - onlyViaLink gLink = "*Please note*: it applies only to members joining via this link: " <> strEncodeTxt (simplexChatContact gLink) + onlyViaLink gLink = "*Please note*: it applies only to members joining via this link: " <> groupLinkText gLink DCGroupFilter gId gName_ acceptance_ -> (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g @@ -739,20 +794,67 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName where sendSettigns n a setTo = sendReply $ - T.unlines + T.unlines $ [ "Spam filter settings for group " <> n <> setTo <> ":", "- reject long/inappropriate names: " <> showCondition (rejectNames a), "- pass captcha to join: " <> showCondition (passCaptcha a), -- "- make observer: " <> showCondition (makeObserver a) <> (if isJust (makeObserver a) then "" else " (use default set with /role command)"), - "", + "" -- "Use */filter " <> tshow gId <> " * to change spam filter level: no (disable), basic, moderate, strong.", -- "Or use */filter " <> tshow gId <> " [name[=noimage]] [captcha[=noimage]] [observer[=noimage]]* for advanced filter configuration." - "Use */filter " <> tshow gId <> " [name] [captcha]* to enable and */filter " <> tshow gId <> " off* to disable filter." ] + <> ["/'filter " <> tshow gId <> " name' - enable name filter" | isNothing (rejectNames a)] + <> ["/'filter " <> tshow gId <> " captcha' - enable captcha challenge" | isNothing (passCaptcha a)] + <> ["/'filter " <> tshow gId <> " name captcha' - enable both" | isNothing (rejectNames a) || isNothing (passCaptcha a)] + <> ["/'filter " <> tshow gId <> " off' - disable filter" | isJust (rejectNames a) || isJust (passCaptcha a)] showCondition = \case Nothing -> "_disabled_" Just PCAll -> "_enabled_" Just PCNoImage -> "_enabled for profiles without image_" + DCShowUpgradeGroupLink gId gName_ -> + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \GroupInfo {groupId, localDisplayName = gName} _ -> do + let groupRef = groupReference' gId gName + withGroupLinkResult groupRef (sendChatCmd cc $ APIGetGroupLink groupId) $ + \GroupLink {connLinkContact = gLink@(CCLink _ sLnk_), acceptMemberRole, shortLinkDataSet, shortLinkLargeDataSet = BoolDef slLargeDataSet} -> do + let shouldBeUpgraded = isNothing sLnk_ || not shortLinkDataSet || not slLargeDataSet + sendReply $ T.unlines $ + [ "The link to join the group " <> groupRef <> ":", + groupLinkText gLink, + "New member role: " <> strEncodeTxt acceptMemberRole + ] + <> ["The link is being upgraded..." | shouldBeUpgraded] + when shouldBeUpgraded $ do + let send = sendComposedMessage cc ct Nothing . MCText . T.unlines + withGroupLinkResult groupRef (sendChatCmd cc $ APIAddGroupShortLink groupId) $ + \GroupLink {connLinkContact = CCLink _ sLnk_'} -> case (sLnk_, sLnk_') of + (Just _, Just _) -> + send ["The group link is upgraded for: " <> groupRef, "No changes to group needed."] + (Nothing, Just sLnk) -> + sendComposedMessages cc (SRDirect $ contactId' ct) + [ MCText $ T.unlines + [ "Please replace the old link in welcome message of your group " <> groupRef, + "If this is the only change, the group will remain listed in directory without re-approval.", + "", + "The new link:" + ], + MCText $ strEncodeTxt sLnk + ] + (_, Nothing) -> + send ["The short link is not created for " <> groupRef, "Please report it to the developers."] + where + withGroupLinkResult groupRef a cb = + a >>= \case + Right CRGroupLink {groupLink} -> cb groupLink + Left (ChatErrorStore (SEGroupLinkNotFound _)) -> + sendReply $ "The group " <> groupRef <> " has no public link." + Right r -> do + ts <- getCurrentTime + tz <- getCurrentTimeZone + let resp = T.pack $ serializeChatResponse (Nothing, Just user) (config cc) ts tz Nothing r + sendReply $ "Unexpected error:\n" <> resp + Left e -> do + let resp = T.pack $ serializeChatError True (config cc) e + sendReply $ "Unexpected error:\n" <> resp DCUnknownCommand -> sendReply "Unknown command" DCCommandError tag -> sendReply $ "Command error: " <> tshow tag where @@ -812,12 +914,12 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName where msgs = replyMsg :| map foundGroup gs <> [moreMsg | moreGroups > 0] replyMsg = (Just ciId, MCText reply) - foundGroup (GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_}}, GroupSummary {currentMembers}) = + foundGroup (GIS GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_}} GroupSummary {currentMembers}) = let membersStr = "_" <> tshow currentMembers <> " members_" showId = if isAdmin then tshow groupId <> ". " else "" text = showId <> groupInfoText p <> "\n" <> membersStr in (Nothing, maybe (MCText text) (\image -> MCImage {text, image}) image_) - moreMsg = (Nothing, MCText $ "Send */next* or just *.* for " <> tshow moreGroups <> " more result(s).") + moreMsg = (Nothing, MCText $ "Send /next for " <> tshow moreGroups <> " more result(s).") deAdminCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRAdmin -> IO () deAdminCommand ct ciId cmd @@ -837,11 +939,11 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let approved = "The group " <> userGroupReference' gr n <> " is approved" notifyOwner gr $ (approved <> " and listed in directory - please moderate it!\n") - <> "Please note: if you change the group profile it will be hidden from directory until it is re-approved.\n\n" + <> "_Please note_: if you change the group profile it will be hidden from directory until it is re-approved.\n\n" <> "Supported commands:\n" - <> ("- */filter " <> tshow ugrId <> "* - to configure anti-spam filter.\n") - <> ("- */role " <> tshow ugrId <> "* - to set default member role.\n") - <> "- */help commands* - other commands." + <> ("/'filter " <> tshow ugrId <> "' - to configure anti-spam filter.\n") + <> ("/'role " <> tshow ugrId <> "' - to set default member role.\n") + <> ("/'link " <> tshow ugrId <> "' - to view/upgrade group link.") invited <- forM ownersGroup $ \og@KnownGroup {localDisplayName = ogName} -> do inviteToOwnersGroup og gr $ \case @@ -850,7 +952,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName pure $ "Invited " <> owner <> " to owners' group " <> viewName ogName Left err -> pure err sendReply $ "Group approved!" <> maybe "" ("\n" <>) invited - notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) <> fromMaybe "" invited + notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) <> maybe "" ("\n" <>) invited Just GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin Just GRSContactNotOwner -> replyNotApproved "user is not an owner." Just GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin @@ -887,26 +989,6 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName _ -> sendReply $ "The group " <> groupRef <> " is not suspended, can't be resumed." DCListLastGroups count -> listGroups count False DCListPendingGroups count -> listGroups count True - DCShowGroupLink groupId gName -> do - let groupRef = groupReference' groupId gName - withGroupAndReg sendReply groupId gName $ \_ _ -> - sendChatCmd cc (APIGetGroupLink groupId) >>= \case - Right CRGroupLink {connLinkContact = CCLink cReq _, memberRole} -> - sendReply $ T.unlines - [ "The link to join the group " <> groupRef <> ":", - strEncodeTxt $ simplexChatContact cReq, - "New member role: " <> strEncodeTxt memberRole - ] - Left (ChatErrorStore (SEGroupLinkNotFound _)) -> - sendReply $ "The group " <> groupRef <> " has no public link." - Right r -> do - ts <- getCurrentTime - tz <- getCurrentTimeZone - let resp = T.pack $ serializeChatResponse (Nothing, Just user) (config cc) ts tz Nothing r - sendReply $ "Unexpected error:\n" <> resp - Left e -> do - let resp = T.pack $ serializeChatError True (config cc) e - sendReply $ "Unexpected error:\n" <> resp DCSendToGroupOwner groupId gName msg -> do let groupRef = groupReference' groupId gName withGroupAndReg sendReply groupId gName $ \_ gr@GroupReg {dbContactId} -> do @@ -1012,7 +1094,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName getGroupAndSummary cc user dbGroupId >>= \case Just (GroupInfo {groupProfile = p@GroupProfile {image = image_}}, GroupSummary {currentMembers}) -> do let membersStr = "_" <> tshow currentMembers <> " members_" - text = T.unlines $ [tshow useGroupId <> ". " <> groupInfoText p] <> maybeToList ownerStr_ <> [membersStr, statusStr] + cmds = "/'role " <> tshow useGroupId <> "', /'filter " <> tshow useGroupId <> "'" + text = T.unlines $ [tshow useGroupId <> ". " <> groupInfoText p] <> maybeToList ownerStr_ <> [membersStr, statusStr, cmds] msg = maybe (MCText text) (\image -> MCImage {text, image}) image_ sendComposedMessage cc ct Nothing msg Nothing -> do @@ -1043,15 +1126,15 @@ vr :: ChatController -> VersionRangeChat vr ChatController {config = ChatConfig {chatVRange}} = chatVRange {-# INLINE vr #-} -getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, CreatedLinkContact, GroupMemberRole)) -getGroupLinkRole cc user gInfo = +getGroupLink' :: ChatController -> User -> GroupInfo -> IO (Maybe GroupLink) +getGroupLink' cc user gInfo = withDB "getGroupLink" cc $ \db -> getGroupLink db user gInfo -setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe ConnReqContact) +setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe CreatedLinkContact) setGroupLinkRole cc GroupInfo {groupId} mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole groupId mRole) where resp = \case - Right (CRGroupLink _ _ (CCLink gLink _) _) -> Just gLink + Right (CRGroupLink {groupLink = GroupLink {connLinkContact}}) -> Just connLinkContact _ -> Nothing unexpectedError :: Text -> Text diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index 628419ea1d..9498fedf95 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -242,10 +242,10 @@ getUserGroupReg st ctId ugrId = find (\r -> ctId == dbContactId r && ugrId == us getUserGroupRegs :: DirectoryStore -> ContactId -> IO [GroupReg] getUserGroupRegs st ctId = filter ((ctId ==) . dbContactId) <$> readTVarIO (groupRegs st) -filterListedGroups :: DirectoryStore -> [(GroupInfo, GroupSummary)] -> IO [(GroupInfo, GroupSummary)] +filterListedGroups :: DirectoryStore -> [GroupInfoSummary] -> IO [GroupInfoSummary] filterListedGroups st gs = do lgs <- readTVarIO $ listedGroups st - pure $ filter (\(GroupInfo {groupId}, _) -> groupId `S.member` lgs) gs + pure $ filter (\(GIS GroupInfo {groupId} _) -> groupId `S.member` lgs) gs listGroup :: DirectoryStore -> GroupId -> STM () listGroup st gId = do diff --git a/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md b/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md index 57c4f69981..c7087f1657 100644 --- a/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md +++ b/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md @@ -89,7 +89,7 @@ Matrix network does not provide connection privacy, as not only user identity ex **Server operator transparency** -Operator transparency means that network users know who operates the servers they use. +Operator transparency means that network users know who operates the servers they use. You may argue that when the operators are known, the servers data can be requested by the authorities. But such requests, in particular when multiple operators are used by all users, will follow a due legal process, and will not result in compromising the privacy of all users. @@ -139,8 +139,8 @@ Evgeny SimpleX Chat founder -[1] You can also to self-host your own SimpleX servers on [Flux decentralized cloud](https://home.runonflux.io/apps/marketplace?q=simplex). +[1]: You can also to self-host your own SimpleX servers on [Flux decentralized cloud](https://home.runonflux.io/apps/marketplace?q=simplex). -[2] The probability of connection being de-anonymized and the number of random server choices follow this equation: `(1 - s ^ 2) ^ n = 1 - p`, where `s` is the share of attacker-controlled servers in the network, `n` is the number of random choices of entry and exit nodes for the circuit, and `p` is the probability of both entry and exit nodes, and the connection privacy being compromised. Substituting `0.02` (2%) for `s`, `0.5` (50%) for `p`, and solving this equation for `n` we obtain that `1733` random circuits have 50% probability of privacy being compromised. +[2]: The probability of connection being de-anonymized and the number of random server choices follow this equation: `(1 - s ^ 2) ^ n = 1 - p`, where `s` is the share of attacker-controlled servers in the network, `n` is the number of random choices of entry and exit nodes for the circuit, and `p` is the probability of both entry and exit nodes, and the connection privacy being compromised. Substituting `0.02` (2%) for `s`, `0.5` (50%) for `p`, and solving this equation for `n` we obtain that `1733` random circuits have 50% probability of privacy being compromised. Also see [this presentation about Tor](https://ritter.vg/p/tor-v1.6.pdf), specifically the approximate calculations on page 76, and also [Tor project post](https://blog.torproject.org/announcing-vanguards-add-onion-services/) about the changes that made attack on hidden service anonymity harder, but still viable in case the it is used for a long time. diff --git a/blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.md b/blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.md new file mode 100644 index 0000000000..077d438e6b --- /dev/null +++ b/blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.md @@ -0,0 +1,192 @@ +--- +layout: layouts/article.html +title: "SimpleX network: new experience of connecting with people — available in SimpleX Chat v6.4-beta.4" +date: 2025-07-03 +preview: Now you can start talking to your contacts much faster, as soon as you scan the link. This technical post covers the technology that enabled this new user experience — short links and associated data of messaging queues. +image: images/20250703-connect1a.png +imageBottom: true +permalink: "/blog/20250703-simplex-network-protocol-extension-for-securely-connecting-people.html" +--- + +# SimpleX network: new experience of connecting with people — available in SimpleX Chat v6.4-beta.4 + +**Published:** Jul 3, 2025 + +The mission of communication network is connecting people [1]. The process of connecting in SimpleX network is really secure — it is protected from server MITM attacks. But before this beta version connecting to contacts had a really bad user experience. + +## What was the problem? + +How did it work before: + +1. Your contact created a large link (1-time invitation or contact address [2]) and shared with you via another messenger, email, social profile or website. Sharing the link "out-of-band" (i.e., not via the server) is necessary for security. But it was not working well in some cases: + - the link was incorrectly changed by some applications, e.g. Telegram, because of link complexity, preventing people from connecting. + - some people were worried that the link "looks like malware". + - the QR code for the link was large, and sometimes difficult to scan. + - the link did not fit the size limit in social media profiles. +2. Once you received the link, you used it in the app. But when using the link you could not see who you were connecting to. The only choice you had is to share your current profile or to use incognito profile. And as people didn't know which profile will be shared with them, many were choosing to share incognito profile, making recognizing contacts more complex — you don't know who is who unless you attach aliases to incognito contacts. +3. Once you tap Connect, all you see is the line in the list of chats that said "Connecting via link". The process of connection required your contact to be online, and in some cases to approve the request, so you may had contact in "connecting" state for a really long time. + +So it is not surprising that a large number of people failed connecting to friends — either they refused to engage because of large and scary looking links, or their application made the link unusable, or they abandoned the process at step 3, deciding that the app is not working correctly. + +## Why can't we just use usernames? + +Many people asked — why don't you just use usernames or a link shortener for some really short links, as other networks and apps do. + +The problem is that usernames or very short links make e2e encryption security of your chats dependent on the servers. Unless the link you share contains enough randomness in it and is cryptographically linked to the encryption keys, the servers can substitute the e2e encryption keys and read all your communication without you knowing it. We see this risk as unacceptable. + +Mitigation against this "man-in-the-middle" attack by the server [3] is offered by Signal and other apps via security code verification [4], when you compare the numeric code in your app with your contact's app, but: +- most people do not verify security codes, and even if they do, they do not re-verify them every time security code changes, so their security is dependent on the server not being compromised, which is not a great security, +- the servers can still compromise the initial messages, where profile names are exchanged, before you had the chance to verify the security codes. + +When we design communication protocols for SimpleX network we always aim to protect you from the attack via your network operators — this is what sets SimpleX network design apart from many other communication networks and platforms. + +Even though you choose the servers that you trust, and they are bound by privacy policy, and we follow the best security practices to protect servers from any 3rd party attacks, there is still a possibility that servers may be compromised by some attackers, and unless your communications are not protected from the servers, they are not protected from whoever can compromise the servers [5]. + +## What is the new way to make connections? + +Before diving into the details of technology, let's walk though the new process of connecting to people that is introduced in [v6.4-beta.4](https://github.com/simplex-chat/simplex-chat/releases/tag/v6.4.0-beta.4). + + + +1. As before, to connect you or your contact need to create a 1-time invitation or a contact address link. All the past problems of the long links are now solved: + - this link is correctly processed by all applications, as it has a simple structure, + - it is short and simple, e.g. the SimpleX Chat team address for support is: https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw + - the QR code is now much smaller, fits a standard business card, and is easy to scan from all devices, + - it fits in most social media profiles. + +While the link is short, it still contains 256 bits of key material with additional 192 bits of server-generated link ID for one-time invitation links, so the connection is as secure as before, and in case of one-time invitations it became more secure (see below). + + + +2. As before, you have to use this link in the app, either by pasting the link or scanning QR code. But now you instantly see the name of your contact or group you are connecting to, and from v6.4.1 that will be released this July you will also see a profile image (currently disabled for backward compatibility). + +3. Once you tap "Open new chat", the app will instantly open a conversation with your contact. As you now can see which profile is shared with you via the link, you can choose which of your profiles to use to connect. If your contact shared an incognito pseudonym, then you may also choose to connect incognito. But if your contact shared a real name, you may want to share your real name as well, making it easier for your contact to recognize you — the law of reciprocity in action! **In any case, your and your contact's profile names are inaccessible to messaging servers — they are e2e encrypted**. + +If you are connecting via a contact address you can also add a message to your request, making it more likely to be accepted when connecting to somebody you don't know well. And from v6.4.1 contact addresses can include a welcome message that you would see before connecting, right in the conversation. This way, you effectively become connected to your contact and start a secure conversation even before you tap "Connect" button. + +If you are connecting via a one-time invitation link, all you need to do is to tap "Connect", and then you can send messages straight after that, without waiting for your contact to be online — they will be securely received later. + +This new experience of connecting is very similar to commonly used messengers, but it protects your security. We hope that it will be much easier for the new users to connect to their friends. + +## What about security? + +We took a great care to design the protocol extension for the new experience of connecting in a way that not only preserves security at the same level as before, but also increases security of connecting via one-time invitation links. + +First, because all the keys are now included in encrypted link data on the server, and not in the link itself as before, we can include the keys for post-quantum (PQ) key exchange and make the first message sent via one-time link (your profile) encrypted with PQ e2e encryption. Previously, PQ encryption started only after the response from your contact. + +Second, whoever can observe the link is not able to determine which public keys are used in key exchange and what messaging queue address is used, and this data is removed from the server once the connection is established. Previously, the invitation link contained public keys and the actual queue address that could have been used for a long time, unless you rotated it. + +Third, if somebody retrieves the associated data of one-time invitation link they observed in transit, this link would become inaccessible for the intended recipient, so the recipient would know that the connection was potentially compromised, and would alert the contact that sent the link. + +## How does it work? + +In short, a new short link references a container with the encrypted data on the server that contains: +- the original full link that now include quantum-resistant keys that previously were not included because of their size, +- contact's or group's profile and conversation preferences, from v6.4.1 it will include profile images, +- also, it will include an optional contact's or group's welcome message from v6.4.1. + +Making user profile and welcome message included in the encrypted link data allows to start conversation as soon as you scan the link, as described in the previous section. + +### Design objectives and cryptographic primitives that achieve them + +This section is not a formal specification of the protocol, but an informal technical explanation of objectives we had for this design and how they were achieved. The technical details are available in [this RFC document](https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2025-03-16-smp-queues.md). + +1. **Encrypted link data cannot be accessed by the server**. + +It means that while the client apps should use the link to derive both the link ID for the server and decryption key for the associated link data, the server should not be able to derive the link and decryption key from the link ID that it knows, and can't access the link data. + +This objective is achieved by using `secret_box` encryption of link data with the symmetric key derived from link URI, which is different from link ID known to the server. As it is a symmetric encryption, it is secure against quantum computers. + +2. **Allow changing encrypted link data without changing the link itself**. + +This is necessary to allow changes in user profile, chat preferences and welcome messages. + +This is possible via a specific server request that allows to change user-defined part of the link data to the link owner. Because the link is derived from fixed part of the link data, the link itself remains the same. + +3. **Prevent MITM attack on the link data by the server, even if the server obtained the link**. + +It means that the server should not be able to replace the associated link data even if it somehow obtained the link and can decrypt the data. + +This objective is achieved by deriving encryption key from the hash of the fixed part of the link data — if server changes the link data, it would be rejected by the client, as its hash won't match the link. Server also cannot replace the user-defined part of the link data, because it is signed and will be verified with the key included in the fixed part of link data. + +Clients use `HKDF` for key derivation, `SHA3-256` to compute hash of the fixed part of link data, and `ED25519` signature to sign user-defined link data. + +4. **Prevent undetectably accessing encrypted link data of one-time links**. + +This is explained in the previous section — if link observers retrieve the link data, the link will become inaccessible for the intended recipient. + +This objective is achieved because the link data of 1-time invitation link data can only be accessed with the server request that locks queue on the first access. Any subsequent access to the queue must uses the same authorization key (`ED25519`). + +5. **The link owner cannot include address of another queue in the link**. + +It means that the link cannot redirect the connecting party to another server or to another queue on the same server — the apps would reject the links that attempt to do it. While allowing redirects may be seen as higher security from the server, it would open the possibility of resource exhaustion attacks, as the server would not know if the links were actually used to connect or not, and when the link data can be removed. So we decided that preventing redirects is a better tradeoff. This cryptographically enforced association between link and queue allows to remove link data from the server once the connection is established, or once some time passes (e.g., 3 weeks for unused one-time invitation links). + +This objective is achieved by including queue ID and link data into the same server response. + +6. **Prevent link owner from being able to change the queue address and encryption keys in the link**. + +This quality prevents the MITM attack on e2e encryption via break-in attack on the client of the link owner. + +The server does not provide any API to change the fixed part of the link data. Also, changing fixed data would require changing the link, as otherwise the hash of the data won't match the link. + +7. **It should be impossible to check the existence of a messaging queue for one-time invitation links**. + +It means that any 3rd party that observed 1-time invitation link (e.g., by reading the message in the messenger or email where it was sent) must not be able to undetectably confirm whether this messaging queue still exists, by attempting to create another queue with the same link ID and the same link data. + +This objective is achieved by servers requiring that sender ID is derived (using `SHA3-384`) from request correlation ID, so an arbitrary sender ID cannot be used, and by generating link ID on the server — for 1-time invitation link, the link ID is included in the link in addition to link key, and is not derived from the link data. + +See the detailed [threat model](https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2025-03-16-smp-queues.md#threat-model) for protocol extension supporting the new user experience of making connections. + +## Let us know what you think! + +We worked really hard to deliver this big change — the simplicity of user experience required to hide a lot of complexity under the hood. We really hope that it will help you to bring more of your friends to SimpleX network and to benefit from using secure communications. + +The stable versions v6.4 and v6.4.1 will be released this July, but you can already use the beta version available via [Play Store](https://play.google.com/store/apps/details?id=chat.simplex.app) (Android), [Test Flight](https://testflight.apple.com/join/DWuT2LQu) (iOS) and [GitHub](https://github.com/simplex-chat/simplex-chat/releases/tag/v6.4.0-beta.4) (Android and desktop). + +Big thank you to everybody who uses SimpleX network, even though the experience of connecting to people was complex before this release. + +With your help, SimpleX network should be able to get over the million active users now! + +## SimpleX network + +Some links to answer the most common questions: + +[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers). + +[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). + +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). + +[Frequently asked questions](../docs/FAQ.md). + +Please also see our [website](https://simplex.chat). + +## Please support us with your donations + +Huge *thank you* to everybody who donated to SimpleX Chat! + +Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations. + +Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users. + +Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds — any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/#please-support-us-with-your-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder + +[1]: An interesting case study is the rise and fall of Nokia as the dominant supplier of mobile phones. The slogan "Connecting people" was created in 1992 by Ove Strandberg, an intern at Nokia, and as it was adopted as the core mission of the company, we saw it rise as the world's main mobile phone supplier. The fall of Nokia is usually attributed on iPhone success. But it may also be attributed to internal cultural changes, with Nokia's communications chief leaving in early 2000s, and Nokia failure to understand how the definition of "Connecting people" should evolve with time. + +[2]: One-time invitation can only be used once by the person you gave it too. Once your contact scans the one-time link, nobody else can connect to you via this link. Contact address can be used by multiple people, and even if you later delete the address, everybody who connected to you will remain connected. You can read more about the differences between one-time invitation links and contact addresses [here](../docs/guide/making-connections.md#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + +[3]: Read more about how man-in-the-middle attack works in our [post about e2e encryption properties](./20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md#5-man-in-the-middle-attack-mitigated-by-two-factor-key-exchange). It also has the comparison of e2e encryption security in different messengers. + +[4]: SimpleX apps also allow security code verification, but it protects against different attack — the link substitution by the channel you use to pass it, not from the attacks by the servers — SimpleX servers cannot compromise e2e encryption. + +[5]: That is also why "securely scanning users' communications", also known as "Chat Control" is impossible — what communication operator can access, cyber-criminals will also access, and instead of reducing crime it would expose users to more crime. diff --git a/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.md b/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.md new file mode 100644 index 0000000000..7a227d4667 --- /dev/null +++ b/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.md @@ -0,0 +1,151 @@ +--- +layout: layouts/article.html +title: "SimpleX Chat v6.4.1: welcome your contacts, review members to protect groups, and more." +date: 2025-07-29 +previewBody: blog_previews/20250729.html +image: images/20250729-join2.png +imageBottom: true +permalink: "/blog/20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.html" +--- + +# SimpleX Chat v6.4.1: welcome your contacts, review members to protect groups, and more. + +**Published:** Jul 29, 2025 + +**What's new in v6.4.1**: + +- [welcome your contacts](#welcome-your-contacts-the-new-experience-of-making-connections): set your profile bio and welcome message. +- [protect your communities](#protect-your-groups) from spam and abuse: + - review new members ("knocking"), + - moderator role to delegate message moderation to trusted members, + - receive direct feedback from your group members. +- [other improvements](#other-improvements): set default time to delete messages for new contacts. +- [improved app integrity](#improved-app-integrity). + +Also, we added 3 new interface languages to Android and desktop apps: Indonesian, Romanian and Vietnamese. + +Huge thanks to our users who [contributed translations](https://github.com/simplex-chat/simplex-chat#help-translating-simplex-chat). + +## What's new in v6.4.1 + +### Welcome your contacts: the new experience of making connections + + + +The new simple way to connect to your friends is fully available in this version. + +We received many compliments from our users who started using it in beta versions and in v6.4 about how it simplifies connecting with friends. We agree - this is the biggest UX revolution since the app was released. + +Instead of connecting blindly, and waiting until your contact is online, as it was before, you can now see profile and welcome message of the person you connect to, before you connect. + +When you tap Open new chat you can decide which profile to use to connect or if you want to connect incognito, and in some cases you can include a message with your connection request. + +This way, the conversation with your friends starts even before they connect to you! + +For previously created SimpleX addresses and group links you have an option to upgrade. The links will become short, and will include profile information into link data. Old long links will continue to work, so you won't lose any contacts or members during the upgrade. + +These links are now short enough to be shared in your social media profiles - they are less than 80 characters. + +And as before, it is as secure - servers cannot see you profiles, unless they have the link, and cannot modify them even if they somehow get the link. You can read more about security property and other technical details in our [post about SimpleX protocols extension](./20250703-simplex-network-protocol-extension-for-securely-connecting-people.md) supporting this new user experience. + +Thank you for bringing your friends to SimpleX network! + +### Protect your groups + + + +**Review new members** + +Since v6.4 there are some major improvements in your ability to protect your group from spam and abuse. + +You can enable an option to review all new group members. It is also commonly called "knocking". It allows you to: +- ask prospective members any questions, +- explain the group rules, +- make sure their profile is appropriate for the group, +- decide whether to allow them joining the group, and whether they should be able to send messages in the group. + +Some small groups may enable member review permanently, while larger public groups may enable it temporarily during spam/troll attacks. + +**New role for group moderators** + +In addition to that, there is a new group role - moderator. + +This role allows: +- to approve members in review, +- moderate messages, +- block members for all. + +Unlike admins, moderators can't add new members or permanently remove members from the group. This allows you to delegate group moderation to your community members without risking that they may disrupt the group. + +**Receive direct feedback from group members** + +Your group members now can send messages to group admins. Each conversation with a group member is a mini-group where all group owners, admins and moderators can talk to a member. Reports that members can send [since v6.3](./20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md) are also added to chat with member, allowing you to discuss the report. + +### Other improvements + +**Enable disappearing messages for new contacts** + +Now you can enable disappearing messages for all new contacts automatically. Tap your profile image in the corner, then tap Chat preferences and set time for messages to disappear. + +**Improved message delivery** + +We improved networking layer by increasing request timeouts for all background requests. It substantially reduces traffic on slow networks. + +### Improved app integrity + +**Supply chain security** + +The app security depends on security of its components and its build process, and many of these components are created by third parties. In this version we improved the build process to control the upgrades of these components: +- all 3rd party GitHub actions used during the build are now moved to [the forks we control](https://github.com/simplex-chat?q=action&type=fork&sort=name) - it prevents supply chain attacks via build actions. +- we now build VLC library for all platforms from the source code ourselves, in [this repository](https://github.com/simplex-chat/vlc). +- SQLCipher and [Haskell dependencies](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/dependencies/HASKELL.md) versions were already "locked" prior to this version. + +**Automatic virus scanning** + +We now run automatic daily virus scanning of all apps released via GitHub using [VirusTotal.com](https://www.virustotal.com/). + +You can see the scan results [here](https://github.com/simplex-chat/simplex-virutstotal-scan). + +**Reproducible builds** + +In addition to [server builds](https://github.com/simplex-chat/simplexmq/releases/tag/v6.4.1) that were reproducible since v6.3, the builds of Linux CLI and desktop apps are now reproducible too. You can build Linux apps from source using [this script](https://github.com/simplex-chat/simplex-chat/blob/master/scripts/simplex-chat-reproduce-builds.sh). + +*Please note*: Linux package upgrades may change the build. + +Stable builds of Linux apps are now independently reproduced and [signed by our and Flux teams](https://github.com/simplex-chat/simplex-chat/releases/tag/v6.4.1) - it verifies the integrity of GitHub builds. + +Huge thanks to [Flux](https://runonflux.com/) for doing that and for providing their servers via the app. + +## SimpleX network + +Some links to answer the most common questions: + +[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers). + +[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). + +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). + +[Frequently asked questions](../docs/FAQ.md). + +Please also see our [website](https://simplex.chat). + +## Please support us with your donations + +Huge *thank you* to everybody who donated to SimpleX Chat! + +Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations. + +Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users. + +Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds — any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/#please-support-us-with-your-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder diff --git a/blog/README.md b/blog/README.md index 00a84eca6c..4544dc0f45 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,6 +1,28 @@ # Blog -Mar 3, 2025 [SimpleX Chat v6.3: new user experience and safety in public groups](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) +Jul 29, 2025 [SimpleX Chat v6.4.1: welcome your contacts, review members to protect groups, and more.](./20250729-simplex-chat-v6-4-1-welcome-contacts-protect-groups-app-security.md) + +What's new in v6.4.1: + +- welcome your contacts: set your profile bio and welcome message. +- protect your communities from spam and abuse: + - review new members ("knocking"), + - moderator role to delegate message moderation to trusted members, + - receive direct feedback from your group members. +- set default time to delete messages for new contacts. +- improved app integrity: Linux app builds are now reproducible. + +Also, we added 3 new interface languages to Android and desktop apps, thanks to our users: Indonesian, Romanian and Vietnamese. + +--- + +Jul 3, 2025 [SimpleX network: new experience of connecting with people — available in SimpleX Chat v6.4-beta.4](./20250703-simplex-network-protocol-extension-for-securely-connecting-people.md) + +Now you can start talking to your contacts much faster, as soon as you scan the link. This technical post covers the technology that enabled this new user experience — short links and associated data of messaging queues. + +--- + +Mar 8, 2025 [SimpleX Chat v6.3: new user experience and safety in public groups](./20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html) What's new in v6.3: - preventing spam and abuse in public groups. @@ -12,13 +34,13 @@ Also, we added Catalan interface language to Android and desktop apps, thanks to The last but not the least - server builds are now reproducible! --- +--- Jan 14, 2025 [SimpleX network: large groups and privacy-preserving content moderation](./20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md) This post explains how server operators can moderate end-to-end encrypted conversations without compromising user privacy or end-to-end encryption. --- +--- Dec 10, 2024 [SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps](./20241210-simplex-network-v6-2-servers-by-flux-business-chats.md) @@ -27,7 +49,7 @@ Dec 10, 2024 [SimpleX network: preset servers operated by Flux, business chats a - Better user experience: open on the first unread, jump to quoted messages, see who reacted. - Improving notifications in iOS app. --- +--- Nov 25, 2024 [Servers operated by Flux - true privacy and decentralization for all users](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md) diff --git a/blog/images/20250703-card.jpg b/blog/images/20250703-card.jpg new file mode 100644 index 0000000000..937c70911d Binary files /dev/null and b/blog/images/20250703-card.jpg differ diff --git a/blog/images/20250703-connect1.png b/blog/images/20250703-connect1.png new file mode 100644 index 0000000000..18638a9b77 Binary files /dev/null and b/blog/images/20250703-connect1.png differ diff --git a/blog/images/20250703-connect1a.png b/blog/images/20250703-connect1a.png new file mode 100644 index 0000000000..0d7bb37ffd Binary files /dev/null and b/blog/images/20250703-connect1a.png differ diff --git a/blog/images/20250703-connect2.png b/blog/images/20250703-connect2.png new file mode 100644 index 0000000000..87ad2458bb Binary files /dev/null and b/blog/images/20250703-connect2.png differ diff --git a/blog/images/20250729-connect1.png b/blog/images/20250729-connect1.png new file mode 100644 index 0000000000..984e4e44ef Binary files /dev/null and b/blog/images/20250729-connect1.png differ diff --git a/blog/images/20250729-connect2.png b/blog/images/20250729-connect2.png new file mode 100644 index 0000000000..784ea74e84 Binary files /dev/null and b/blog/images/20250729-connect2.png differ diff --git a/blog/images/20250729-join1.png b/blog/images/20250729-join1.png new file mode 100644 index 0000000000..6cec898eaf Binary files /dev/null and b/blog/images/20250729-join1.png differ diff --git a/blog/images/20250729-join2.png b/blog/images/20250729-join2.png new file mode 100644 index 0000000000..43fa412112 Binary files /dev/null and b/blog/images/20250729-join2.png differ diff --git a/bots/README.md b/bots/README.md new file mode 100644 index 0000000000..9449e9d847 --- /dev/null +++ b/bots/README.md @@ -0,0 +1,199 @@ +# SimpleX Chat bot API + +- [Why create a bot](#why-create-a-bot) +- [What is SimpleX bot](#what-is-simplex-bot) +- [How to configure bot profile](#how-to-configure-bot-profile) +- [How to create a bot](#how-to-create-a-bot) +- [Sending commands](#sending-commands) +- [Processing events](#processing-events) +- [Security considerations](#security-considerations) +- [Useful bots](#useful-bots) +- [API types reference](./api/README.md) (another page) + + +## Why create a bot + +You can implement SimpleX Chat for these and many other scenarios: +- customer support - both as a single- and a multi-agent support chat (using SimpleX Chat [business address](https://simplex.chat/docs/business.html) feature), +- information search and retrieval bots, with or without LLM integration, +- moderation bots, to moderate your group and communities. +- broadcast bot, when messages from your trusted users are forwarded to all connected contacts - e.g., see our SimpleX Status bot in the app ([source code](../apps/simplex-broadcast-bot/)), +- feedback bot, when messages from connected contacts are forwarded to a preset list of your trusted users, +- P2P trading bots, connecting buyers and sellers, +- etc. + +We will share all useful bots you create in the bottom of this page - please submit a PR to add it. + + +## What is SimpleX bot + +SimpleX bot is a participant of SimpleX network. Theoretically, bot can do everything that a usual SimpleX Chat user can do – send and receive messages and files, connect to addresses and join groups, etc. But to be useful, a bot should distinguish itself as a bot, and to provide an interface for the users to interact with it. + +## How to configure bot profile + +Starting from v6.4.3, SimpleX Chat apps support bot configuration to distinguish bots, to highlight commands in messages, and to show command menus. + +### Set up bot profile + +To distinguish SimpleX user profile as a bot, set its `peerType` property to `"bot"`. It can be done in one of these ways: +- using CLI options `--create-bot-display-name` and `create-bot-allow-files` when first starting CLI to create bot profile, +- using command `/create bot [files=on] [ ]` (if name contains spaces, it must be in single quotes), when creating additional bot profiles in the same database, +- by configuring bot commands that the users will see in the UI when they type `/` character or tap `//` button with `/set bot commands ...` CLI command (see syntax below), +- by using [APIUpdateProfile](./api/COMMANDS.md#apiupdateprofile) bot command to set `peerType` and configure bot commands at the same time. + +### Configure bot commands + +Bot commands are messages that start from `/` character. Normally, they would consist of lowercase latin letters, but commands can use any letters, digits and underscores. Commands can have parameters. + +All commands in messages will be highlighted in the chats with the bot, and when users tap them, they will be instantly sent. If the message has a single line and starts from `/` character, the whole message will be highlighted. Otherwise, if command is included as part of the message, it will be highlighted until the first space after `/` character: e.g., `/list` command in Directory service shows user's groups. + +*Please note*: commands in messages will be highlighted based purely on `/` character, regardless of whether they are supported by the bot or included in bot configuration. It allows bots to have "hidden" commands that bot would support, but that won't be shown in the menu. But it may also lead to mistakes if bot sends incorrect commands in the instructions to the users. + +Bots can also send highlighted commands with parameters. To do that, bots should surround both command and its parameters in single quotes: e.g., `/'role 2'`. Quotes won't show in the apps UI, and if the user taps this command, it will be sent as `/role 2`. + +Configured bot commands will be be offered to the users as a menu, and for quick lookup as the user types. + +Bot commands configuration is a property in `preferences` object in bot profile received by the user. These preferences can be configured both on the bot user profile level, to offer the same commands to all connected users, and as overrides for specific contacts, to offer different commands to different bot contacts. + +Configuring commands in bot user can be done either with [APIUpdateProfile](./api/COMMANDS.md#apiupdateprofile) or with `/set bot commands` CLI command: + +``` +/set bot commands +``` + +where: + +``` +commands = [,...] +commandOrMenu = command | menu +command = '