diff --git a/.dockerignore b/.dockerignore index 8f595b4d9c..977836d339 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,4 +11,3 @@ **/test/androidTestEspresso/res/values/arrays.xml reproducible-builds/certs reproducible-builds/outputs -reproducible-builds/test-reports diff --git a/.editorconfig b/.editorconfig index 9e408524b6..c0a90ddfbb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,6 @@ root = true [*.kt] indent_size = 2 +ktlint_standard_trailing-comma-on-call-site = disable +ktlint_standard_trailing-comma-on-declaration-site = disable +ktlink_standard_spacing-between-declarations-with-annotations = disable diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index ccc84d7ee4..7fffeef69d 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -14,19 +14,12 @@ jobs: if: "github.event.base_ref != 'refs/heads/main'" runs-on: ubuntu-22.04 env: + GRADLE_OPTS: "-Dorg.gradle.project.kotlin.compiler.execution.strategy=in-process" CI_MAPS_API_KEY: ${{ secrets.MAPS_API_KEY }} steps: - uses: actions/checkout@v3 - - name: Increase swap space - run: | - sudo fallocate -l 8G /swapB - sudo chmod 600 /swapB - sudo mkswap /swapB - sudo swapon /swapB - swapon --show - - name: Set up builder image run: docker-compose build working-directory: reproducible-builds diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15220740c2..85705198a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,19 +14,12 @@ jobs: if: "github.event.base_ref == 'refs/heads/main'" runs-on: ubuntu-22.04 env: + GRADLE_OPTS: "-Dorg.gradle.project.kotlin.compiler.execution.strategy=in-process" CI_MAPS_API_KEY: ${{ secrets.MAPS_API_KEY }} steps: - uses: actions/checkout@v3 - - name: Increase swap space - run: | - sudo fallocate -l 8G /swapB - sudo chmod 600 /swapB - sudo mkswap /swapB - sudo swapon /swapB - swapon --show - - name: Set up builder image run: docker-compose build working-directory: reproducible-builds diff --git a/.github/workflows/reprocheck.yml b/.github/workflows/reprocheck.yml index 8ddc51a43d..1a5d2752ad 100644 --- a/.github/workflows/reprocheck.yml +++ b/.github/workflows/reprocheck.yml @@ -23,6 +23,7 @@ jobs: name: Build new runs-on: ubuntu-22.04 env: + GRADLE_OPTS: "-Dorg.gradle.project.kotlin.compiler.execution.strategy=in-process" CI_MAPS_API_KEY: ${{ secrets.MAPS_API_KEY }} steps: @@ -30,14 +31,6 @@ jobs: with: ref: "${{ env.TAG_NAME }}" - - name: Increase swap space - run: | - sudo fallocate -l 8G /swapB - sudo chmod 600 /swapB - sudo mkswap /swapB - sudo swapon /swapB - swapon --show - - name: Set up builder image run: docker-compose build working-directory: reproducible-builds diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4e80085b9..b287ee6864 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,9 @@ on: - '.github/FUNDING.yml' - '.github/ISSUE_TEMPLATE/**' +permissions: + contents: read # to fetch code (actions/checkout) + jobs: wrapper_validation: name: Validate Gradle wrapper @@ -18,41 +21,31 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 11 - - name: Run wrapper validation - uses: gradle/wrapper-validation-action@master + uses: gradle/wrapper-validation-action@v1 test: name: Run tests runs-on: ubuntu-22.04 + env: + GRADLE_OPTS: "-Dorg.gradle.project.kotlin.compiler.execution.strategy=in-process" steps: - uses: actions/checkout@v3 - - name: Increase swap space - run: | - sudo fallocate -l 8G /swapB - sudo chmod 600 /swapB - sudo mkswap /swapB - sudo swapon /swapB - swapon --show - - - name: Set up builder image - run: docker-compose build - working-directory: reproducible-builds + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + cache: gradle - name: Run tests - run: docker-compose run test - working-directory: reproducible-builds + run: ./gradlew build --no-daemon - name: Archive reports for failed tests if: "failure()" uses: actions/upload-artifact@v3 with: name: test-reports - path: "reproducible-builds/test-reports" + path: '*/build/reports' diff --git a/.gitignore b/.gitignore index 8558e66f8e..f2166627fe 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ signing.properties test/androidTestEspresso/res/values/arrays.xml /reproducible-builds/certs /reproducible-builds/outputs -/reproducible-builds/test-reports diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index f4d066945d..6a5c9cbb8c 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -43,11 +43,10 @@ + diff --git a/Dockerfile b/Dockerfile index 68717fc9bc..259d548078 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,4 @@ COPY . /molly/ WORKDIR /molly RUN git clean -df -ENV ORG_GRADLE_PROJECT_CI=true - -ENTRYPOINT ["./gradlew"] +ENTRYPOINT ["./gradlew", "-PCI=true"] diff --git a/app/build.gradle b/app/build.gradle index e7af4bdf38..e64dbd764a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,56 +1,22 @@ import com.android.build.api.dsl.ManagedVirtualDevice -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'com.google.protobuf' -apply plugin: 'androidx.navigation.safeargs' -apply plugin: 'org.jlleitschuh.gradle.ktlint' -apply from: 'translations.gradle' -apply plugin: 'org.jetbrains.kotlin.android' -apply plugin: 'app.cash.exhaustive' -apply plugin: 'kotlin-parcelize' -apply plugin: 'com.squareup.wire' - - -repositories { - maven { - url "https://raw.githubusercontent.com/signalapp/maven/master/sqlcipher/release/" - content { - includeModule 'org.signal', 'android-database-sqlcipher' - } - } - maven { - url "https://raw.githubusercontent.com/mollyim/maven/master/argon2/releases/" - content { - includeModule 'im.molly', 'argon2' - } - } - maven { - url "https://raw.githubusercontent.com/mollyim/maven/master/ringrtc/releases/" - content { - includeModule 'im.molly', 'ringrtc-android' - } - } - maven { - url "https://raw.githubusercontent.com/mollyim/maven/master/native-utils/releases/" - content { - includeModule 'im.molly', 'native-utils' - } - } - - google() - mavenCentral() - mavenLocal() - maven { - url "https://dl.cloudsmith.io/qxAgwaeEE1vN8aLU/mobilecoin/mobilecoin/maven/" - } - jcenter { - content { - includeVersion "mobi.upod", "time-duration-picker", "1.1.3" - } - } +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'com.google.protobuf' + id 'androidx.navigation.safeargs' + id 'org.jetbrains.kotlin.android' + id 'app.cash.exhaustive' + id 'kotlin-parcelize' + id 'com.squareup.wire' + id 'android-constants' + id 'translations' } +// Sort baseline.profm for reproducible builds +// See issue: https://issuetracker.google.com/issues/231837768 +apply from: 'fix-profm.gradle' + protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.18.0' @@ -90,13 +56,8 @@ ext { MAPS_API_KEY = getEnv('CI_MAPS_API_KEY') ?: mapsApiKey } -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" -} - -def canonicalVersionCode = 1213 -def canonicalVersionName = "6.11.7" +def canonicalVersionCode = 1232 +def canonicalVersionName = "6.14.5" def mollyRevision = 0 def postFixSize = 100 @@ -113,20 +74,21 @@ def selectableVariants = [ 'stagingFossWebsiteDebug', 'stagingFossWebsiteRelease', 'stagingGmsWebsiteDebug', - 'stagingGmsWebsiteInstrumentation', 'stagingGmsWebsiteRelease', ] android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'org.thoughtcrime.securesms' + + buildToolsVersion = signalBuildToolsVersion + compileSdkVersion = signalCompileSdkVersion flavorDimensions 'environment', 'license', 'distribution' useLibrary 'org.apache.http.legacy' testBuildType 'instrumentation' kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" freeCompilerArgs = ["-Xallow-result-return-type"] } @@ -163,11 +125,6 @@ android { } } - lintOptions { - abortOnError true - baseline file("lint-baseline.xml") - disable "LintError" - } sourceSets { test { @@ -181,26 +138,19 @@ android { compileOptions { coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION + sourceCompatibility signalJavaVersion + targetCompatibility signalJavaVersion } packagingOptions { - exclude 'LICENSE.txt' - exclude 'LICENSE' - exclude 'NOTICE' - exclude 'asm-license.txt' - exclude 'META-INF/LICENSE' - exclude 'META-INF/NOTICE' - exclude 'META-INF/proguard/androidx-annotations.pro' - exclude 'libsignal_jni.dylib' - exclude 'signal_jni.dll' - exclude '**/*.proto' + resources { + excludes += ['LICENSE.txt', 'LICENSE', 'NOTICE', 'asm-license.txt', 'META-INF/LICENSE', 'META-INF/LICENSE.md', 'META-INF/NOTICE', 'META-INF/LICENSE-notice.md', 'META-INF/proguard/androidx-annotations.pro', 'libsignal_jni.dylib', 'signal_jni.dll', '**/*.proto'] + } jniLibs { // MOLLY: Compress native libs by default as APK is not split on ABIs useLegacyPackaging true - } - } + } } + buildFeatures { viewBinding true @@ -219,8 +169,8 @@ android { versionCode canonicalVersionCode * postFixSize + mollyRevision versionName project.hasProperty('CI') ? getCommitTag() : canonicalVersionName - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK + minSdkVersion signalMinSdkVersion + targetSdkVersion signalTargetSdkVersion multiDexEnabled true @@ -253,8 +203,7 @@ android { buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" buildConfigField "int", "CONTENT_PROXY_PORT", "443" buildConfigField "String", "SIGNAL_AGENT", "\"OWA\"" - buildConfigField "String", "CDS_MRENCLAVE", "\"74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081\"" - buildConfigField "String", "CDSI_MRENCLAVE", "\"ef4787a56a154ac6d009138cac17155acd23cfe4329281252365dd7c252e7fbf\"" + buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\"" buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " + "\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " + "\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")" @@ -382,7 +331,6 @@ android { buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\"" buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\"" buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"" - buildConfigField "String", "CDS_MRENCLAVE", "\"74778bb0f93ae1f78c26e67152bab0bbeb693cd56d1bb9b4e9244157acc58081\"" buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " + "\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " + "\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")" @@ -397,6 +345,12 @@ android { } } + lint { + abortOnError true + baseline file('lint-baseline.xml') + disable 'LintError' + } + android.applicationVariants.all { variant -> def isStaging = variant.productFlavors*.name.contains("staging") def hasSigningConfig = buildType.signingConfig || variant.signingConfig @@ -432,7 +386,7 @@ dependencies { implementation (libs.androidx.appcompat) { version { - strictly '1.5.1' + strictly '1.6.1' } } implementation libs.androidx.window.window @@ -440,7 +394,6 @@ dependencies { implementation libs.androidx.recyclerview implementation libs.material.material implementation libs.androidx.legacy.support - implementation libs.androidx.cardview implementation libs.androidx.preference implementation libs.androidx.legacy.preference implementation libs.androidx.gridlayout @@ -518,7 +471,6 @@ dependencies { implementation libs.greenrobot.eventbus implementation libs.waitingdots implementation libs.google.zxing.android.integration - implementation libs.time.duration.picker implementation libs.google.zxing.core implementation libs.google.flexbox implementation (libs.subsampling.scale.image.view) { @@ -571,6 +523,7 @@ dependencies { androidTestImplementation testLibs.androidx.test.ext.junit.ktx androidTestImplementation testLibs.mockito.android androidTestImplementation testLibs.mockito.kotlin + androidTestImplementation testLibs.mockk.android androidTestImplementation testLibs.square.okhttp.mockserver instrumentationImplementation (libs.androidx.fragment.testing) { diff --git a/app/fix-profm.gradle b/app/fix-profm.gradle new file mode 100644 index 0000000000..038f474cd0 --- /dev/null +++ b/app/fix-profm.gradle @@ -0,0 +1,38 @@ +import com.android.tools.profgen.ArtProfileKt +import com.android.tools.profgen.ArtProfileSerializer +import com.android.tools.profgen.DexFile + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + } +} + +project.afterEvaluate { + tasks.each { task -> + if (task.name.startsWith("compile") && task.name.endsWith("ReleaseArtProfile")) { + task.doLast { + outputs.files.each { file -> + if (file.name.endsWith(".profm")) { + println("Sorting ${file} ...") + def version = ArtProfileSerializer.valueOf("METADATA_0_0_2") + def profile = ArtProfileKt.ArtProfile(file) + def keys = new ArrayList(profile.profileData.keySet()) + def sortedData = new LinkedHashMap() + Collections.sort keys, new DexFile.Companion() + keys.each { key -> sortedData[key] = profile.profileData[key] } + new FileOutputStream(file).with { + write(version.magicBytes$profgen) + write(version.versionBytes$profgen) + version.write$profgen(it, sortedData, "") + } + } + } + } + } + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt index 1731009acf..210da55a87 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/SignalInstrumentationApplicationContext.kt @@ -1,9 +1,17 @@ package org.thoughtcrime.securesms +import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.logging.AndroidLogger +import org.signal.core.util.logging.Log +import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider import org.thoughtcrime.securesms.crypto.MasterSecretUtil +import org.thoughtcrime.securesms.database.LogDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider +import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger +import org.thoughtcrime.securesms.logging.PersistentLogger +import org.thoughtcrime.securesms.testing.InMemoryLogger /** * Application context for running instrumentation tests (aka androidTests). @@ -14,8 +22,24 @@ class SignalInstrumentationApplicationContext : ApplicationContext() { super.onCreate() } + val inMemoryLogger: InMemoryLogger = InMemoryLogger() + override fun initializeAppDependencies() { val default = ApplicationDependencyProvider(this) ApplicationDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default)) + ApplicationDependencies.getDeadlockDetector().start() + } + + override fun initializeLogging() { + persistentLogger = PersistentLogger(this) + + Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger) + + SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) + + SignalExecutors.UNBOUNDED.execute { + Log.blockUntilAllWritesFinished() + LogDatabase.getInstance(this).trimToSize() + } } } diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt index 00fc4f7fd6..8c2579f1c8 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModelTest.kt @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository import org.thoughtcrime.securesms.registration.VerifyResponseProcessor import org.thoughtcrime.securesms.testing.Get import org.thoughtcrime.securesms.testing.MockProvider +import org.thoughtcrime.securesms.testing.Post import org.thoughtcrime.securesms.testing.Put import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.assertIs @@ -82,8 +83,10 @@ class ChangeNumberViewModelTest { lateinit var setPreKeysRequest: PreKeyState InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) }, - Put("/v1/accounts/number") { r -> + Put("/v2/accounts/number") { r -> changeNumberRequest = r.parsedRequestBody() MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni)) }, @@ -95,6 +98,7 @@ class ChangeNumberViewModelTest { ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow // THEN @@ -112,11 +116,14 @@ class ChangeNumberViewModelTest { val oldE164 = Recipient.self().requireE164() InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) }, - Put("/v1/accounts/number") { MockResponse().failure(500) }, + Put("/v2/accounts/number") { MockResponse().failure(500) } ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet() // THEN @@ -142,12 +149,15 @@ class ChangeNumberViewModelTest { val oldE164 = Recipient.self().requireE164() InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) }, - Put("/v1/accounts/number") { MockResponse().connectionFailure() }, + Put("/v2/accounts/number") { MockResponse().connectionFailure() }, Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, oldPni, oldE164)) } ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet() // THEN @@ -181,8 +191,10 @@ class ChangeNumberViewModelTest { lateinit var setPreKeysRequest: PreKeyState InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) }, - Put("/v1/accounts/number") { r -> + Put("/v2/accounts/number") { r -> changeNumberRequest = r.parsedRequestBody() MockResponse().timeout() }, @@ -195,6 +207,7 @@ class ChangeNumberViewModelTest { ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet() // THEN @@ -225,8 +238,10 @@ class ChangeNumberViewModelTest { MockProvider.mockGetRegistrationLockStringFlow(kbsRepository) InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) }, - Put("/v1/accounts/number") { r -> + Put("/v2/accounts/number") { r -> changeNumberRequest = r.parsedRequestBody() if (changeNumberRequest.registrationLock.isNullOrEmpty()) { MockResponse().failure(423, MockProvider.lockedFailure) @@ -242,6 +257,7 @@ class ChangeNumberViewModelTest { ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor -> processor.registrationLock() assertIs true Recipient.self().requirePni() assertIsNot newPni @@ -263,8 +279,10 @@ class ChangeNumberViewModelTest { lateinit var setPreKeysRequest: PreKeyState InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) }, - Put("/v1/accounts/number") { r -> + Put("/v2/accounts/number") { r -> changeNumberRequest = r.parsedRequestBody() if (changeNumberRequest.deviceMessages.isEmpty()) { MockResponse().failure( @@ -289,6 +307,7 @@ class ChangeNumberViewModelTest { ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow // THEN @@ -307,7 +326,9 @@ class ChangeNumberViewModelTest { MockProvider.mockGetRegistrationLockStringFlow(kbsRepository) InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( - Put("/v1/accounts/number") { r -> + Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) }, + Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) }, + Put("/v2/accounts/number") { r -> changeNumberRequest = r.parsedRequestBody() if (changeNumberRequest.registrationLock.isNullOrEmpty()) { MockResponse().failure(423, MockProvider.lockedFailure) @@ -345,6 +366,7 @@ class ChangeNumberViewModelTest { ) // WHEN + viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor -> processor.registrationLock() assertIs true Recipient.self().requirePni() assertIsNot newPni diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt index ed5ae06838..5e741f466a 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt @@ -69,7 +69,7 @@ class ConversationItemPreviewer { sentTimeMillis = System.currentTimeMillis(), serverTimeMillis = System.currentTimeMillis(), receivedTimeMillis = System.currentTimeMillis(), - attachments = PointerAttachment.forPointers(Optional.of(attachments)), + attachments = PointerAttachment.forPointers(Optional.of(attachments)) ) SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get() @@ -88,7 +88,7 @@ class ConversationItemPreviewer { sentTimeMillis = System.currentTimeMillis(), serverTimeMillis = System.currentTimeMillis(), receivedTimeMillis = System.currentTimeMillis(), - attachments = PointerAttachment.forPointers(Optional.of(attachments)), + attachments = PointerAttachment.forPointers(Optional.of(attachments)) ) val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt index 876641d292..9ec88c352c 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database import android.net.Uri import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.FlakyTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Before @@ -34,6 +35,7 @@ class AttachmentTableTest { assertEquals(attachment2.fileName, attachment.fileName) } + @FlakyTest @Test fun givenABlobAndDifferentTransformQuality_whenIInsert2AttachmentsForPreUpload_thenIExpectDifferentFileInfos() { val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory() @@ -61,6 +63,7 @@ class AttachmentTableTest { assertNotEquals(attachment1Info, attachment2Info) } + @FlakyTest @Test fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() { val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt index f791e6f64c..2560b2e10a 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt @@ -119,7 +119,7 @@ class GroupTableTest { } val groupRecord = groupTable.getGroup(v2Group).get() - assertEquals(groupRecord.members.toSet(), setOf(harness.self.id, harness.others[1])) + assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.members.toSet()) } @Test @@ -159,59 +159,6 @@ class GroupTableTest { assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.get().members.toSet()) } - @Test - fun givenAnMmsGroup_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val members: List = listOf(harness.self.id, harness.others[0]) - val other = insertMmsGroup(members + listOf(harness.others[1])) - val mmsGroup = insertMmsGroup(members) - val actual = groupTable.getOrCreateMmsGroupForMembers(members.toSet()) - - assertNotEquals(other, actual) - assertEquals(mmsGroup, actual) - } - - @Test - fun givenMultipleMmsGroups_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val group1Members: List = listOf(harness.self.id, harness.others[0], harness.others[1]) - val group2Members: List = listOf(harness.self.id, harness.others[0], harness.others[2]) - - val group1: GroupId = insertMmsGroup(group1Members) - val group2: GroupId = insertMmsGroup(group2Members) - - val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.toSet()) - val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.toSet()) - - assertEquals(group1, group1Result) - assertEquals(group2, group2Result) - assertNotEquals(group1Result, group2Result) - } - - @Test - fun givenMultipleMmsGroupsWithDifferentMemberOrders_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val group1Members: List = listOf(harness.self.id, harness.others[0], harness.others[1], harness.others[2]).shuffled() - val group2Members: List = listOf(harness.self.id, harness.others[0], harness.others[2], harness.others[3]).shuffled() - - val group1: GroupId = insertMmsGroup(group1Members) - val group2: GroupId = insertMmsGroup(group2Members) - - val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.shuffled().toSet()) - val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.shuffled().toSet()) - - assertEquals(group1, group1Result) - assertEquals(group2, group2Result) - assertNotEquals(group1Result, group2Result) - } - - @Test - fun givenMmsGroupWithOneMember_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() { - val groupMembers: List = listOf(harness.self.id) - val group: GroupId = insertMmsGroup(groupMembers) - - val groupResult: GroupId = groupTable.getOrCreateMmsGroupForMembers(groupMembers.toSet()) - - assertEquals(group, groupResult) - } - @Test fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() { val g1 = insertPushGroup(listOf()) @@ -224,6 +171,24 @@ class GroupTableTest { assertEquals(g2, gr2.get().id) } + @Test + fun givenASharedActiveGroupWithoutAThread_whenISearchForRecipientsWithGroupsInCommon_thenIExpectThatGroup() { + val groupInCommon = insertPushGroup() + val expected = Recipient.resolved(harness.others[0]) + + SignalDatabase.recipients.setProfileSharing(expected.id, false) + + SignalDatabase.recipients.queryGroupMemberContacts("Buddy")!!.use { + assertTrue(it.moveToFirst()) + assertEquals(1, it.count) + assertEquals(expected.id.toLong(), it.requireLong(RecipientTable.ID)) + } + + val groups = groupTable.getPushGroupsContainingMember(expected.id) + assertEquals(1, groups.size) + assertEquals(groups[0].id, groupInCommon) + } + private fun insertThread(groupId: GroupId): Long { val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get() return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient)) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt index 45e29814e5..c20399199d 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/MmsTableTest_stories.kt @@ -128,7 +128,7 @@ class MmsTableTest_stories { sentTimeMillis = 2, serverTimeMillis = 2, receivedTimeMillis = 2, - storyType = StoryType.STORY_WITH_REPLIES, + storyType = StoryType.STORY_WITH_REPLIES ), -1L ).get().messageId @@ -160,7 +160,7 @@ class MmsTableTest_stories { sentTimeMillis = System.currentTimeMillis(), serverTimeMillis = 2, receivedTimeMillis = 2, - storyType = StoryType.STORY_WITH_REPLIES, + storyType = StoryType.STORY_WITH_REPLIES ), -1L ).get().messageId @@ -174,7 +174,7 @@ class MmsTableTest_stories { sentTimeMillis = System.currentTimeMillis(), serverTimeMillis = 2, receivedTimeMillis = 2, - storyType = StoryType.STORY_WITH_REPLIES, + storyType = StoryType.STORY_WITH_REPLIES ), -1L ).get().messageId @@ -219,7 +219,7 @@ class MmsTableTest_stories { sentTimeMillis = 200, serverTimeMillis = 2, receivedTimeMillis = 2, - storyType = StoryType.STORY_WITH_REPLIES, + storyType = StoryType.STORY_WITH_REPLIES ), -1L ) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt index b90f14210e..e9559c9941 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest.kt @@ -11,6 +11,11 @@ import org.signal.core.util.CursorUtil import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.testing.SignalActivityRule +import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.FeatureFlagsAccessor +import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.PNI +import java.util.UUID @RunWith(AndroidJUnit4::class) class RecipientTableTest { @@ -159,4 +164,47 @@ class RecipientTableTest { assertNotEquals(0, results.size) assertFalse(blockedRecipient in results) } + + @Test + fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() { + FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true) + + val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) + + SignalDatabase.recipients.markUnregistered(mainId) + + val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + + val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get() + + assertEquals(mainId, byAci) + assertEquals(byE164, byPni) + assertNotEquals(byAci, byE164) + } + + @Test + fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() { + FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true) + + val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) + val mainRecord = SignalDatabase.recipients.getRecord(mainId) + + SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!) + + val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + + val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get() + + assertEquals(mainId, byAci) + assertEquals(byE164, byPni) + assertNotEquals(byAci, byE164) + } + + companion object { + val ACI_A = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e")) + val PNI_A = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999")) + const val E164_A = "+12222222222" + } } diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt index d800486ebf..13c73e11fd 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPossiblyMerge.kt @@ -58,6 +58,33 @@ class RecipientTableTest_getAndPossiblyMerge { FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true) } + @Test + fun allNonMergeTests() { + test("e164-only insert") { + val id = process(E164_A, null, null) + expect(E164_A, null, null) + + val record = SignalDatabase.recipients.getRecord(id) + assertEquals(RecipientTable.RegisteredState.UNKNOWN, record.registered) + } + + test("pni-only insert") { + val id = process(null, PNI_A, null) + expect(null, PNI_A, null) + + val record = SignalDatabase.recipients.getRecord(id) + assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered) + } + + test("aci-only insert") { + val id = process(null, null, ACI_A) + expect(null, null, ACI_A) + + val record = SignalDatabase.recipients.getRecord(id) + assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered) + } + } + @Test fun allSimpleTests() { test("no match, e164-only") { @@ -346,6 +373,32 @@ class RecipientTableTest_getAndPossiblyMerge { expectThreadMergeEvent(E164_A) } + test("merge, e164 & pni & aci, all provided, no threads") { + given(E164_A, null, null, createThread = false) + given(null, PNI_A, null, createThread = false) + given(null, null, ACI_A, createThread = false) + + process(E164_A, PNI_A, ACI_A) + + expectDeleted() + expectDeleted() + expect(E164_A, PNI_A, ACI_A) + } + + test("merge, e164 & pni & aci, all provided, pni session no threads") { + given(E164_A, null, null, createThread = false) + given(null, PNI_A, null, createThread = true, pniSession = true) + given(null, null, ACI_A, createThread = false) + + process(E164_A, PNI_A, ACI_A) + + expectDeleted() + expectDeleted() + expect(E164_A, PNI_A, ACI_A) + + expectSessionSwitchoverEvent(E164_A) + } + test("merge, e164 & pni, no aci provided") { given(E164_A, null, null) given(null, PNI_A, null) @@ -382,7 +435,7 @@ class RecipientTableTest_getAndPossiblyMerge { expectThreadMergeEvent("") } - test("merge, e164 & pni, aci provided, existing pni session") { + test("merge, e164 & pni, aci provided, existing pni session, thread merge shadows") { given(E164_A, null, null) given(null, PNI_A, null, pniSession = true) @@ -392,6 +445,17 @@ class RecipientTableTest_getAndPossiblyMerge { expectDeleted() expectThreadMergeEvent("") + } + + test("merge, e164 & pni, aci provided, existing pni session, no thread merge") { + given(E164_A, null, null, createThread = true) + given(null, PNI_A, null, createThread = false, pniSession = true) + + process(E164_A, PNI_A, ACI_A) + + expect(E164_A, PNI_A, ACI_A) + expectDeleted() + expectSessionSwitchoverEvent(E164_A) } @@ -407,7 +471,7 @@ class RecipientTableTest_getAndPossiblyMerge { expectThreadMergeEvent("") } - test("merge, e164+pni & aci") { + test("merge, e164+pni & aci, no pni session") { given(E164_A, PNI_A, null) given(null, null, ACI_A) @@ -419,6 +483,52 @@ class RecipientTableTest_getAndPossiblyMerge { expectThreadMergeEvent(E164_A) } + test("merge, e164+pni & aci, pni session, thread merge shadows") { + given(E164_A, PNI_A, null, pniSession = true) + given(null, null, ACI_A) + + process(E164_A, PNI_A, ACI_A) + + expectDeleted() + expect(E164_A, PNI_A, ACI_A) + + expectThreadMergeEvent(E164_A) + } + + test("merge, e164+pni & aci, pni session, no thread merge") { + given(E164_A, PNI_A, null, createThread = true, pniSession = true) + given(null, null, ACI_A, createThread = false) + + process(E164_A, PNI_A, ACI_A) + + expectDeleted() + expect(E164_A, PNI_A, ACI_A) + + expectSessionSwitchoverEvent(E164_A) + } + + test("merge, e164+pni & aci, pni session, no thread merge, pni verified") { + given(E164_A, PNI_A, null, createThread = true, pniSession = true) + given(null, null, ACI_A, createThread = false) + + process(E164_A, PNI_A, ACI_A, pniVerified = true) + + expectDeleted() + expect(E164_A, PNI_A, ACI_A) + } + + test("merge, e164+pni & aci, pni session, pni verified") { + given(E164_A, PNI_A, null, pniSession = true) + given(null, null, ACI_A) + + process(E164_A, PNI_A, ACI_A, pniVerified = true) + + expectDeleted() + expect(E164_A, PNI_A, ACI_A) + + expectThreadMergeEvent(E164_A) + } + test("merge, e164+pni & e164+pni+aci, change number") { given(E164_A, PNI_A, null) given(E164_B, PNI_B, ACI_A) @@ -758,9 +868,10 @@ class RecipientTableTest_getAndPossiblyMerge { return id } - fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false) { - outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = false, changeSelf = changeSelf) + fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false, pniVerified: Boolean = false): RecipientId { + outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf) generatedIds += outputRecipientId + return outputRecipientId } fun expect(e164: String?, pni: PNI?, aci: ACI?) { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_processPnpTupleToChangeSet.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_processPnpTupleToChangeSet.kt deleted file mode 100644 index bac0d2b30f..0000000000 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_processPnpTupleToChangeSet.kt +++ /dev/null @@ -1,842 +0,0 @@ -package org.thoughtcrime.securesms.database - -import androidx.core.content.contentValuesOf -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.testing.SignalDatabaseRule -import org.thoughtcrime.securesms.util.Util -import org.whispersystems.signalservice.api.push.ACI -import org.whispersystems.signalservice.api.push.PNI -import org.whispersystems.signalservice.api.push.ServiceId -import java.lang.AssertionError -import java.lang.IllegalStateException -import java.util.UUID - -@RunWith(AndroidJUnit4::class) -class RecipientTableTest_processPnpTupleToChangeSet { - - @Rule - @JvmField - val databaseRule = SignalDatabaseRule(deleteAllThreadsOnEachRun = false) - - private lateinit var db: RecipientTable - - @Before - fun setup() { - db = SignalDatabase.recipients - } - - @Test - fun noMatch_e164Only() { - val changeSet = db.processPnpTupleToChangeSet(E164_A, null, null, pniVerified = false) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpInsert(E164_A, null, null) - ), - changeSet - ) - } - - @Test - fun noMatch_e164AndPni() { - val changeSet = db.processPnpTupleToChangeSet(E164_A, PNI_A, null, pniVerified = false) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpInsert(E164_A, PNI_A, null) - ), - changeSet - ) - } - - @Test - fun noMatch_aciOnly() { - val changeSet = db.processPnpTupleToChangeSet(null, null, ACI_A, pniVerified = false) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpInsert(null, null, ACI_A) - ), - changeSet - ) - } - - @Test(expected = IllegalStateException::class) - fun noMatch_noData() { - db.processPnpTupleToChangeSet(null, null, null, pniVerified = false) - } - - @Test - fun noMatch_allFields() { - val changeSet = db.processPnpTupleToChangeSet(E164_A, PNI_A, ACI_A, pniVerified = false) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpInsert(E164_A, PNI_A, ACI_A) - ), - changeSet - ) - } - - @Test - fun fullMatch() { - val result = applyAndAssert( - Input(E164_A, PNI_A, ACI_A), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id) - ), - result.changeSet - ) - } - - @Test - fun onlyE164Matches() { - val result = applyAndAssert( - Input(E164_A, null, null), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetPni(result.id, PNI_A), - PnpOperation.SetAci(result.id, ACI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyE164Matches_pniChanges_noAciProvided_existingPniSession() { - val result = applyAndAssert( - Input(E164_A, PNI_B, null, pniSession = true), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetPni(result.id, PNI_A), - PnpOperation.SessionSwitchoverInsert(result.id, E164_A) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyE164Matches_pniChanges_noAciProvided_noPniSession() { - val result = applyAndAssert( - Input(E164_A, PNI_B, null), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetPni(result.id, PNI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun e164AndPniMatches_noExistingSession() { - val result = applyAndAssert( - Input(E164_A, PNI_A, null), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetAci(result.id, ACI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun e164AndPniMatches_existingPniSession() { - val result = applyAndAssert( - Input(E164_A, PNI_A, null, pniSession = true), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetAci(result.id, ACI_A), - PnpOperation.SessionSwitchoverInsert(result.id, E164_A) - ) - ), - result.changeSet - ) - } - - @Test - fun e164AndAciMatches() { - val result = applyAndAssert( - Input(E164_A, null, ACI_A), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetPni(result.id, PNI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyPniMatches_noExistingSession() { - val result = applyAndAssert( - Input(null, PNI_A, null), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - PnpOperation.SetAci(result.id, ACI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyPniMatches_existingPniSession() { - val result = applyAndAssert( - Input(null, PNI_A, null, pniSession = true), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - PnpOperation.SetAci(result.id, ACI_A), - PnpOperation.SessionSwitchoverInsert(result.id, E164_A) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyPniMatches_existingPniSession_changeNumber() { - val result = applyAndAssert( - Input(E164_B, PNI_A, null, pniSession = true), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - PnpOperation.SetAci(result.id, ACI_A), - PnpOperation.ChangeNumberInsert( - recipientId = result.id, - oldE164 = E164_B, - newE164 = E164_A - ), - PnpOperation.SessionSwitchoverInsert(result.id, E164_A) - ) - ), - result.changeSet - ) - } - - @Test - fun pniAndAciMatches() { - val result = applyAndAssert( - Input(null, PNI_A, ACI_A), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - ) - ), - result.changeSet - ) - } - - @Test - fun pniAndAciMatches_changeNumber() { - val result = applyAndAssert( - Input(E164_B, PNI_A, ACI_A), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - PnpOperation.ChangeNumberInsert( - recipientId = result.id, - oldE164 = E164_B, - newE164 = E164_A - ) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyAciMatches() { - val result = applyAndAssert( - Input(null, null, ACI_A), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - PnpOperation.SetPni(result.id, PNI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun onlyAciMatches_changeNumber() { - val result = applyAndAssert( - Input(E164_B, null, ACI_A), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.id), - operations = linkedSetOf( - PnpOperation.SetE164(result.id, E164_A), - PnpOperation.SetPni(result.id, PNI_A), - PnpOperation.ChangeNumberInsert( - recipientId = result.id, - oldE164 = E164_B, - newE164 = E164_A - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164Only_pniOnly_aciOnly() { - val result = applyAndAssert( - listOf( - Input(E164_A, null, null), - Input(null, PNI_A, null), - Input(null, null, ACI_A) - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.thirdId), - operations = linkedSetOf( - PnpOperation.Merge( - primaryId = result.firstId, - secondaryId = result.secondId - ), - PnpOperation.Merge( - primaryId = result.thirdId, - secondaryId = result.firstId - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164Only_pniOnly_noAciProvided() { - val result = applyAndAssert( - listOf( - Input(E164_A, null, null), - Input(null, PNI_A, null), - ), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.firstId), - operations = linkedSetOf( - PnpOperation.Merge( - primaryId = result.firstId, - secondaryId = result.secondId - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164Only_pniOnly_aciProvidedButNoAciRecord() { - val result = applyAndAssert( - listOf( - Input(E164_A, null, null), - Input(null, PNI_A, null), - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.firstId), - operations = linkedSetOf( - PnpOperation.Merge( - primaryId = result.firstId, - secondaryId = result.secondId - ), - PnpOperation.SetAci( - recipientId = result.firstId, - aci = ACI_A - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164Only_pniAndE164_noAciProvided() { - val result = applyAndAssert( - listOf( - Input(E164_A, null, null), - Input(E164_B, PNI_A, null), - ), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.firstId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.secondId), - PnpOperation.SetPni( - recipientId = result.firstId, - pni = PNI_A - ), - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_pniOnly_noAciProvided() { - val result = applyAndAssert( - listOf( - Input(E164_A, PNI_B, null), - Input(null, PNI_A, null), - ), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.firstId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.firstId), - PnpOperation.Merge( - primaryId = result.firstId, - secondaryId = result.secondId - ), - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_e164AndPni_noAciProvided_noSessions() { - val result = applyAndAssert( - listOf( - Input(E164_A, PNI_B, null), - Input(E164_B, PNI_A, null), - ), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.firstId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.secondId), - PnpOperation.SetPni(result.firstId, PNI_A) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_e164AndPni_noAciProvided_sessionsExist() { - val result = applyAndAssert( - listOf( - Input(E164_A, PNI_B, null, pniSession = true), - Input(E164_B, PNI_A, null, pniSession = true), - ), - Update(E164_A, PNI_A, null), - Output(E164_A, PNI_A, null) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.firstId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.secondId), - PnpOperation.SetPni(result.firstId, PNI_A), - PnpOperation.SessionSwitchoverInsert(result.secondId, E164_A), - PnpOperation.SessionSwitchoverInsert(result.firstId, E164_A) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_aciOnly() { - val result = applyAndAssert( - listOf( - Input(E164_A, PNI_A, null), - Input(null, null, ACI_A), - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.secondId), - operations = linkedSetOf( - PnpOperation.Merge( - primaryId = result.secondId, - secondaryId = result.firstId - ), - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_aciOnly_e164RecordHasSeparateE164() { - val result = applyAndAssert( - listOf( - Input(E164_B, PNI_A, null), - Input(null, null, ACI_A), - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.secondId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.firstId), - PnpOperation.SetPni( - recipientId = result.secondId, - pni = PNI_A, - ), - PnpOperation.SetE164( - recipientId = result.secondId, - e164 = E164_A, - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_aciOnly_e164RecordHasSeparateE164_changeNumber() { - val result = applyAndAssert( - listOf( - Input(E164_B, PNI_A, null), - Input(E164_C, null, ACI_A), - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.secondId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.firstId), - PnpOperation.SetPni( - recipientId = result.secondId, - pni = PNI_A, - ), - PnpOperation.SetE164( - recipientId = result.secondId, - e164 = E164_A, - ), - PnpOperation.ChangeNumberInsert( - recipientId = result.secondId, - oldE164 = E164_C, - newE164 = E164_A - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_e164AndPniAndAci_changeNumber() { - val result = applyAndAssert( - listOf( - Input(E164_A, PNI_A, null), - Input(E164_B, PNI_B, ACI_A), - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.secondId), - operations = linkedSetOf( - PnpOperation.RemovePni(result.secondId), - PnpOperation.RemoveE164(result.secondId), - PnpOperation.Merge( - primaryId = result.secondId, - secondaryId = result.firstId - ), - PnpOperation.ChangeNumberInsert( - recipientId = result.secondId, - oldE164 = E164_B, - newE164 = E164_A - ) - ) - ), - result.changeSet - ) - } - - @Test - fun merge_e164AndPni_e164Aci_changeNumber() { - val result = applyAndAssert( - listOf( - Input(E164_A, PNI_A, null), - Input(E164_B, null, ACI_A), - ), - Update(E164_A, PNI_A, ACI_A), - Output(E164_A, PNI_A, ACI_A) - ) - - assertEquals( - PnpChangeSet( - id = PnpIdResolver.PnpNoopId(result.secondId), - operations = linkedSetOf( - PnpOperation.RemoveE164(result.secondId), - PnpOperation.Merge( - primaryId = result.secondId, - secondaryId = result.firstId - ), - PnpOperation.ChangeNumberInsert( - recipientId = result.secondId, - oldE164 = E164_B, - newE164 = E164_A - ) - ) - ), - result.changeSet - ) - } - - private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId { - val id: Long = SignalDatabase.rawDatabase.insert( - RecipientTable.TABLE_NAME, - null, - contentValuesOf( - RecipientTable.PHONE to e164, - RecipientTable.SERVICE_ID to (aci ?: pni)?.toString(), - RecipientTable.PNI_COLUMN to pni?.toString(), - RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id - ) - ) - - return RecipientId.from(id) - } - - private fun insertMockSessionFor(account: ServiceId, address: ServiceId) { - SignalDatabase.rawDatabase.insert( - SessionTable.TABLE_NAME, null, - contentValuesOf( - SessionTable.ACCOUNT_ID to account.toString(), - SessionTable.ADDRESS to address.toString(), - SessionTable.DEVICE to 1, - SessionTable.RECORD to Util.getSecretBytes(32) - ) - ) - } - - data class Input(val e164: String?, val pni: PNI?, val aci: ACI?, val pniSession: Boolean = false, val aciSession: Boolean = false) - data class Update(val e164: String?, val pni: PNI?, val aci: ACI?, val pniVerified: Boolean = false) - data class Output(val e164: String?, val pni: PNI?, val aci: ACI?) - data class PnpMatchResult(val ids: List, val changeSet: PnpChangeSet) { - val id - get() = if (ids.size == 1) { - ids[0] - } else { - throw IllegalStateException("There are multiple IDs, but you assumed 1!") - } - - val firstId - get() = ids[0] - - val secondId - get() = ids[1] - - val thirdId - get() = ids[2] - } - - private fun applyAndAssert(input: Input, update: Update, output: Output): PnpMatchResult { - return applyAndAssert(listOf(input), update, output) - } - - /** - * Helper method that will call insert your recipients, call [RecipientTable.processPnpTupleToChangeSet] with your params, - * and then verify your output matches what you expect. - * - * It results the inserted ID's and changeset for additional verification. - * - * But basically this is here to make the tests more readable. It gives you a clear list of: - * - input - * - update - * - output - * - * that you can spot check easily. - * - * Important: The output will only include records that contain fields from the input. That means - * for: - * - * Input: E164_B, PNI_A, null - * Update: E164_A, PNI_A, null - * - * You will get: - * Output: E164_A, PNI_A, null - * - * Even though there was an update that will also result in the row (E164_B, null, null) - */ - private fun applyAndAssert(input: List, update: Update, output: Output): PnpMatchResult { - val ids = input.map { insert(it.e164, it.pni, it.aci) } - - input - .filter { it.pniSession } - .forEach { insertMockSessionFor(databaseRule.localAci, it.pni!!) } - - input - .filter { it.aciSession } - .forEach { insertMockSessionFor(databaseRule.localAci, it.aci!!) } - - val byE164 = update.e164?.let { db.getByE164(it).orElse(null) } - val byPniSid = update.pni?.let { db.getByServiceId(it).orElse(null) } - val byAciSid = update.aci?.let { db.getByServiceId(it).orElse(null) } - - val data = PnpDataSet( - e164 = update.e164, - pni = update.pni, - aci = update.aci, - byE164 = byE164, - byPniSid = byPniSid, - byPniOnly = update.pni?.let { db.getByPni(it).orElse(null) }, - byAciSid = byAciSid, - e164Record = byE164?.let { db.getRecord(it) }, - pniSidRecord = byPniSid?.let { db.getRecord(it) }, - aciSidRecord = byAciSid?.let { db.getRecord(it) } - ) - val changeSet = db.processPnpTupleToChangeSet(update.e164, update.pni, update.aci, pniVerified = update.pniVerified) - - val finalData = data.perform(changeSet.operations) - - val finalRecords = setOfNotNull(finalData.e164Record, finalData.pniSidRecord, finalData.aciSidRecord) - assertEquals("There's still multiple records in the resulting record set! $finalRecords", 1, finalRecords.size) - - finalRecords.firstOrNull { record -> record.e164 == output.e164 && record.pni == output.pni && record.serviceId == (output.aci ?: output.pni) } - ?: throw AssertionError("Expected output was not found in the result set! Expected: $output") - - return PnpMatchResult( - ids = ids, - changeSet = changeSet - ) - } - - companion object { - val ACI_A = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e")) - val ACI_B = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed")) - - val PNI_A = PNI.from(UUID.fromString("154b8d92-c960-4f6c-8385-671ad2ffb999")) - val PNI_B = PNI.from(UUID.fromString("ba92b1fb-cd55-40bf-adda-c35a85375533")) - - const val E164_A = "+12221234567" - const val E164_B = "+13331234567" - const val E164_C = "+14441234567" - } -} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt index d8a2fecb33..b482a30cfa 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/StorySendTableTest.kt @@ -65,17 +65,17 @@ class StorySendTableTest { messageId1 = MmsHelper.insert( recipient = distributionListRecipient1, - storyType = StoryType.STORY_WITHOUT_REPLIES, + storyType = StoryType.STORY_WITHOUT_REPLIES ) messageId2 = MmsHelper.insert( recipient = distributionListRecipient2, - storyType = StoryType.STORY_WITH_REPLIES, + storyType = StoryType.STORY_WITH_REPLIES ) messageId3 = MmsHelper.insert( recipient = distributionListRecipient3, - storyType = StoryType.STORY_WITHOUT_REPLIES, + storyType = StoryType.STORY_WITHOUT_REPLIES ) recipients6to15 = recipients1to10.takeLast(5) + recipients11to20.take(5) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt index feca767192..1f333ef0be 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/dependencies/InstrumentationApplicationDependencyProvider.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.dependencies import android.app.Application import okhttp3.ConnectionSpec +import okhttp3.WebSocketListener import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -15,15 +16,16 @@ import org.thoughtcrime.securesms.net.Network import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess import org.thoughtcrime.securesms.push.SignalServiceTrustStore import org.thoughtcrime.securesms.recipients.LiveRecipientCache +import org.thoughtcrime.securesms.testing.Get import org.thoughtcrime.securesms.testing.Verb import org.thoughtcrime.securesms.testing.runSync +import org.thoughtcrime.securesms.testing.success import org.thoughtcrime.securesms.util.Base64 import org.whispersystems.signalservice.api.KeyBackupService import org.whispersystems.signalservice.api.SignalServiceAccountManager import org.whispersystems.signalservice.api.push.TrustStore import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl -import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl @@ -48,6 +50,12 @@ class InstrumentationApplicationDependencyProvider(application: Application, def runSync { webServer = MockWebServer() baseUrl = webServer.url("").toString() + + addMockWebRequestHandlers( + Get("/v1/websocket/") { + MockResponse().success().withWebSocketUpgrade(object : WebSocketListener() {}) + } + ) } webServer.setDispatcher(object : Dispatcher() { @@ -66,15 +74,14 @@ class InstrumentationApplicationDependencyProvider(application: Application, def 0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), 2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)) ), - arrayOf(SignalContactDiscoveryUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), emptyList(), Network.socketFactory, + Network.proxySelectorForSocks, Network.dns, - Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS), - true + Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS) ) serviceNetworkAccessMock = mock { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt index cfab95f4b5..6b053b5bc1 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt @@ -69,7 +69,7 @@ class PreKeysSyncJobTest { Put("/v2/keys/signed?identity=pni") { r -> pniSignedPreKey = r.parsedRequestBody() MockResponse().success() - }, + } ) // WHEN @@ -107,7 +107,7 @@ class PreKeysSyncJobTest { InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) }, - Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) }, + Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) } ) // WHEN @@ -134,7 +134,7 @@ class PreKeysSyncJobTest { InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) }, - Put("/v2/keys/signed?identity=pni") { MockResponse().success() }, + Put("/v2/keys/signed?identity=pni") { MockResponse().success() } ) // WHEN @@ -173,7 +173,7 @@ class PreKeysSyncJobTest { Put("/v2/keys/?identity=pni") { r -> pniPreKeyStateRequest = r.parsedRequestBody() MockResponse().success() - }, + } ) // WHEN diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob__checkUsernameIsInSyncTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob__checkUsernameIsInSyncTest.kt new file mode 100644 index 0000000000..e79f804715 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob__checkUsernameIsInSyncTest.kt @@ -0,0 +1,175 @@ +package org.thoughtcrime.securesms.jobs + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import okhttp3.mockwebserver.MockResponse +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.signal.libsignal.usernames.Username +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.testing.Get +import org.thoughtcrime.securesms.testing.Put +import org.thoughtcrime.securesms.testing.SignalActivityRule +import org.thoughtcrime.securesms.testing.failure +import org.thoughtcrime.securesms.testing.success +import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse +import org.whispersystems.signalservice.internal.push.WhoAmIResponse +import org.whispersystems.util.Base64UrlSafe + +@Suppress("ClassName") +@RunWith(AndroidJUnit4::class) +class RefreshOwnProfileJob__checkUsernameIsInSyncTest { + + @get:Rule + val harness = SignalActivityRule() + + @After + fun tearDown() { + InstrumentationApplicationDependencyProvider.clearHandlers() + SignalStore.phoneNumberPrivacy().clearUsernameOutOfSync() + } + + @Test + fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() { + // WHEN + RefreshOwnProfileJob.checkUsernameIsInSync() + } + + @Test + fun givenLocalUsernameDoesNotMatchServerUsername_whenICheckUsernameIsInSync_thenIExpectRetry() { + // GIVEN + var didReserve = false + var didConfirm = false + val username = "hello.32" + val serverUsername = "hello.3232" + SignalDatabase.recipients.setUsername(harness.self.id, username) + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v1/accounts/whoami") { r -> + MockResponse().success( + WhoAmIResponse().apply { + usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(serverUsername)) + } + ) + }, + Put("/v1/accounts/username_hash/reserve") { r -> + didReserve = true + MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username)))) + }, + Put("/v1/accounts/username_hash/confirm") { r -> + didConfirm = true + MockResponse().success() + } + ) + + // WHEN + RefreshOwnProfileJob.checkUsernameIsInSync() + + // THEN + assertTrue(didReserve) + assertTrue(didConfirm) + assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync) + } + + @Test + fun givenLocalAndNoServer_whenICheckUsernameIsInSync_thenIExpectRetry() { + // GIVEN + var didReserve = false + var didConfirm = false + val username = "hello.32" + SignalDatabase.recipients.setUsername(harness.self.id, username) + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v1/accounts/whoami") { r -> + MockResponse().success(WhoAmIResponse()) + }, + Put("/v1/accounts/username_hash/reserve") { r -> + didReserve = true + MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username)))) + }, + Put("/v1/accounts/username_hash/confirm") { r -> + didConfirm = true + MockResponse().success() + } + ) + + // WHEN + RefreshOwnProfileJob.checkUsernameIsInSync() + + // THEN + assertTrue(didReserve) + assertTrue(didConfirm) + assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync) + } + + @Test + fun givenLocalAndServerMatch_whenICheckUsernameIsInSync_thenIExpectNoRetry() { + // GIVEN + var didReserve = false + var didConfirm = false + val username = "hello.32" + SignalDatabase.recipients.setUsername(harness.self.id, username) + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v1/accounts/whoami") { r -> + MockResponse().success( + WhoAmIResponse().apply { + usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username)) + } + ) + }, + Put("/v1/accounts/username_hash/reserve") { r -> + didReserve = true + MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username)))) + }, + Put("/v1/accounts/username_hash/confirm") { r -> + didConfirm = true + MockResponse().success() + } + ) + + // WHEN + RefreshOwnProfileJob.checkUsernameIsInSync() + + // THEN + assertFalse(didReserve) + assertFalse(didConfirm) + assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync) + } + + @Test + fun givenMismatchAndReservationFails_whenICheckUsernameIsInSync_thenIExpectNoConfirm() { + // GIVEN + var didReserve = false + var didConfirm = false + val username = "hello.32" + SignalDatabase.recipients.setUsername(harness.self.id, username) + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v1/accounts/whoami") { r -> + MockResponse().success( + WhoAmIResponse().apply { + usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash("${username}23")) + } + ) + }, + Put("/v1/accounts/username_hash/reserve") { r -> + didReserve = true + MockResponse().failure(418) + }, + Put("/v1/accounts/username_hash/confirm") { r -> + didConfirm = true + MockResponse().success() + } + ) + + // WHEN + RefreshOwnProfileJob.checkUsernameIsInSync() + + // THEN + assertTrue(didReserve) + assertFalse(didConfirm) + assertTrue(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync) + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt index 800c0bec5e..2e65da6bca 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessorTest.kt @@ -30,7 +30,7 @@ abstract class MessageContentProcessorTest { protected fun createNormalContentTestSubject(): MessageContentProcessor { val context = ApplicationProvider.getApplicationContext() - return MessageContentProcessor.forNormalContent(context) + return MessageContentProcessor.create(context) } /** diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt new file mode 100644 index 0000000000..c18e368d49 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageProcessingPerformanceTest.kt @@ -0,0 +1,163 @@ +package org.thoughtcrime.securesms.messages + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.signal.core.util.logging.Log +import org.signal.libsignal.protocol.ecc.Curve +import org.signal.libsignal.protocol.ecc.ECKeyPair +import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.testing.AliceClient +import org.thoughtcrime.securesms.testing.BobClient +import org.thoughtcrime.securesms.testing.Entry +import org.thoughtcrime.securesms.testing.FakeClientHelpers +import org.thoughtcrime.securesms.testing.SignalActivityRule +import org.thoughtcrime.securesms.testing.awaitFor +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds +import android.util.Log as AndroidLog + +/** + * Sends N messages from Bob to Alice to track performance of Alice's processing of messages. + */ +@Ignore("Ignore test in normal testing as it's a performance test with no assertions") +@RunWith(AndroidJUnit4::class) +class MessageProcessingPerformanceTest { + + companion object { + private val TAG = Log.tag(MessageProcessingPerformanceTest::class.java) + private val TIMING_TAG = "TIMING_$TAG".substring(0..23) + } + + @get:Rule + val harness = SignalActivityRule() + + private val trustRoot: ECKeyPair = Curve.generateKeyPair() + + @Before + fun setup() { + mockkStatic(UnidentifiedAccessUtil::class) + every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator + + mockkStatic(MessageContentProcessor::class) + every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application) + } + + @After + fun after() { + unmockkStatic(UnidentifiedAccessUtil::class) + unmockkStatic(MessageContentProcessor::class) + } + + @Test + fun testPerformance() { + val aliceClient = AliceClient( + serviceId = harness.self.requireServiceId(), + e164 = harness.self.requireE164(), + trustRoot = trustRoot + ) + + val bob = Recipient.resolved(harness.others[0]) + val bobClient = BobClient( + serviceId = bob.requireServiceId(), + e164 = bob.requireE164(), + identityKeyPair = harness.othersKeys[0], + trustRoot = trustRoot, + profileKey = ProfileKey(bob.profileKey) + ) + + // Send message from Bob to Alice (self) + + val firstPreKeyMessageTimestamp = System.currentTimeMillis() + val encryptedEnvelope = bobClient.encrypt(firstPreKeyMessageTimestamp) + + val aliceProcessFirstMessageLatch = harness + .inMemoryLogger + .getLockForUntil(TimingMessageContentProcessor.endTagPredicate(firstPreKeyMessageTimestamp)) + + Thread { aliceClient.process(encryptedEnvelope, System.currentTimeMillis()) }.start() + aliceProcessFirstMessageLatch.awaitFor(15.seconds) + + // Send message from Alice to Bob + val aliceNow = System.currentTimeMillis() + bobClient.decrypt(aliceClient.encrypt(aliceNow, bob), aliceNow) + + // Build N messages from Bob to Alice + + val messageCount = 100 + val envelopes = ArrayList(messageCount) + var now = System.currentTimeMillis() + for (i in 0..messageCount) { + envelopes += bobClient.encrypt(now) + now += 3 + } + + val firstTimestamp = envelopes.first().timestamp + val lastTimestamp = envelopes.last().timestamp + + // Alice processes N messages + + val aliceProcessLastMessageLatch = harness + .inMemoryLogger + .getLockForUntil(TimingMessageContentProcessor.endTagPredicate(lastTimestamp)) + + Thread { + for (envelope in envelopes) { + Log.i(TIMING_TAG, "Retrieved envelope! ${envelope.timestamp}") + aliceClient.process(envelope, envelope.timestamp) + } + }.start() + + // Wait for Alice to finish processing messages + aliceProcessLastMessageLatch.awaitFor(1.minutes) + harness.inMemoryLogger.flush() + + // Process logs for timing data + val entries = harness.inMemoryLogger.entries() + + // Calculate decryption average + + val decrypts = entries + .filter { it.tag == AliceClient.TAG } + .drop(1) + + val totalDecryptDuration = decrypts.sumOf { it.message!!.toLong() } + + AndroidLog.w(TAG, "Decryption: Average runtime: ${totalDecryptDuration.toFloat() / decrypts.size.toFloat()}ms") + + // Calculate MessageContentProcessor + + val takeLast: List = entries.filter { it.tag == TimingMessageContentProcessor.TAG }.drop(2) + val iterator = takeLast.iterator() + var processCount = 0L + var processDuration = 0L + while (iterator.hasNext()) { + val start = iterator.next() + val end = iterator.next() + processCount++ + processDuration += end.timestamp - start.timestamp + } + + AndroidLog.w(TAG, "MessageContentProcessor.process: Average runtime: ${processDuration.toFloat() / processCount.toFloat()}ms") + + // Calculate messages per second from "retrieving" first message post session initialization to processing last message + + val start = entries.first { it.message == "Retrieved envelope! $firstTimestamp" } + val end = entries.first { it.message == TimingMessageContentProcessor.endTag(lastTimestamp) } + + val duration = (end.timestamp - start.timestamp).toFloat() / 1000f + val messagePerSecond = messageCount.toFloat() / duration + + AndroidLog.w(TAG, "Processing $messageCount messages took ${duration}s or ${messagePerSecond}m/s") + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/TimingMessageContentProcessor.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/TimingMessageContentProcessor.kt new file mode 100644 index 0000000000..9fa27e7548 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/TimingMessageContentProcessor.kt @@ -0,0 +1,25 @@ +package org.thoughtcrime.securesms.messages + +import android.content.Context +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.testing.LogPredicate +import org.whispersystems.signalservice.api.messages.SignalServiceContent + +class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) { + companion object { + val TAG = Log.tag(TimingMessageContentProcessor::class.java) + + fun endTagPredicate(timestamp: Long): LogPredicate = { entry -> + entry.tag == TAG && entry.message == endTag(timestamp) + } + + private fun startTag(timestamp: Long) = "$timestamp start" + fun endTag(timestamp: Long) = "$timestamp end" + } + + override fun process(messageState: MessageState?, content: SignalServiceContent?, exceptionMetadata: ExceptionMetadata?, envelopeTimestamp: Long, smsMessageId: Long) { + Log.d(TAG, startTag(envelopeTimestamp)) + super.process(messageState, content, exceptionMetadata, envelopeTimestamp, smsMessageId) + Log.d(TAG, endTag(envelopeTimestamp)) + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragmentTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragmentTest.kt index 1804db2bbe..47de2aa06c 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragmentTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragmentTest.kt @@ -100,7 +100,7 @@ class UsernameEditFragmentTest { InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( Put("/v1/accounts/username/reserved") { - MockResponse().success(ReserveUsernameResponse(username, "reservationToken")) + MockResponse().success(ReserveUsernameResponse(username)) }, Put("/v1/accounts/username/confirm") { MockResponse().success() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt new file mode 100644 index 0000000000..e5919a777c --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt @@ -0,0 +1,127 @@ +package org.thoughtcrime.securesms.storage + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.signal.core.util.update +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.Base64 +import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.FeatureFlagsAccessor +import org.whispersystems.signalservice.api.push.ACI +import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.storage.SignalContactRecord +import org.whispersystems.signalservice.api.storage.StorageId +import org.whispersystems.signalservice.internal.storage.protos.ContactRecord +import java.util.UUID + +@RunWith(AndroidJUnit4::class) +class ContactRecordProcessorTest { + + @Before + fun setup() { + SignalStore.account().setE164(E164_SELF) + SignalStore.account().setAci(ACI_SELF) + SignalStore.account().setPni(PNI_SELF) + FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true) + } + + @Test + fun process_splitContact_normalSplit() { + // GIVEN + val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) + setStorageId(originalId, STORAGE_ID_A) + + val remote1 = buildRecord(STORAGE_ID_B) { + setServiceId(ACI_A.toString()) + setUnregisteredAtTimestamp(100) + } + + val remote2 = buildRecord(STORAGE_ID_C) { + setServiceId(PNI_A.toString()) + setServicePni(PNI_A.toString()) + setServiceE164(E164_A) + } + + // WHEN + val subject = ContactRecordProcessor() + subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR) + + // THEN + val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + + val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get() + + assertEquals(originalId, byAci) + assertEquals(byE164, byPni) + assertNotEquals(byAci, byE164) + } + + @Test + fun process_splitContact_doNotSplitIfAciRecordIsRegistered() { + // GIVEN + val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) + setStorageId(originalId, STORAGE_ID_A) + + val remote1 = buildRecord(STORAGE_ID_B) { + setServiceId(ACI_A.toString()) + setUnregisteredAtTimestamp(0) + } + + val remote2 = buildRecord(STORAGE_ID_C) { + setServiceId(PNI_A.toString()) + setServicePni(PNI_A.toString()) + setServiceE164(E164_A) + } + + // WHEN + val subject = ContactRecordProcessor() + subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR) + + // THEN + val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get() + val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get() + val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get() + + assertEquals(originalId, byAci) + assertEquals(byE164, byPni) + assertEquals(byAci, byE164) + } + + private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord { + return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build()) + } + + private fun setStorageId(recipientId: RecipientId, storageId: StorageId) { + SignalDatabase.rawDatabase + .update(RecipientTable.TABLE_NAME) + .values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw)) + .where("${RecipientTable.ID} = ?", recipientId) + .run() + } + + companion object { + val ACI_A = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e")) + val ACI_B = ACI.from(UUID.fromString("bbbb0000-0b60-4a68-9cd9-ed2f8453f9ed")) + val ACI_SELF = ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641")) + + val PNI_A = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999")) + val PNI_B = PNI.from(UUID.fromString("bbbb1111-cd55-40bf-adda-c35a85375533")) + val PNI_SELF = PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910")) + + const val E164_A = "+12222222222" + const val E164_B = "+13333333333" + const val E164_SELF = "+10000000000" + + val STORAGE_ID_A: StorageId = StorageId.forContact(byteArrayOf(1, 2, 3, 4)) + val STORAGE_ID_B: StorageId = StorageId.forContact(byteArrayOf(5, 6, 7, 8)) + val STORAGE_ID_C: StorageId = StorageId.forContact(byteArrayOf(9, 10, 11, 12)) + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt new file mode 100644 index 0000000000..5847e42995 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/AliceClient.kt @@ -0,0 +1,52 @@ +package org.thoughtcrime.securesms.testing + +import org.signal.core.util.logging.Log +import org.signal.libsignal.protocol.ecc.ECKeyPair +import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope + +/** + * Welcome to Alice's Client. + * + * Alice represent the Android instrumentation test user. Unlike [BobClient] much less is needed here + * as it can make use of the standard Signal Android App infrastructure. + */ +class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECKeyPair) { + + companion object { + val TAG = Log.tag(AliceClient::class.java) + } + + private val aliceSenderCertificate = FakeClientHelpers.createCertificateFor( + trustRoot = trustRoot, + uuid = serviceId.uuid(), + e164 = e164, + deviceId = 1, + identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey, + expires = 31337 + ) + + fun process(envelope: Envelope, serverDeliveredTimestamp: Long) { + val start = System.currentTimeMillis() + ApplicationDependencies.getIncomingMessageObserver().processEnvelope(envelope, serverDeliveredTimestamp) + val end = System.currentTimeMillis() + Log.d(TAG, "${end - start}") + } + + fun encrypt(now: Long, destination: Recipient): Envelope { + return ApplicationDependencies.getSignalServiceMessageSender().getEncryptedMessage( + SignalServiceAddress(destination.requireServiceId(), destination.requireE164()), + FakeClientHelpers.getTargetUnidentifiedAccess(ProfileKeyUtil.getSelfProfileKey(), ProfileKey(destination.profileKey), aliceSenderCertificate), + 1, + FakeClientHelpers.encryptedTextMessage(now), + false + ).toEnvelope(now, destination.requireServiceId()) + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt new file mode 100644 index 0000000000..fe69354744 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/BobClient.kt @@ -0,0 +1,167 @@ +package org.thoughtcrime.securesms.testing + +import org.signal.core.util.readToSingleInt +import org.signal.core.util.select +import org.signal.libsignal.protocol.IdentityKey +import org.signal.libsignal.protocol.IdentityKeyPair +import org.signal.libsignal.protocol.SessionBuilder +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.signal.libsignal.protocol.ecc.ECKeyPair +import org.signal.libsignal.protocol.groups.state.SenderKeyRecord +import org.signal.libsignal.protocol.state.IdentityKeyStore +import org.signal.libsignal.protocol.state.PreKeyBundle +import org.signal.libsignal.protocol.state.PreKeyRecord +import org.signal.libsignal.protocol.state.SessionRecord +import org.signal.libsignal.protocol.state.SignedPreKeyRecord +import org.signal.libsignal.protocol.util.KeyHelper +import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil +import org.thoughtcrime.securesms.database.OneTimePreKeyTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.SignedPreKeyTable +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope +import org.whispersystems.signalservice.api.SignalServiceAccountDataStore +import org.whispersystems.signalservice.api.SignalSessionLock +import org.whispersystems.signalservice.api.crypto.SignalServiceCipher +import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess +import org.whispersystems.signalservice.api.push.DistributionId +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.push.SignalServiceProtos +import java.util.Optional +import java.util.UUID +import java.util.concurrent.locks.ReentrantLock + +/** + * Welcome to Bob's Client. + * + * Bob is a "fake" client that can start a session with the Android instrumentation test user (Alice). + * + * Bob can create a new session using a prekey bundle created from Alice's prekeys, send a message, decrypt + * a return message from Alice, and that'll start a standard Signal session with normal keys/ratcheting. + */ +class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair: IdentityKeyPair, val trustRoot: ECKeyPair, val profileKey: ProfileKey) { + + private val serviceAddress = SignalServiceAddress(serviceId, e164) + private val registrationId = KeyHelper.generateRegistrationId(false) + private val aciStore = BobSignalServiceAccountDataStore(registrationId, identityKeyPair) + private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.uuid(), e164, 1, identityKeyPair.publicKey.publicKey, 31337) + private val sessionLock = object : SignalSessionLock { + private val lock = ReentrantLock() + + override fun acquire(): SignalSessionLock.Lock { + lock.lock() + return SignalSessionLock.Lock { lock.unlock() } + } + } + + /** Inspired by SignalServiceMessageSender#getEncryptedMessage */ + fun encrypt(now: Long): SignalServiceProtos.Envelope { + val envelopeContent = FakeClientHelpers.encryptedTextMessage(now) + + val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null) + + if (!aciStore.containsSession(getAliceProtocolAddress())) { + val sessionBuilder = SignalSessionBuilder(sessionLock, SessionBuilder(aciStore, getAliceProtocolAddress())) + sessionBuilder.process(getAlicePreKeyBundle()) + } + + return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent) + .toEnvelope(envelopeContent.content.get().dataMessage.timestamp, getAliceServiceId()) + } + + fun decrypt(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) { + val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator()) + cipher.decrypt(envelope, serverDeliveredTimestamp) + } + + private fun getAliceServiceId(): ServiceId { + return SignalStore.account().requireAci() + } + + private fun getAlicePreKeyBundle(): PreKeyBundle { + val selfPreKeyId = SignalDatabase.rawDatabase + .select(OneTimePreKeyTable.KEY_ID) + .from(OneTimePreKeyTable.TABLE_NAME) + .where("${OneTimePreKeyTable.ACCOUNT_ID} = ?", getAliceServiceId().toString()) + .run() + .readToSingleInt(-1) + + val selfPreKeyRecord = SignalDatabase.oneTimePreKeys.get(getAliceServiceId(), selfPreKeyId)!! + + val selfSignedPreKeyId = SignalDatabase.rawDatabase + .select(SignedPreKeyTable.KEY_ID) + .from(SignedPreKeyTable.TABLE_NAME) + .where("${SignedPreKeyTable.ACCOUNT_ID} = ?", getAliceServiceId().toString()) + .run() + .readToSingleInt(-1) + + val selfSignedPreKeyRecord = SignalDatabase.signedPreKeys.get(getAliceServiceId(), selfSignedPreKeyId)!! + + return PreKeyBundle( + SignalStore.account().registrationId, + 1, + selfPreKeyId, + selfPreKeyRecord.keyPair.publicKey, + selfSignedPreKeyId, + selfSignedPreKeyRecord.keyPair.publicKey, + selfSignedPreKeyRecord.signature, + getAlicePublicKey() + ) + } + + private fun getAliceProtocolAddress(): SignalProtocolAddress { + return SignalProtocolAddress(SignalStore.account().requireAci().toString(), 1) + } + + private fun getAlicePublicKey(): IdentityKey { + return SignalStore.account().aciIdentityKey.publicKey + } + + private fun getAliceProfileKey(): ProfileKey { + return ProfileKeyUtil.getSelfProfileKey() + } + + private fun getAliceUnidentifiedAccess(): Optional { + return FakeClientHelpers.getTargetUnidentifiedAccess(profileKey, getAliceProfileKey(), senderCertificate) + } + + private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore { + private var aliceSessionRecord: SessionRecord? = null + + override fun getIdentityKeyPair(): IdentityKeyPair = identityKeyPair + + override fun getLocalRegistrationId(): Int = registrationId + override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean = true + override fun loadSession(address: SignalProtocolAddress?): SessionRecord = aliceSessionRecord ?: SessionRecord() + override fun saveIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?): Boolean = false + override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) { aliceSessionRecord = record } + override fun getSubDeviceSessions(name: String?): List = emptyList() + override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null + override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey + + override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException() + override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException() + override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException() + override fun removePreKey(preKeyId: Int) = throw UnsupportedOperationException() + override fun loadExistingSessions(addresses: MutableList?): MutableList = throw UnsupportedOperationException() + override fun deleteSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException() + override fun deleteAllSessions(name: String?) = throw UnsupportedOperationException() + override fun loadSignedPreKey(signedPreKeyId: Int): SignedPreKeyRecord = throw UnsupportedOperationException() + override fun loadSignedPreKeys(): MutableList = throw UnsupportedOperationException() + override fun storeSignedPreKey(signedPreKeyId: Int, record: SignedPreKeyRecord?) = throw UnsupportedOperationException() + override fun containsSignedPreKey(signedPreKeyId: Int): Boolean = throw UnsupportedOperationException() + override fun removeSignedPreKey(signedPreKeyId: Int) = throw UnsupportedOperationException() + override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = throw UnsupportedOperationException() + override fun loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException() + override fun archiveSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException() + override fun getAllAddressesWithActiveSessions(addressNames: MutableList?): MutableSet = throw UnsupportedOperationException() + override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet = throw UnsupportedOperationException() + override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection?) = throw UnsupportedOperationException() + override fun clearSenderKeySharedWith(addresses: MutableCollection?) = throw UnsupportedOperationException() + override fun isMultiDevice(): Boolean = throw UnsupportedOperationException() + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt new file mode 100644 index 0000000000..6a04e1f504 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/FakeClientHelpers.kt @@ -0,0 +1,79 @@ +package org.thoughtcrime.securesms.testing + +import org.signal.libsignal.internal.Native +import org.signal.libsignal.internal.NativeHandleGuard +import org.signal.libsignal.metadata.certificate.CertificateValidator +import org.signal.libsignal.metadata.certificate.SenderCertificate +import org.signal.libsignal.metadata.certificate.ServerCertificate +import org.signal.libsignal.protocol.ecc.Curve +import org.signal.libsignal.protocol.ecc.ECKeyPair +import org.signal.libsignal.protocol.ecc.ECPublicKey +import org.signal.libsignal.zkgroup.profiles.ProfileKey +import org.thoughtcrime.securesms.database.model.toProtoByteString +import org.whispersystems.signalservice.api.crypto.ContentHint +import org.whispersystems.signalservice.api.crypto.EnvelopeContent +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.internal.push.OutgoingPushMessage +import org.whispersystems.signalservice.internal.push.SignalServiceProtos +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope +import org.whispersystems.util.Base64 +import java.util.Optional +import java.util.UUID + +object FakeClientHelpers { + + val noOpCertificateValidator = object : CertificateValidator(null) { + override fun validate(certificate: SenderCertificate, validationTime: Long) = Unit + } + + fun createCertificateFor(trustRoot: ECKeyPair, uuid: UUID, e164: String, deviceId: Int, identityKey: ECPublicKey, expires: Long): SenderCertificate { + val serverKey: ECKeyPair = Curve.generateKeyPair() + NativeHandleGuard(serverKey.publicKey).use { serverPublicGuard -> + NativeHandleGuard(trustRoot.privateKey).use { trustRootPrivateGuard -> + val serverCertificate = ServerCertificate(Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle())) + NativeHandleGuard(identityKey).use { identityGuard -> + NativeHandleGuard(serverCertificate).use { serverCertificateGuard -> + NativeHandleGuard(serverKey.privateKey).use { serverPrivateGuard -> + return SenderCertificate(Native.SenderCertificate_New(uuid.toString(), e164, deviceId, identityGuard.nativeHandle(), expires, serverCertificateGuard.nativeHandle(), serverPrivateGuard.nativeHandle())) + } + } + } + } + } + } + + fun getTargetUnidentifiedAccess(myProfileKey: ProfileKey, theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): Optional { + val selfUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(myProfileKey) + val themUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey) + + return UnidentifiedAccessPair(UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate.serialized), UnidentifiedAccess(themUnidentifiedAccessKey, senderCertificate.serialized)).targetUnidentifiedAccess + } + + fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent { + val content = SignalServiceProtos.Content.newBuilder().apply { + setDataMessage( + SignalServiceProtos.DataMessage.newBuilder().apply { + body = message + timestamp = now + } + ) + } + return EnvelopeContent.encrypted(content.build(), ContentHint.RESENDABLE, Optional.empty()) + } + + fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope { + return Envelope.newBuilder() + .setType(Envelope.Type.valueOf(this.type)) + .setSourceDevice(1) + .setTimestamp(timestamp) + .setServerTimestamp(timestamp + 1) + .setDestinationUuid(destination.toString()) + .setServerGuid(UUID.randomUUID().toString()) + .setContent(Base64.decode(this.content).toProtoByteString()) + .setUrgent(true) + .setStory(false) + .build() + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InMemoryLogger.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InMemoryLogger.kt new file mode 100644 index 0000000000..efe6a54ea8 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/InMemoryLogger.kt @@ -0,0 +1,88 @@ +package org.thoughtcrime.securesms.testing + +import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.logging.Log +import java.util.concurrent.CountDownLatch + +typealias LogPredicate = (Entry) -> Boolean + +/** + * Logging implementation that holds logs in memory as they are added to be retrieve at a later time by a test. + * Can also be used for multithreaded synchronization and waiting until certain logs are emitted before continuing + * a test. + */ +class InMemoryLogger : Log.Logger() { + + private val executor = SignalExecutors.newCachedSingleThreadExecutor("inmemory-logger") + private val predicates = mutableListOf() + private val logEntries = mutableListOf() + + override fun v(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) = add(Verbose(tag, message, t, System.currentTimeMillis())) + override fun d(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) = add(Debug(tag, message, t, System.currentTimeMillis())) + override fun i(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) = add(Info(tag, message, t, System.currentTimeMillis())) + override fun w(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) = add(Warn(tag, message, t, System.currentTimeMillis())) + override fun e(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) = add(Error(tag, message, t, System.currentTimeMillis())) + + override fun flush() { + val latch = CountDownLatch(1) + executor.execute { latch.countDown() } + latch.await() + } + + override fun clear() = Unit + + private fun add(entry: Entry) { + executor.execute { + logEntries += entry + + val iterator = predicates.iterator() + while (iterator.hasNext()) { + val predicate = iterator.next() + if (predicate(entry)) { + iterator.remove() + } + } + } + } + + /** Blocks until a snapshot of all log entries can be taken in a thread-safe way. */ + fun entries(): List { + val latch = CountDownLatch(1) + var entries: List = emptyList() + executor.execute { + entries = logEntries.toList() + latch.countDown() + } + latch.await() + return entries + } + + /** Returns a countdown latch that'll fire at a future point when an [Entry] is received that matches the predicate. */ + fun getLockForUntil(predicate: LogPredicate): CountDownLatch { + val latch = CountDownLatch(1) + executor.execute { + predicates += { entry -> + if (predicate(entry)) { + latch.countDown() + true + } else { + false + } + } + } + return latch + } +} + +sealed interface Entry { + val tag: String + val message: String? + val throwable: Throwable? + val timestamp: Long +} + +data class Verbose(override val tag: String, override val message: String?, override val throwable: Throwable?, override val timestamp: Long) : Entry +data class Debug(override val tag: String, override val message: String?, override val throwable: Throwable?, override val timestamp: Long) : Entry +data class Info(override val tag: String, override val message: String?, override val throwable: Throwable?, override val timestamp: Long) : Entry +data class Warn(override val tag: String, override val message: String?, override val throwable: Throwable?, override val timestamp: Long) : Entry +data class Error(override val tag: String, override val message: String?, override val throwable: Throwable?, override val timestamp: Long) : Entry diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt index a3c5bf8f18..c638fbbf5c 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/MockProvider.kt @@ -32,6 +32,7 @@ import org.whispersystems.signalservice.internal.push.PreKeyEntity import org.whispersystems.signalservice.internal.push.PreKeyResponse import org.whispersystems.signalservice.internal.push.PreKeyResponseItem import org.whispersystems.signalservice.internal.push.PushServiceSocket +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson import org.whispersystems.signalservice.internal.push.SenderCertificate import org.whispersystems.signalservice.internal.push.VerifyAccountResponse import org.whispersystems.signalservice.internal.push.WhoAmIResponse @@ -56,6 +57,16 @@ object MockProvider { ) } + val sessionMetadataJson = RegistrationSessionMetadataJson( + id = "asdfasdfasdfasdf", + nextCall = null, + nextSms = null, + nextVerificationAttempt = null, + allowedToRequestCode = true, + requestedInformation = emptyList(), + verified = true + ) + fun createVerifyAccountResponse(aci: ServiceId, newPni: ServiceId): VerifyAccountResponse { return VerifyAccountResponse().apply { uuid = aci.toString() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt index 20268a8bee..dfe45effec 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/ResponseMocking.kt @@ -17,6 +17,8 @@ class Get(path: String, responseFactory: ResponseFactory) : Verb("GET", path, re class Put(path: String, responseFactory: ResponseFactory) : Verb("PUT", path, responseFactory) +class Post(path: String, responseFactory: ResponseFactory) : Verb("POST", path, responseFactory) + fun MockResponse.success(response: Any? = null): MockResponse { return setResponseCode(200).apply { if (response != null) { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/RxTestSchedulerRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/RxTestSchedulerRule.kt index 26036889ad..c5c84f141e 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/RxTestSchedulerRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/RxTestSchedulerRule.kt @@ -13,7 +13,7 @@ class RxTestSchedulerRule( val ioTestScheduler: TestScheduler = defaultTestScheduler, val computationTestScheduler: TestScheduler = defaultTestScheduler, val singleTestScheduler: TestScheduler = defaultTestScheduler, - val newThreadTestScheduler: TestScheduler = defaultTestScheduler, + val newThreadTestScheduler: TestScheduler = defaultTestScheduler ) : ExternalResource() { override fun before() { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt index 24ea46ad72..3cded142d4 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt @@ -9,7 +9,9 @@ import androidx.test.platform.app.InstrumentationRegistry import okhttp3.mockwebserver.MockResponse import org.junit.rules.ExternalResource import org.signal.libsignal.protocol.IdentityKey +import org.signal.libsignal.protocol.IdentityKeyPair import org.signal.libsignal.protocol.SignalProtocolAddress +import org.thoughtcrime.securesms.SignalInstrumentationApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.database.IdentityTable @@ -17,7 +19,6 @@ import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId @@ -52,18 +53,23 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() private set lateinit var others: List private set + lateinit var othersKeys: List + + val inMemoryLogger: InMemoryLogger + get() = (application as SignalInstrumentationApplicationContext).inMemoryLogger override fun before() { context = InstrumentationRegistry.getInstrumentation().targetContext self = setupSelf() - others = setupOthers() + + val setupOthers = setupOthers() + others = setupOthers.first + othersKeys = setupOthers.second InstrumentationApplicationDependencyProvider.clearHandlers() } private fun setupSelf(): Recipient { - DeviceTransferBlockingInterceptor.getInstance().blockNetwork() - ApplicationDependencies.getNetworkManager().isNetworkEnabled = true SecurePreferenceManager.getSecurePreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit() @@ -80,22 +86,25 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() registrationId = registrationRepository.registrationId, profileKey = registrationRepository.getProfileKey("+15555550101"), fcmToken = null, - pniRegistrationId = registrationRepository.pniRegistrationId + pniRegistrationId = registrationRepository.pniRegistrationId, + recoveryPassword = "asdfasdfasdfasdf" ), - VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null) + VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null), + false ).blockingGet() ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow SignalStore.kbsValues().optOut() - RegistrationUtil.maybeMarkRegistrationComplete(application) + RegistrationUtil.maybeMarkRegistrationComplete() SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) return Recipient.self() } - private fun setupOthers(): List { + private fun setupOthers(): Pair, List> { val others = mutableListOf() + val othersKeys = mutableListOf() if (othersCount !in 0 until 1000) { throw IllegalArgumentException("$othersCount must be between 0 and 1000") @@ -109,11 +118,13 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true)) SignalDatabase.recipients.setProfileSharing(recipientId, true) SignalDatabase.recipients.markRegistered(recipientId, aci) - ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey) + val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair() + ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey) others += recipientId + othersKeys += otherIdentity } - return others + return others to othersKeys } inline fun launchActivity(initIntent: Intent.() -> Unit = {}): ActivityScenario { diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt index 5d2efabcd5..928ec9e662 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestProtos.kt @@ -21,7 +21,7 @@ class TestProtos private constructor() { } fun metadata( - address: AddressProto = address().build(), + address: AddressProto = address().build() ): MetadataProto.Builder { return MetadataProto.newBuilder() .setAddress(address) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt index 0604ec82a1..4b787f09ed 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/TestUtils.kt @@ -7,6 +7,9 @@ import org.hamcrest.Matchers.not import org.hamcrest.Matchers.notNullValue import org.hamcrest.Matchers.nullValue import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import kotlin.time.Duration /** * Run the given [runnable] on a new thread and wait for it to finish. @@ -44,3 +47,9 @@ infix fun T.assertIsNot(expected: T) { infix fun > T.assertIsSize(expected: Int) { assertThat(this, hasSize(expected)) } + +fun CountDownLatch.awaitFor(duration: Duration) { + if (!await(duration.inWholeMilliseconds, TimeUnit.MILLISECONDS)) { + throw TimeoutException("Latch await took longer than ${duration.inWholeMilliseconds}ms") + } +} diff --git a/app/src/gms/AndroidManifest.xml b/app/src/gms/AndroidManifest.xml index 9b1bd9193e..b7129ff375 100644 --- a/app/src/gms/AndroidManifest.xml +++ b/app/src/gms/AndroidManifest.xml @@ -1,23 +1,26 @@ - + - + - + - + - + diff --git a/app/src/instrumentation/AndroidManifest.xml b/app/src/instrumentation/AndroidManifest.xml index 6c9103481d..93821ec6c5 100644 --- a/app/src/instrumentation/AndroidManifest.xml +++ b/app/src/instrumentation/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> + xmlns:tools="http://schemas.android.com/tools"> @@ -416,7 +415,7 @@ @@ -625,6 +624,7 @@ + diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 0426c0a84f..3d8395ed13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -67,6 +67,7 @@ import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; +import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob; import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob; @@ -76,10 +77,10 @@ import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver; import org.thoughtcrime.securesms.migrations.ApplicationMigrations; +import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.SignalGlideComponents; import org.thoughtcrime.securesms.mms.SignalGlideModule; import org.thoughtcrime.securesms.net.NetworkManager; -import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.ratelimit.RateLimitUtil; import org.thoughtcrime.securesms.recipients.Recipient; @@ -109,7 +110,8 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.security.Security; -import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; @@ -130,6 +132,9 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr private static final String TAG = Log.tag(ApplicationContext.class); + @VisibleForTesting + protected PersistentLogger persistentLogger; + private static ApplicationContext instance; public ApplicationContext() { @@ -185,13 +190,14 @@ private void onCreateUnlock() { .addBlocking("first-launch", this::initializeFirstEverAppLaunch) .addBlocking("gcm-check", this::initializeFcmCheck) .addBlocking("app-migrations", this::initializeApplicationMigrations) - .addBlocking("ring-rtc", this::initializeRingRtc) - .addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this)) + .addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete()) .addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this)) .addBlocking("message-retriever", this::initializeMessageRetrieval) .addBlocking("blob-provider", this::initializeBlobProvider) .addBlocking("feature-flags", FeatureFlags::init) + .addBlocking("ring-rtc", this::initializeRingRtc) .addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents())) + .addNonBlocking(() -> GlideApp.get(this)) .addNonBlocking(this::cleanAvatarStorage) .addNonBlocking(this::initializeRevealableMessageManager) .addNonBlocking(this::initializePendingRetryReceiptManager) @@ -211,6 +217,7 @@ private void onCreateUnlock() { .addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this)) .addPostRender(this::initializeExpiringMessageManager) .addPostRender(this::initializeTrimThreadsByDateManager) + .addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary) .addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this)) .addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary) .addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge())) @@ -223,6 +230,8 @@ private void onCreateUnlock() { .addPostRender(StoryOnboardingDownloadJob.Companion::enqueueIfNeeded) .addPostRender(PnpInitializeDevicesJob::enqueueIfNecessary) .addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved()) + .addPostRender(() -> SignalDatabase.groupCallRings().removeOldRings()) + .addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp()) .execute(); Log.d(TAG, "onCreateUnlock() took " + (System.currentTimeMillis() - startTime) + " ms"); @@ -248,7 +257,6 @@ private void onStartUnlock() { SignalExecutors.BOUNDED.execute(() -> { FeatureFlags.refreshIfNecessary(); - ApplicationDependencies.getRecipientCache().warmUp(); RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this); executePendingContactSync(); checkBuildExpiration(); @@ -312,6 +320,10 @@ public void onLock() { }, TimeUnit.SECONDS.toMillis(1)); } + public PersistentLogger getPersistentLogger() { + return persistentLogger; + } + public void checkBuildExpiration() { if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) { Log.w(TAG, "Build expired!"); @@ -343,8 +355,9 @@ private void initializeSecurityProvider() { } } - private void initializeLogging() { - PersistentLogger persistentLogger = new PersistentLogger(this); + @VisibleForTesting + protected void initializeLogging() { + persistentLogger = new PersistentLogger(this); Log.setInternalCheck(FeatureFlags::internalUser); Log.setPersistentLogger(persistentLogger); Log.setLogging(TextSecurePreferences.isLogEnabled(this)); @@ -522,7 +535,11 @@ private void initializePeriodicTasks() { private void initializeRingRtc() { try { - CallManager.initialize(this, new RingRtcLogger()); + Map fieldTrials = new HashMap<>(); + if (FeatureFlags.callingFieldTrialAnyAddressPortsKillSwitch()) { + fieldTrials.put("RingRTC-AnyAddressPortsKillSwitch", "Enabled"); + } + CallManager.initialize(this, new RingRtcLogger(), fieldTrials); } catch (UnsatisfiedLinkError e) { throw new AssertionError("Unable to load ringrtc library", e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index 700d53045a..a4a176e60e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -10,10 +10,11 @@ import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState; import org.thoughtcrime.securesms.contactshare.Contact; +import org.thoughtcrime.securesms.conversation.ConversationItem; +import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode; import org.thoughtcrime.securesms.conversation.ConversationMessage; import org.thoughtcrime.securesms.conversation.colors.Colorizable; import org.thoughtcrime.securesms.conversation.colors.Colorizer; -import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart; import org.thoughtcrime.securesms.conversation.mutiselect.Multiselectable; import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord; @@ -23,6 +24,7 @@ import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.linkpreview.LinkPreview; +import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -110,5 +112,7 @@ interface EventListener { /** @return true if handled, false if you want to let the normal url handling continue */ boolean onUrlClicked(@NonNull String url); + + void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/BiometricDeviceAuthentication.kt b/app/src/main/java/org/thoughtcrime/securesms/BiometricDeviceAuthentication.kt index e81544d0da..ffc4363ba7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BiometricDeviceAuthentication.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/BiometricDeviceAuthentication.kt @@ -25,6 +25,14 @@ class BiometricDeviceAuthentication( const val TAG: String = "BiometricDeviceAuth" const val BIOMETRIC_AUTHENTICATORS = BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.BIOMETRIC_WEAK const val ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS or BiometricManager.Authenticators.DEVICE_CREDENTIAL + + /** + * From the docs on [BiometricManager.canAuthenticate] + * + * > Note that not all combinations of authenticator types are supported prior to Android 11 (API 30). Specifically, DEVICE_CREDENTIAL alone is unsupported + * > prior to API 30, and BIOMETRIC_STRONG | DEVICE_CREDENTIAL is unsupported on API 28-29. + */ + private val DISALLOWED_BIOMETRIC_VERSIONS = setOf(28, 29) } fun authenticate(context: Context, force: Boolean, showConfirmDeviceCredentialIntent: () -> Unit): Boolean { @@ -35,7 +43,7 @@ class BiometricDeviceAuthentication( return false } - return if (Build.VERSION.SDK_INT != 29 && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) { + return if (!DISALLOWED_BIOMETRIC_VERSIONS.contains(Build.VERSION.SDK_INT) && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) { if (force) { Log.i(TAG, "Listening for biometric authentication...") biometricPrompt.authenticate(biometricPromptInfo) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListAdapter.kt index a0e924b9cd..afc44734ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListAdapter.kt @@ -5,19 +5,21 @@ import android.view.View import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration import org.thoughtcrime.securesms.contacts.paged.ContactSearchData +import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder class ContactSelectionListAdapter( context: Context, + fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: DisplaySmsTag, - displayPhoneNumber: DisplayPhoneNumber, + displaySecondaryInformation: DisplaySecondaryInformation, onClickCallbacks: OnContactSelectionClick, longClickCallbacks: LongClickCallbacks, storyContextMenuCallbacks: StoryContextMenuCallbacks -) : ContactSearchAdapter(context, emptySet(), displayCheckBox, displaySmsTag, displayPhoneNumber, onClickCallbacks, longClickCallbacks, storyContextMenuCallbacks) { +) : ContactSearchAdapter(context, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, onClickCallbacks, longClickCallbacks, storyContextMenuCallbacks) { init { registerFactory(NewGroupModel::class.java, LayoutFactory({ NewGroupViewHolder(it, onClickCallbacks::onNewGroupClicked) }, R.layout.contact_selection_new_group_item)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java index d953be6fb3..39b285d8a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -84,6 +84,7 @@ import java.io.IOException; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -338,7 +339,7 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { selectionLimit, isMulti, ContactSearchAdapter.DisplaySmsTag.DEFAULT, - ContactSearchAdapter.DisplayPhoneNumber.ALWAYS, + ContactSearchAdapter.DisplaySecondaryInformation.ALWAYS, this::mapStateToConfiguration, new ContactSearchMediator.SimpleCallbacks() { @Override @@ -347,11 +348,12 @@ public void onAdapterListCommitted(int size) { } }, false, - (context, fixedContacts, displayCheckBox, displaySmsTag, displayPhoneNumber, callbacks, longClickCallbacks, storyContextMenuCallbacks) -> new ContactSelectionListAdapter( + (context, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, callbacks, longClickCallbacks, storyContextMenuCallbacks) -> new ContactSelectionListAdapter( context, + fixedContacts, displayCheckBox, displaySmsTag, - displayPhoneNumber, + displaySecondaryInformation, new ContactSelectionListAdapter.OnContactSelectionClick() { @Override public void onNewGroupClicked() { @@ -441,7 +443,7 @@ private Set getCurrentSelection() { } return currentSelection == null ? Collections.emptySet() - : Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet())); + : Collections.unmodifiableSet(new HashSet<>(currentSelection)); } public boolean isMulti() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.java index c52df410ce..f910dc57fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.os.Bundle; import android.view.View; +import android.view.ViewTreeObserver; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,6 +15,7 @@ import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner; +import org.thoughtcrime.securesms.conversationlist.RelinkDevicesReminderBottomSheetFragment; import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.stories.Stories; @@ -37,6 +39,8 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot private VoiceNoteMediaController mediaController; private ConversationListTabsViewModel conversationListTabsViewModel; + private boolean onFirstRender = false; + public static @NonNull Intent clearTop(@NonNull Context context) { Intent intent = new Intent(context, MainActivity.class); @@ -53,8 +57,23 @@ protected void onCreate(Bundle savedInstanceState, boolean ready) { super.onCreate(savedInstanceState, ready); setContentView(R.layout.main_activity); - - mediaController = new VoiceNoteMediaController(this); + final View content = findViewById(android.R.id.content); + content.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + // Use pre draw listener to delay drawing frames till conversation list is ready + if (onFirstRender) { + content.getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } else { + return false; + } + } + }); + + + mediaController = new VoiceNoteMediaController(this, true); ConversationListTabRepository repository = new ConversationListTabRepository(); ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository); @@ -96,6 +115,11 @@ protected void onResume() { OldDeviceTransferLockedDialog.show(getSupportFragmentManager()); } + if (SignalStore.misc().getShouldShowLinkedDevicesReminder()) { + SignalStore.misc().setShouldShowLinkedDevicesReminder(false); + RelinkDevicesReminderBottomSheetFragment.show(getSupportFragmentManager()); + } + updateTabVisibility(); } @@ -149,6 +173,10 @@ private void handleSignalMeIntent(Intent intent) { } } + public void onFirstRender() { + onFirstRender = true; + } + @Override public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() { return mediaController; diff --git a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java index 865d3d83f4..2ab117333f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java @@ -40,28 +40,22 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.components.menu.ActionItem; import org.thoughtcrime.securesms.components.menu.SignalContextMenu; -import org.thoughtcrime.securesms.contacts.ContactSelectionListItem; import org.thoughtcrime.securesms.contacts.management.ContactsManagementRepository; import org.thoughtcrime.securesms.contacts.management.ContactsManagementViewModel; -import org.thoughtcrime.securesms.contacts.paged.ContactSearchData; import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery; import org.thoughtcrime.securesms.conversation.ConversationIntents; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.LifecycleDisposable; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -179,22 +173,23 @@ private void launch(Recipient recipient) { public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); - switch (item.getItemId()) { - case android.R.id.home: - super.onBackPressed(); - return true; - case R.id.menu_refresh: - handleManualRefresh(); - return true; - case R.id.menu_new_group: - handleCreateGroup(); - return true; - case R.id.menu_invite: - handleInvite(); - return true; + int itemId = item.getItemId(); + + if (itemId == android.R.id.home) { + super.onBackPressed(); + return true; + } else if (itemId == R.id.menu_refresh) { + handleManualRefresh(); + return true; + } else if (itemId == R.id.menu_new_group) { + handleCreateGroup(); + return true; + } else if (itemId == R.id.menu_invite) { + handleInvite(); + return true; + } else { + return false; } - - return false; } private void handleManualRefresh() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index 24055422ff..148b14427a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -20,20 +20,17 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; -import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity; import org.thoughtcrime.securesms.migrations.ApplicationMigrations; import org.thoughtcrime.securesms.pin.PinRestoreActivity; import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; -import org.thoughtcrime.securesms.profiles.username.AddAUsernameActivity; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.AppStartup; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.Locale; @@ -55,7 +52,6 @@ public abstract class PassphraseRequiredActivity extends PassphraseActivity impl private static final int STATE_TRANSFER_ONGOING = 8; private static final int STATE_TRANSFER_LOCKED = 9; private static final int STATE_CHANGE_NUMBER_LOCK = 10; - private static final int STATE_CREATE_USERNAME = 11; private SignalServiceNetworkAccess networkAccess; private BroadcastReceiver clearKeyReceiver; @@ -164,7 +160,6 @@ private Intent getIntentForState(int state) { case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent(); case STATE_TRANSFER_LOCKED: return getOldDeviceTransferLockedIntent(); case STATE_CHANGE_NUMBER_LOCK: return getChangeNumberLockIntent(); - case STATE_CREATE_USERNAME: return getCreateUsernameIntent(); default: return null; } } @@ -184,8 +179,6 @@ private int getApplicationState(boolean locked) { return STATE_CREATE_SIGNAL_PIN; } else if (userMustSetProfileName()) { return STATE_CREATE_PROFILE_NAME; - } else if (shouldAskUserToCreateUsername()) { - return STATE_CREATE_USERNAME; } else if (userMustCreateSignalPin()) { return STATE_CREATE_SIGNAL_PIN; } else if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null && getClass() != OldDeviceTransferActivity.class) { @@ -211,13 +204,6 @@ private boolean userMustSetProfileName() { return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName().isEmpty(); } - private boolean shouldAskUserToCreateUsername() { - return FeatureFlags.usernames() && - FeatureFlags.phoneNumberPrivacy() && - !SignalStore.uiHints().hasSetOrSkippedUsernameCreation() && - SignalStore.phoneNumberPrivacy().getPhoneNumberListingMode() == PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED; - } - private Intent getCreatePassphraseIntent() { return getRoutedIntent(PassphraseCreateActivity.class, getIntent()); } @@ -272,10 +258,6 @@ private Intent getChangeNumberLockIntent() { return ChangeNumberLockActivity.createIntent(this); } - private Intent getCreateUsernameIntent() { - return getRoutedIntent(AddAUsernameActivity.class, getIntent()); - } - private Intent getRoutedIntent(Intent destination, @Nullable Intent nextIntent) { if (nextIntent != null) destination.putExtra("next_intent", nextIntent); return destination; diff --git a/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt b/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt index 2e68975ef4..ef1a076688 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/absbackup/SignalBackupAgent.kt @@ -17,7 +17,7 @@ import java.io.IOException */ class SignalBackupAgent : BackupAgent() { private val items: List = listOf( - KbsAuthTokens, + KbsAuthTokens ) override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) { @@ -47,6 +47,7 @@ class SignalBackupAgent : BackupAgent() { } override fun onRestore(dataInput: BackupDataInput, appVersionCode: Int, newState: ParcelFileDescriptor) { + Log.i(TAG, "Restoring from Android Backup Service.") while (dataInput.readNextHeader()) { val buffer = ByteArray(dataInput.dataSize) dataInput.readEntityData(buffer, 0, dataInput.dataSize) diff --git a/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt b/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt index b510845ac4..0d767d3894 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/absbackup/backupables/KbsAuthTokens.kt @@ -17,8 +17,7 @@ object KbsAuthTokens : AndroidBackupItem { } override fun getDataForBackup(): ByteArray { - val registrationRecoveryTokenList = SignalStore.kbsValues().kbsAuthTokenList - val proto = KbsAuthToken(tokens = registrationRecoveryTokenList) + val proto = KbsAuthToken(tokens = SignalStore.kbsValues().kbsAuthTokenList) return proto.encode() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationCompleteListener.java b/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationCompleteListener.java index 3063a04c91..afcb813d57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationCompleteListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationCompleteListener.java @@ -11,7 +11,7 @@ public final void onAnimationStart(Animator animation) {} public abstract void onAnimationEnd(Animator animation); @Override - public final void onAnimationCancel(Animator animation) {} + public void onAnimationCancel(Animator animation) {} @Override public final void onAnimationRepeat(Animator animation) {} } diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationStartListener.kt b/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationStartListener.kt new file mode 100644 index 0000000000..228e0503a5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/AnimationStartListener.kt @@ -0,0 +1,9 @@ +package org.thoughtcrime.securesms.animation + +import android.animation.Animator + +abstract class AnimationStartListener : Animator.AnimatorListener { + override fun onAnimationEnd(animation: Animator) = Unit + override fun onAnimationCancel(animation: Animator) = Unit + override fun onAnimationRepeat(animation: Animator) = Unit +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java index add2d2388e..05e893c185 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleSquareImageViewTransition.java @@ -13,7 +13,6 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable; -@TargetApi(21) abstract class CircleSquareImageViewTransition extends Transition { private static final String CIRCLE_RATIO = "CIRCLE_RATIO"; diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java index 4c07fa0d00..9594810817 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleToSquareImageViewTransition.java @@ -7,7 +7,6 @@ /** * Will only transition {@link android.widget.ImageView}s that contain a {@link androidx.core.graphics.drawable.RoundedBitmapDrawable}. */ -@TargetApi(21) public final class CircleToSquareImageViewTransition extends CircleSquareImageViewTransition { public CircleToSquareImageViewTransition(Context context, AttributeSet attrs) { super(false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java index 42b5133966..8849f0cad0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java +++ b/app/src/main/java/org/thoughtcrime/securesms/animation/transitions/SquareToCircleImageViewTransition.java @@ -7,7 +7,6 @@ /** * Will only transition {@link android.widget.ImageView}s that contain a {@link androidx.core.graphics.drawable.RoundedBitmapDrawable}. */ -@TargetApi(21) public final class SquareToCircleImageViewTransition extends CircleSquareImageViewTransition { public SquareToCircleImageViewTransition(Context context, AttributeSet attrs) { super(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioFileInfo.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioFileInfo.java new file mode 100644 index 0000000000..248d1a8b8a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioFileInfo.java @@ -0,0 +1,45 @@ +package org.thoughtcrime.securesms.audio; + +import androidx.annotation.NonNull; + +import com.google.protobuf.ByteString; + +import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData; + +import java.util.concurrent.TimeUnit; + +public class AudioFileInfo { + private final long durationUs; + private final byte[] waveFormBytes; + private final float[] waveForm; + + public static @NonNull AudioFileInfo fromDatabaseProtobuf(@NonNull AudioWaveFormData audioWaveForm) { + return new AudioFileInfo(audioWaveForm.getDurationUs(), audioWaveForm.getWaveForm().toByteArray()); + } + + AudioFileInfo(long durationUs, byte[] waveFormBytes) { + this.durationUs = durationUs; + this.waveFormBytes = waveFormBytes; + this.waveForm = new float[waveFormBytes.length]; + + for (int i = 0; i < waveFormBytes.length; i++) { + int unsigned = waveFormBytes[i] & 0xff; + this.waveForm[i] = unsigned / 255f; + } + } + + public long getDuration(@NonNull TimeUnit timeUnit) { + return timeUnit.convert(durationUs, TimeUnit.MICROSECONDS); + } + + public float[] getWaveForm() { + return waveForm; + } + + public @NonNull AudioWaveFormData toDatabaseProtobuf() { + return AudioWaveFormData.newBuilder() + .setDurationUs(durationUs) + .setWaveForm(ByteString.copyFrom(waveFormBytes)) + .build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java index acc41141c7..c4c3ca8132 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java @@ -68,7 +68,8 @@ public AudioRecorder(@NonNull Context context, @Nullable AudioRecordingHandler u Log.i(TAG, "Running startRecording() + " + Thread.currentThread().getId()); try { if (recorder != null) { - throw new AssertionError("We can only record once at a time."); + recordingSingle.onError(new IllegalStateException("We can only do one recording at a time!")); + return; } ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java deleted file mode 100644 index 76c9a8e876..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java +++ /dev/null @@ -1,324 +0,0 @@ -package org.thoughtcrime.securesms.audio; - -import android.content.Context; -import android.media.MediaCodec; -import android.media.MediaExtractor; -import android.media.MediaFormat; -import android.net.Uri; -import android.util.LruCache; - -import androidx.annotation.AnyThread; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.annotation.WorkerThread; -import androidx.core.util.Consumer; - -import com.google.protobuf.ByteString; - -import org.signal.core.util.ThreadUtil; -import org.signal.core.util.concurrent.SignalExecutors; -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.database.AttachmentTable; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData; -import org.thoughtcrime.securesms.media.DecryptableUriMediaInput; -import org.thoughtcrime.securesms.media.MediaInput; -import org.thoughtcrime.securesms.mms.AudioSlide; -import org.thoughtcrime.securesms.util.concurrent.SerialExecutor; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Locale; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -@RequiresApi(api = 23) -public final class AudioWaveForm { - - private static final String TAG = Log.tag(AudioWaveForm.class); - - private static final int BAR_COUNT = 46; - private static final int SAMPLES_PER_BAR = 4; - - private final Context context; - private final AudioSlide slide; - - public AudioWaveForm(@NonNull Context context, @NonNull AudioSlide slide) { - this.context = context.getApplicationContext(); - this.slide = slide; - } - - private static final LruCache WAVE_FORM_CACHE = new LruCache<>(200); - private static final Executor AUDIO_DECODER_EXECUTOR = new SerialExecutor(SignalExecutors.BOUNDED); - - @AnyThread - public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Runnable onFailure) { - Uri uri = slide.getUri(); - Attachment attachment = slide.asAttachment(); - - if (uri == null) { - Log.w(TAG, "No uri"); - ThreadUtil.runOnMain(onFailure); - return; - } - - String cacheKey = uri.toString(); - AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey); - if (cached != null) { - Log.i(TAG, "Loaded wave form from cache " + cacheKey); - ThreadUtil.runOnMain(() -> onSuccess.accept(cached)); - return; - } - - AUDIO_DECODER_EXECUTOR.execute(() -> { - AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey); - if (cachedInExecutor != null) { - Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey); - ThreadUtil.runOnMain(() -> onSuccess.accept(cachedInExecutor)); - return; - } - - AudioHash audioHash = attachment.getAudioHash(); - if (audioHash != null) { - AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm()); - if (audioFileInfo.waveForm.length == 0) { - Log.w(TAG, "Recovering from a wave form generation error " + cacheKey); - ThreadUtil.runOnMain(onFailure); - return; - } else if (audioFileInfo.waveForm.length != BAR_COUNT) { - Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey); - } else { - WAVE_FORM_CACHE.put(cacheKey, audioFileInfo); - Log.i(TAG, "Loaded wave form from DB " + cacheKey); - ThreadUtil.runOnMain(() -> onSuccess.accept(audioFileInfo)); - return; - } - } - - if (attachment instanceof DatabaseAttachment) { - try { - AttachmentTable attachmentDatabase = SignalDatabase.attachments(); - DatabaseAttachment dbAttachment = (DatabaseAttachment) attachment; - long startTime = System.currentTimeMillis(); - - attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), AudioWaveFormData.getDefaultInstance()); - - Log.i(TAG, String.format("Starting wave form generation (%s)", cacheKey)); - - AudioFileInfo fileInfo = generateWaveForm(uri); - - Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey)); - - attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf()); - - WAVE_FORM_CACHE.put(cacheKey, fileInfo); - ThreadUtil.runOnMain(() -> onSuccess.accept(fileInfo)); - } catch (Throwable e) { - Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e); - ThreadUtil.runOnMain(onFailure); - } - } else { - try { - Log.i(TAG, "Not in database and not cached. Generating wave form on-the-fly."); - - long startTime = System.currentTimeMillis(); - - Log.i(TAG, String.format("Starting wave form generation (%s)", cacheKey)); - - AudioFileInfo fileInfo = generateWaveForm(uri); - - Log.i(TAG, String.format(Locale.US, "Audio wave form generation time %d ms (%s)", System.currentTimeMillis() - startTime, cacheKey)); - - WAVE_FORM_CACHE.put(cacheKey, fileInfo); - ThreadUtil.runOnMain(() -> onSuccess.accept(fileInfo)); - } catch (IOException e) { - Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e); - ThreadUtil.runOnMain(onFailure); - } - } - }); - } - - /** - * Based on decode sample from: - *

- * https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/DecoderTest.java - */ - @WorkerThread - @RequiresApi(api = 23) - private @NonNull AudioFileInfo generateWaveForm(@NonNull Uri uri) throws IOException { - try (MediaInput dataSource = DecryptableUriMediaInput.createForUri(context, uri)) { - long[] wave = new long[BAR_COUNT]; - int[] waveSamples = new int[BAR_COUNT]; - - MediaExtractor extractor = dataSource.createExtractor(); - - if (extractor.getTrackCount() == 0) { - throw new IOException("No audio track"); - } - - MediaFormat format = extractor.getTrackFormat(0); - - if (!format.containsKey(MediaFormat.KEY_DURATION)) { - throw new IOException("Unknown duration"); - } - - long totalDurationUs = format.getLong(MediaFormat.KEY_DURATION); - String mime = format.getString(MediaFormat.KEY_MIME); - - if (!mime.startsWith("audio/")) { - throw new IOException("Mime not audio"); - } - - MediaCodec codec = MediaCodec.createDecoderByType(mime); - - if (totalDurationUs == 0) { - throw new IOException("Zero duration"); - } - - codec.configure(format, null, null, 0); - codec.start(); - - ByteBuffer[] codecInputBuffers = codec.getInputBuffers(); - ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers(); - - extractor.selectTrack(0); - - long kTimeOutUs = 5000; - MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); - boolean sawInputEOS = false; - boolean sawOutputEOS = false; - int noOutputCounter = 0; - - while (!sawOutputEOS && noOutputCounter < 50) { - noOutputCounter++; - if (!sawInputEOS) { - int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs); - if (inputBufIndex >= 0) { - ByteBuffer dstBuf = codecInputBuffers[inputBufIndex]; - int sampleSize = extractor.readSampleData(dstBuf, 0); - long presentationTimeUs = 0; - - if (sampleSize < 0) { - sawInputEOS = true; - sampleSize = 0; - } else { - presentationTimeUs = extractor.getSampleTime(); - } - - codec.queueInputBuffer( - inputBufIndex, - 0, - sampleSize, - presentationTimeUs, - sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); - - if (!sawInputEOS) { - int barSampleIndex = (int) (SAMPLES_PER_BAR * (wave.length * extractor.getSampleTime()) / totalDurationUs); - sawInputEOS = !extractor.advance(); - int nextBarSampleIndex = (int) (SAMPLES_PER_BAR * (wave.length * extractor.getSampleTime()) / totalDurationUs); - while (!sawInputEOS && nextBarSampleIndex == barSampleIndex) { - sawInputEOS = !extractor.advance(); - if (!sawInputEOS) { - nextBarSampleIndex = (int) (SAMPLES_PER_BAR * (wave.length * extractor.getSampleTime()) / totalDurationUs); - } - } - } - } - } - - int outputBufferIndex; - do { - outputBufferIndex = codec.dequeueOutputBuffer(info, kTimeOutUs); - if (outputBufferIndex >= 0) { - if (info.size > 0) { - noOutputCounter = 0; - } - - ByteBuffer buf = codecOutputBuffers[outputBufferIndex]; - int barIndex = (int) ((wave.length * info.presentationTimeUs) / totalDurationUs); - long total = 0; - for (int i = 0; i < info.size; i += 2 * 4) { - short aShort = buf.getShort(i); - total += Math.abs(aShort); - } - if (barIndex >= 0 && barIndex < wave.length) { - wave[barIndex] += total; - waveSamples[barIndex] += info.size / 2; - } - codec.releaseOutputBuffer(outputBufferIndex, false); - if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - sawOutputEOS = true; - } - } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { - codecOutputBuffers = codec.getOutputBuffers(); - } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - Log.d(TAG, "output format has changed to " + codec.getOutputFormat()); - } - } while (outputBufferIndex >= 0); - } - - codec.stop(); - codec.release(); - extractor.release(); - - float[] floats = new float[BAR_COUNT]; - byte[] bytes = new byte[BAR_COUNT]; - float max = 0; - - for (int i = 0; i < BAR_COUNT; i++) { - if (waveSamples[i] == 0) continue; - - floats[i] = wave[i] / (float) waveSamples[i]; - if (floats[i] > max) { - max = floats[i]; - } - } - - for (int i = 0; i < BAR_COUNT; i++) { - float normalized = floats[i] / max; - bytes[i] = (byte) (255 * normalized); - } - - return new AudioFileInfo(totalDurationUs, bytes); - } - } - - public static class AudioFileInfo { - private final long durationUs; - private final byte[] waveFormBytes; - private final float[] waveForm; - - private static @NonNull AudioFileInfo fromDatabaseProtobuf(@NonNull AudioWaveFormData audioWaveForm) { - return new AudioFileInfo(audioWaveForm.getDurationUs(), audioWaveForm.getWaveForm().toByteArray()); - } - - private AudioFileInfo(long durationUs, byte[] waveFormBytes) { - this.durationUs = durationUs; - this.waveFormBytes = waveFormBytes; - this.waveForm = new float[waveFormBytes.length]; - - for (int i = 0; i < waveFormBytes.length; i++) { - int unsigned = waveFormBytes[i] & 0xff; - this.waveForm[i] = unsigned / 255f; - } - } - - public long getDuration(@NonNull TimeUnit timeUnit) { - return timeUnit.convert(durationUs, TimeUnit.MICROSECONDS); - } - - public float[] getWaveForm() { - return waveForm; - } - - private @NonNull AudioWaveFormData toDatabaseProtobuf() { - return AudioWaveFormData.newBuilder() - .setDurationUs(durationUs) - .setWaveForm(ByteString.copyFrom(waveFormBytes)) - .build(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveFormGenerator.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveFormGenerator.java new file mode 100644 index 0000000000..30e5d0e3d7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveFormGenerator.java @@ -0,0 +1,168 @@ +package org.thoughtcrime.securesms.audio; + +import android.content.Context; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.annotation.WorkerThread; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.media.DecryptableUriMediaInput; +import org.thoughtcrime.securesms.media.MediaInput; + +import java.io.IOException; +import java.nio.ByteBuffer; + +@RequiresApi(api = 23) +public final class AudioWaveFormGenerator { + + private static final String TAG = Log.tag(AudioWaveFormGenerator.class); + + public static final int BAR_COUNT = 46; + private static final int SAMPLES_PER_BAR = 4; + + private AudioWaveFormGenerator() {} + + /** + * Based on decode sample from: + *

+ * https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/DecoderTest.java + */ + @WorkerThread + public static @NonNull AudioFileInfo generateWaveForm(@NonNull Context context, @NonNull Uri uri) throws IOException { + try (MediaInput dataSource = DecryptableUriMediaInput.createForUri(context, uri)) { + long[] wave = new long[BAR_COUNT]; + int[] waveSamples = new int[BAR_COUNT]; + + MediaExtractor extractor = dataSource.createExtractor(); + + if (extractor.getTrackCount() == 0) { + throw new IOException("No audio track"); + } + + MediaFormat format = extractor.getTrackFormat(0); + + if (!format.containsKey(MediaFormat.KEY_DURATION)) { + throw new IOException("Unknown duration"); + } + + long totalDurationUs = format.getLong(MediaFormat.KEY_DURATION); + String mime = format.getString(MediaFormat.KEY_MIME); + + if (!mime.startsWith("audio/")) { + throw new IOException("Mime not audio"); + } + + MediaCodec codec = MediaCodec.createDecoderByType(mime); + + if (totalDurationUs == 0) { + throw new IOException("Zero duration"); + } + + codec.configure(format, null, null, 0); + codec.start(); + + extractor.selectTrack(0); + + long kTimeOutUs = 5000; + MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); + boolean sawInputEOS = false; + boolean sawOutputEOS = false; + int noOutputCounter = 0; + + while (!sawOutputEOS && noOutputCounter < 50) { + noOutputCounter++; + if (!sawInputEOS) { + int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs); + if (inputBufIndex >= 0) { + ByteBuffer dstBuf = codec.getInputBuffer(inputBufIndex); + int sampleSize = extractor.readSampleData(dstBuf, 0); + long presentationTimeUs = 0; + + if (sampleSize < 0) { + sawInputEOS = true; + sampleSize = 0; + } else { + presentationTimeUs = extractor.getSampleTime(); + } + + codec.queueInputBuffer( + inputBufIndex, + 0, + sampleSize, + presentationTimeUs, + sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); + + if (!sawInputEOS) { + int barSampleIndex = (int) (SAMPLES_PER_BAR * (wave.length * extractor.getSampleTime()) / totalDurationUs); + sawInputEOS = !extractor.advance(); + int nextBarSampleIndex = (int) (SAMPLES_PER_BAR * (wave.length * extractor.getSampleTime()) / totalDurationUs); + while (!sawInputEOS && nextBarSampleIndex == barSampleIndex) { + sawInputEOS = !extractor.advance(); + if (!sawInputEOS) { + nextBarSampleIndex = (int) (SAMPLES_PER_BAR * (wave.length * extractor.getSampleTime()) / totalDurationUs); + } + } + } + } + } + + int outputBufferIndex; + do { + outputBufferIndex = codec.dequeueOutputBuffer(info, kTimeOutUs); + if (outputBufferIndex >= 0) { + if (info.size > 0) { + noOutputCounter = 0; + } + + ByteBuffer buf = codec.getOutputBuffer(outputBufferIndex); + int barIndex = (int) ((wave.length * info.presentationTimeUs) / totalDurationUs); + long total = 0; + for (int i = 0; i < info.size; i += 2 * 4) { + short aShort = buf.getShort(i); + total += Math.abs(aShort); + } + if (barIndex >= 0 && barIndex < wave.length) { + wave[barIndex] += total; + waveSamples[barIndex] += info.size / 2; + } + codec.releaseOutputBuffer(outputBufferIndex, false); + if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + sawOutputEOS = true; + } + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + Log.d(TAG, "output format has changed to " + codec.getOutputFormat()); + } + } while (outputBufferIndex >= 0); + } + + codec.stop(); + codec.release(); + extractor.release(); + + float[] floats = new float[BAR_COUNT]; + byte[] bytes = new byte[BAR_COUNT]; + float max = 0; + + for (int i = 0; i < BAR_COUNT; i++) { + if (waveSamples[i] == 0) continue; + + floats[i] = wave[i] / (float) waveSamples[i]; + if (floats[i] > max) { + max = floats[i]; + } + } + + for (int i = 0; i < BAR_COUNT; i++) { + float normalized = floats[i] / max; + bytes[i] = (byte) (255 * normalized); + } + + return new AudioFileInfo(totalDurationUs, bytes); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForms.kt b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForms.kt new file mode 100644 index 0000000000..ff337a824f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForms.kt @@ -0,0 +1,152 @@ +package org.thoughtcrime.securesms.audio + +import android.content.Context +import android.net.Uri +import android.util.LruCache +import androidx.annotation.AnyThread +import androidx.annotation.RequiresApi +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.attachments.Attachment +import org.thoughtcrime.securesms.attachments.AttachmentId +import org.thoughtcrime.securesms.attachments.DatabaseAttachment +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData +import java.io.IOException +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +/** + * Uses [AudioWaveFormGenerator] to generate audio wave forms. + * + * Maintains an in-memory cache of recently requested wave forms. + */ +@RequiresApi(23) +object AudioWaveForms { + + private val TAG = Log.tag(AudioWaveForms::class.java) + + private val cache = ThreadSafeLruCache(200) + + @AnyThread + @JvmStatic + fun getWaveForm(context: Context, attachment: Attachment): Single { + val uri = attachment.uri + if (uri == null) { + Log.i(TAG, "No uri") + return Single.error(IllegalArgumentException("No uri from attachment")) + } + + val cacheKey = uri.toString() + val cachedInfo = cache.get(cacheKey) + if (cachedInfo != null) { + Log.i(TAG, "Loaded wave form from cache $cacheKey") + return Single.just(cachedInfo) + } + + val databaseCache = Single.fromCallable { + val audioHash = attachment.audioHash + return@fromCallable if (audioHash != null) { + checkDatabaseCache(cacheKey, audioHash.audioWaveForm) + } else { + Miss + } + }.subscribeOn(Schedulers.io()) + + val generateWaveForm: Single = if (attachment is DatabaseAttachment) { + Single.fromCallable { generateWaveForm(context, uri, cacheKey, attachment.attachmentId) } + } else { + Single.fromCallable { generateWaveForm(context, uri, cacheKey) } + }.subscribeOn(Schedulers.io()) + + return databaseCache + .flatMap { r -> + if (r is Miss) { + generateWaveForm + } else { + Single.just(r) + } + } + .map { r -> + if (r is Success) { + r.audioFileInfo + } else { + throw IOException("Unable to generate wave form") + } + } + } + + private fun checkDatabaseCache(cacheKey: String, audioWaveForm: AudioWaveFormData): CacheCheckResult { + val audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioWaveForm) + if (audioFileInfo.waveForm.isEmpty()) { + Log.w(TAG, "Recovering from a wave form generation error $cacheKey") + return Failure + } else if (audioFileInfo.waveForm.size != AudioWaveFormGenerator.BAR_COUNT) { + Log.w(TAG, "Wave form from database does not match bar count, regenerating $cacheKey") + } else { + cache.put(cacheKey, audioFileInfo) + Log.i(TAG, "Loaded wave form from DB $cacheKey") + return Success(audioFileInfo) + } + + return Miss + } + + private fun generateWaveForm(context: Context, uri: Uri, cacheKey: String, attachmentId: AttachmentId): CacheCheckResult { + try { + val startTime = System.currentTimeMillis() + SignalDatabase.attachments.writeAudioHash(attachmentId, AudioWaveFormData.getDefaultInstance()) + + Log.i(TAG, "Starting wave form generation ($cacheKey)") + val fileInfo: AudioFileInfo = AudioWaveFormGenerator.generateWaveForm(context, uri) + Log.i(TAG, "Audio wave form generation time ${System.currentTimeMillis() - startTime} ms ($cacheKey)") + + SignalDatabase.attachments.writeAudioHash(attachmentId, fileInfo.toDatabaseProtobuf()) + cache.put(cacheKey, fileInfo) + + return Success(fileInfo) + } catch (e: Throwable) { + Log.w(TAG, "Failed to create audio wave form for $cacheKey", e) + return Failure + } + } + + private fun generateWaveForm(context: Context, uri: Uri, cacheKey: String): CacheCheckResult { + try { + Log.i(TAG, "Not in database and not cached. Generating wave form on-the-fly.") + + val startTime = System.currentTimeMillis() + + Log.i(TAG, "Starting wave form generation ($cacheKey)") + val fileInfo: AudioFileInfo = AudioWaveFormGenerator.generateWaveForm(context, uri) + Log.i(TAG, "Audio wave form generation time ${System.currentTimeMillis() - startTime} ms ($cacheKey)") + + cache.put(cacheKey, fileInfo) + + return Success(fileInfo) + } catch (e: Throwable) { + Log.w(TAG, "Failed to create audio wave form for $cacheKey", e) + return Failure + } + } + + private class ThreadSafeLruCache(maxSize: Int) { + private val cache = LruCache(maxSize) + private val lock = ReentrantReadWriteLock() + + fun put(key: String, info: AudioFileInfo) { + lock.write { cache.put(key, info) } + } + + fun get(key: String): AudioFileInfo? { + return lock.read { cache.get(key) } + } + } + + private sealed class CacheCheckResult + private class Success(val audioFileInfo: AudioFileInfo) : CacheCheckResult() + private object Failure : CacheCheckResult() + private object Miss : CacheCheckResult() +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatar.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatar.kt index ae66b114c8..62cf866455 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatar.kt @@ -21,7 +21,7 @@ sealed class Avatar( data class Text( val text: String, val color: Avatars.ColorPair, - override val databaseId: DatabaseId, + override val databaseId: DatabaseId ) : Avatar(databaseId) { override fun withDatabaseId(databaseId: DatabaseId): Avatar { return copy(databaseId = databaseId) @@ -35,7 +35,7 @@ sealed class Avatar( data class Vector( val key: String, val color: Avatars.ColorPair, - override val databaseId: DatabaseId, + override val databaseId: DatabaseId ) : Avatar(databaseId) { override fun withDatabaseId(databaseId: DatabaseId): Avatar { return copy(databaseId = databaseId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarBundler.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarBundler.kt index 786e2d70b0..32bcdd66d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarBundler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarBundler.kt @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.avatar +import android.net.Uri import android.os.Bundle +import org.signal.core.util.getParcelableCompat /** * Utility class which encapsulates reading and writing Avatar objects to and from Bundles. @@ -33,7 +35,7 @@ object AvatarBundler { } fun extractPhoto(bundle: Bundle): Avatar.Photo = Avatar.Photo( - uri = requireNotNull(bundle.getParcelable(URI)), + uri = requireNotNull(bundle.getParcelableCompat(URI, Uri::class.java)), size = bundle.getLong(SIZE), databaseId = bundle.getDatabaseId() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt index da9cbb4ded..5cf4cbb616 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/AvatarRenderer.kt @@ -6,6 +6,7 @@ import android.graphics.Canvas import android.graphics.Typeface import android.graphics.drawable.Drawable import android.net.Uri +import androidx.annotation.MainThread import androidx.appcompat.content.res.AppCompatResources import com.airbnb.lottie.SimpleColorFilter import org.signal.core.util.concurrent.SignalExecutors @@ -28,8 +29,13 @@ object AvatarRenderer { val DIMENSIONS = AvatarHelper.AVATAR_DIMENSIONS + private var typeface: Typeface? = null + + @MainThread fun getTypeface(context: Context): Typeface { - return Typeface.createFromAsset(context.assets, "fonts/Inter-Medium.otf") + val interMedium = typeface ?: Typeface.createFromAsset(context.assets, "fonts/Inter-Medium.otf") + typeface = interMedium + return interMedium } fun renderAvatar(context: Context, avatar: Avatar, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatars.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatars.kt index 8222e2d474..ec38196e5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatars.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/Avatars.kt @@ -74,7 +74,7 @@ object Avatars { "avatar_sunset" to DefaultAvatar(R.drawable.ic_avatar_sunset, "A120"), "avatar_surfboard" to DefaultAvatar(R.drawable.ic_avatar_surfboard, "A110"), "avatar_soccerball" to DefaultAvatar(R.drawable.ic_avatar_soccerball, "A130"), - "avatar_football" to DefaultAvatar(R.drawable.ic_avatar_football, "A220"), + "avatar_football" to DefaultAvatar(R.drawable.ic_avatar_football, "A220") ) @DrawableRes diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt index 0f8e145a90..1cae56e71c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/picker/AvatarPickerFragment.kt @@ -16,6 +16,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.Navigation import androidx.recyclerview.widget.RecyclerView import org.signal.core.util.ThreadUtil +import org.signal.core.util.getParcelableExtraCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.avatar.Avatar import org.thoughtcrime.securesms.avatar.AvatarBundler @@ -87,8 +88,9 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) { val selectedPosition = items.indexOfFirst { it.isSelected } adapter.submitList(items) { - if (selectedPosition > -1) + if (selectedPosition > -1) { recycler.smoothScrollToPosition(selectedPosition) + } } } @@ -146,10 +148,9 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) { ViewUtil.hideKeyboard(requireContext(), requireView()) } - @Suppress("DEPRECATION") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_CODE_SELECT_IMAGE && resultCode == Activity.RESULT_OK && data != null) { - val media: Media = requireNotNull(data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA)) + val media: Media = requireNotNull(data.getParcelableExtraCompat(AvatarSelectionActivity.EXTRA_MEDIA, Media::class.java)) viewModel.onAvatarPhotoSelectionCompleted(media) } else { super.onActivityResult(requestCode, resultCode, data) diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/text/TextAvatarCreationState.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/text/TextAvatarCreationState.kt index 52493ad3a7..dd8a02f15c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/text/TextAvatarCreationState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/text/TextAvatarCreationState.kt @@ -5,7 +5,7 @@ import org.thoughtcrime.securesms.avatar.AvatarColorItem import org.thoughtcrime.securesms.avatar.Avatars data class TextAvatarCreationState( - val currentAvatar: Avatar.Text, + val currentAvatar: Avatar.Text ) { fun colors(): List = Avatars.colors.map { AvatarColorItem(it, currentAvatar.color == it) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/vector/VectorAvatarCreationState.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/vector/VectorAvatarCreationState.kt index 20da33108f..1cb6c1269e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/vector/VectorAvatarCreationState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/vector/VectorAvatarCreationState.kt @@ -5,7 +5,7 @@ import org.thoughtcrime.securesms.avatar.AvatarColorItem import org.thoughtcrime.securesms.avatar.Avatars data class VectorAvatarCreationState( - val currentAvatar: Avatar.Vector, + val currentAvatar: Avatar.Vector ) { fun colors(): List = Avatars.colors.map { AvatarColorItem(it, currentAvatar.color == it) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRecordInputStream.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRecordInputStream.java index 9c11e14e1e..e90f3169b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRecordInputStream.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRecordInputStream.java @@ -6,6 +6,8 @@ import org.signal.core.util.StreamUtil; import org.signal.libsignal.protocol.kdf.HKDF; import org.signal.libsignal.protocol.util.ByteUtil; +import org.thoughtcrime.securesms.backup.proto.BackupFrame; +import org.thoughtcrime.securesms.backup.proto.Header; import java.io.IOException; import java.io.InputStream; @@ -45,21 +47,21 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream { byte[] headerFrame = new byte[headerLength]; StreamUtil.readFully(in, headerFrame); - BackupProtos.BackupFrame frame = BackupProtos.BackupFrame.parseFrom(headerFrame); + BackupFrame frame = BackupFrame.ADAPTER.decode(headerFrame); - if (!frame.hasHeader()) { + if (frame.header_ == null) { throw new IOException("Backup stream does not start with header!"); } - BackupProtos.Header header = frame.getHeader(); + Header header = frame.header_; - this.iv = header.getIv().toByteArray(); + this.iv = header.iv.toByteArray(); if (iv.length != 16) { throw new IOException("Invalid IV length!"); } - byte[] key = getBackupKey(passphrase, header.hasSalt() ? header.getSalt().toByteArray() : null); + byte[] key = getBackupKey(passphrase, header.salt != null ? header.salt.toByteArray() : null); byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64); byte[][] split = ByteUtil.split(derived, 32, 32); @@ -76,7 +78,7 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream { } } - BackupProtos.BackupFrame readFrame() throws IOException { + BackupFrame readFrame() throws IOException { return readFrame(in); } @@ -128,7 +130,7 @@ void readAttachmentTo(OutputStream out, int length) throws IOException { } } - private BackupProtos.BackupFrame readFrame(InputStream in) throws IOException { + private BackupFrame readFrame(InputStream in) throws IOException { try { byte[] length = new byte[4]; StreamUtil.readFully(in, length); @@ -151,7 +153,7 @@ private BackupProtos.BackupFrame readFrame(InputStream in) throws IOException { byte[] plaintext = cipher.doFinal(frame, 0, frame.length - 10); - return BackupProtos.BackupFrame.parseFrom(plaintext); + return BackupFrame.ADAPTER.decode(plaintext); } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupVerifier.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupVerifier.kt index 0bcc2d59ca..73073ac01e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupVerifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupVerifier.kt @@ -2,7 +2,10 @@ package org.thoughtcrime.securesms.backup import org.greenrobot.eventbus.EventBus import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame +import org.thoughtcrime.securesms.backup.proto.Attachment +import org.thoughtcrime.securesms.backup.proto.Avatar +import org.thoughtcrime.securesms.backup.proto.BackupFrame +import org.thoughtcrime.securesms.backup.proto.Sticker import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -23,11 +26,11 @@ object BackupVerifier { var frame: BackupFrame = inputStream.readFrame() cipherStream.use { - while (!frame.end && !cancellationSignal.isCanceled) { + while (frame.end != true && !cancellationSignal.isCanceled) { val verified = when { - frame.hasAttachment() -> verifyAttachment(frame.attachment, inputStream) - frame.hasSticker() -> verifySticker(frame.sticker, inputStream) - frame.hasAvatar() -> verifyAvatar(frame.avatar, inputStream) + frame.attachment != null -> verifyAttachment(frame.attachment!!, inputStream) + frame.sticker != null -> verifySticker(frame.sticker!!, inputStream) + frame.avatar != null -> verifyAvatar(frame.avatar!!, inputStream) else -> true } @@ -48,9 +51,9 @@ object BackupVerifier { return true } - private fun verifyAttachment(attachment: BackupProtos.Attachment, inputStream: BackupRecordInputStream): Boolean { + private fun verifyAttachment(attachment: Attachment, inputStream: BackupRecordInputStream): Boolean { try { - inputStream.readAttachmentTo(NullOutputStream, attachment.length) + inputStream.readAttachmentTo(NullOutputStream, attachment.length ?: 0) } catch (e: IOException) { Log.w(TAG, "Bad attachment id: ${attachment.attachmentId} len: ${attachment.length}", e) return false @@ -59,9 +62,9 @@ object BackupVerifier { return true } - private fun verifySticker(sticker: BackupProtos.Sticker, inputStream: BackupRecordInputStream): Boolean { + private fun verifySticker(sticker: Sticker, inputStream: BackupRecordInputStream): Boolean { try { - inputStream.readAttachmentTo(NullOutputStream, sticker.length) + inputStream.readAttachmentTo(NullOutputStream, sticker.length ?: 0) } catch (e: IOException) { Log.w(TAG, "Bad sticker id: ${sticker.rowId} len: ${sticker.length}", e) return false @@ -69,9 +72,9 @@ object BackupVerifier { return true } - private fun verifyAvatar(avatar: BackupProtos.Avatar, inputStream: BackupRecordInputStream): Boolean { + private fun verifyAvatar(avatar: Avatar, inputStream: BackupRecordInputStream): Boolean { try { - inputStream.readAttachmentTo(NullOutputStream, avatar.length) + inputStream.readAttachmentTo(NullOutputStream, avatar.length ?: 0) } catch (e: IOException) { Log.w(TAG, "Bad avatar id: ${avatar.recipientId} len: ${avatar.length}", e) return false diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java index ff6881f2f0..c1f8473ff6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.java @@ -12,7 +12,6 @@ import androidx.documentfile.provider.DocumentFile; import com.annimon.stream.function.Predicate; -import com.google.protobuf.ByteString; import net.zetetic.database.sqlcipher.SQLiteDatabase; @@ -26,6 +25,15 @@ import org.signal.libsignal.protocol.kdf.HKDF; import org.signal.libsignal.protocol.util.ByteUtil; import org.thoughtcrime.securesms.attachments.AttachmentId; +import org.thoughtcrime.securesms.backup.proto.Attachment; +import org.thoughtcrime.securesms.backup.proto.Avatar; +import org.thoughtcrime.securesms.backup.proto.BackupFrame; +import org.thoughtcrime.securesms.backup.proto.DatabaseVersion; +import org.thoughtcrime.securesms.backup.proto.Header; +import org.thoughtcrime.securesms.backup.proto.KeyValue; +import org.thoughtcrime.securesms.backup.proto.SharedPreference; +import org.thoughtcrime.securesms.backup.proto.SqlStatement; +import org.thoughtcrime.securesms.backup.proto.Sticker; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.database.AttachmentTable; @@ -80,6 +88,8 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import okio.ByteString; + public class FullBackupExporter extends FullBackupBase { private static final String TAG = Log.tag(FullBackupExporter.class); @@ -187,7 +197,7 @@ private static BackupEvent internalExport(@NonNull Context context, stopwatch.split("table::" + table); } - for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) { + for (SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) { throwIfCanceled(cancellationSignal); EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount)); outputStream.write(preference); @@ -287,7 +297,7 @@ private static List exportSchema(@NonNull SQLiteDatabase input, @NonNull String statement = createStatementsByTable.get(table); if (statement != null) { - outputStream.write(BackupProtos.SqlStatement.newBuilder().setStatement(statement).build()); + outputStream.write(new SqlStatement.Builder().statement(statement).build()); } else { throw new IOException("Failed to find a create statement for table: " + table); } @@ -299,7 +309,7 @@ private static List exportSchema(@NonNull SQLiteDatabase input, @NonNull String name = cursor.getString(1); if (isTableAllowed(name)) { - outputStream.write(BackupProtos.SqlStatement.newBuilder().setStatement(sql).build()); + outputStream.write(new SqlStatement.Builder().statement(sql).build()); } } } @@ -393,8 +403,10 @@ private static int exportTable(@NonNull String table, throwIfCanceled(cancellationSignal); if (predicate == null || predicate.test(cursor)) { - StringBuilder statement = new StringBuilder(template); - BackupProtos.SqlStatement.Builder statementBuilder = BackupProtos.SqlStatement.newBuilder(); + StringBuilder statement = new StringBuilder(template); + SqlStatement.Builder statementBuilder = new SqlStatement.Builder(); + + statementBuilder.parameters = new ArrayList<>(); statement.append('('); @@ -402,15 +414,15 @@ private static int exportTable(@NonNull String table, statement.append('?'); if (cursor.getType(i) == Cursor.FIELD_TYPE_STRING) { - statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setStringParamter(cursor.getString(i))); + statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().stringParamter(cursor.getString(i)).build()); } else if (cursor.getType(i) == Cursor.FIELD_TYPE_FLOAT) { - statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setDoubleParameter(cursor.getDouble(i))); + statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().doubleParameter(cursor.getDouble(i)).build()); } else if (cursor.getType(i) == Cursor.FIELD_TYPE_INTEGER) { - statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setIntegerParameter(cursor.getLong(i))); + statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().integerParameter(cursor.getLong(i)).build()); } else if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) { - statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setBlobParameter(ByteString.copyFrom(cursor.getBlob(i)))); + statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().blobParameter(new ByteString(cursor.getBlob(i))).build()); } else if (cursor.getType(i) == Cursor.FIELD_TYPE_NULL) { - statementBuilder.addParameters(BackupProtos.SqlStatement.SqlParameter.newBuilder().setNullparameter(true)); + statementBuilder.parameters.add(new SqlStatement.SqlParameter.Builder().nullparameter(true).build()); } else { throw new AssertionError("unknown type?" + cursor.getType(i)); } @@ -423,7 +435,7 @@ private static int exportTable(@NonNull String table, statement.append(')'); EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount)); - outputStream.write(statementBuilder.setStatement(statement.toString()).build()); + outputStream.write(statementBuilder.statement(statement.toString()).build()); if (postProcess != null) { count = postProcess.postProcess(cursor, count); @@ -504,29 +516,30 @@ private static int exportKeyValues(@NonNull BackupFrameOutputStream outputStream if (!dataSet.containsKey(key)) { continue; } - BackupProtos.KeyValue.Builder builder = BackupProtos.KeyValue.newBuilder() - .setKey(key); + + KeyValue.Builder builder = new KeyValue.Builder() + .key(key); Class type = dataSet.getType(key); if (type == byte[].class) { byte[] data = dataSet.getBlob(key, null); if (data != null) { - builder.setBlobValue(ByteString.copyFrom(dataSet.getBlob(key, null))); + builder.blobValue(new ByteString(dataSet.getBlob(key, null))); } else { Log.w(TAG, "Skipping storing null blob for key: " + key); } } else if (type == Boolean.class) { - builder.setBooleanValue(dataSet.getBoolean(key, false)); + builder.booleanValue(dataSet.getBoolean(key, false)); } else if (type == Float.class) { - builder.setFloatValue(dataSet.getFloat(key, 0)); + builder.floatValue(dataSet.getFloat(key, 0)); } else if (type == Integer.class) { - builder.setIntegerValue(dataSet.getInteger(key, 0)); + builder.integerValue(dataSet.getInteger(key, 0)); } else if (type == Long.class) { - builder.setLongValue(dataSet.getLong(key, 0)); + builder.longValue(dataSet.getLong(key, 0)); } else if (type == String.class) { String data = dataSet.getString(key, null); if (data != null) { - builder.setStringValue(dataSet.getString(key, null)); + builder.stringValue(dataSet.getString(key, null)); } else { Log.w(TAG, "Skipping storing null string for key: " + key); } @@ -596,10 +609,12 @@ private BackupFrameOutputStream(@NonNull OutputStream output, @NonNull String pa mac.init(new SecretKeySpec(macKey, "HmacSHA256")); - byte[] header = BackupProtos.BackupFrame.newBuilder().setHeader(BackupProtos.Header.newBuilder() - .setIv(ByteString.copyFrom(iv)) - .setSalt(ByteString.copyFrom(salt))) - .build().toByteArray(); + byte[] header = new BackupFrame.Builder().header_(new Header.Builder() + .iv(new okio.ByteString(iv)) + .salt(new okio.ByteString(salt)) + .build()) + .build() + .encode(); outputStream.write(Conversions.intToByteArray(header.length)); outputStream.write(header); @@ -608,26 +623,26 @@ private BackupFrameOutputStream(@NonNull OutputStream output, @NonNull String pa } } - public void write(BackupProtos.SharedPreference preference) throws IOException { - write(outputStream, BackupProtos.BackupFrame.newBuilder().setPreference(preference).build()); + public void write(SharedPreference preference) throws IOException { + write(outputStream, new BackupFrame.Builder().preference(preference).build()); } - public void write(BackupProtos.KeyValue keyValue) throws IOException { - write(outputStream, BackupProtos.BackupFrame.newBuilder().setKeyValue(keyValue).build()); + public void write(KeyValue keyValue) throws IOException { + write(outputStream, new BackupFrame.Builder().keyValue(keyValue).build()); } - public void write(BackupProtos.SqlStatement statement) throws IOException { - write(outputStream, BackupProtos.BackupFrame.newBuilder().setStatement(statement).build()); + public void write(SqlStatement statement) throws IOException { + write(outputStream, new BackupFrame.Builder().statement(statement).build()); } public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException { try { - write(outputStream, BackupProtos.BackupFrame.newBuilder() - .setAvatar(BackupProtos.Avatar.newBuilder() - .setRecipientId(avatarName) - .setLength(Util.toIntExact(size)) - .build()) - .build()); + write(outputStream, new BackupFrame.Builder() + .avatar(new Avatar.Builder() + .recipientId(avatarName) + .length(Util.toIntExact(size)) + .build()) + .build()); } catch (ArithmeticException e) { Log.w(TAG, "Unable to write avatar to backup", e); throw new InvalidBackupStreamException(); @@ -640,13 +655,13 @@ public void write(@NonNull String avatarName, @NonNull InputStream in, long size public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException { try { - write(outputStream, BackupProtos.BackupFrame.newBuilder() - .setAttachment(BackupProtos.Attachment.newBuilder() - .setRowId(attachmentId.getRowId()) - .setAttachmentId(attachmentId.getUniqueId()) - .setLength(Util.toIntExact(size)) - .build()) - .build()); + write(outputStream, new BackupFrame.Builder() + .attachment(new Attachment.Builder() + .rowId(attachmentId.getRowId()) + .attachmentId(attachmentId.getUniqueId()) + .length(Util.toIntExact(size)) + .build()) + .build()); } catch (ArithmeticException e) { Log.w(TAG, "Unable to write " + attachmentId + " to backup", e); throw new InvalidBackupStreamException(); @@ -654,22 +669,20 @@ public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, l long totalWritten = writeStream(in); if (totalWritten != size) { - if (totalWritten == 0) { - // MOLLY: Quick workaround for zero-sized broken attachments - SignalDatabase.attachments().deleteAttachment(attachmentId); - } + // MOLLY: Quick workaround for broken attachments + SignalDatabase.attachments().deleteAttachment(attachmentId); throw new IOException("Size mismatch!"); } } public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException { try { - write(outputStream, BackupProtos.BackupFrame.newBuilder() - .setSticker(BackupProtos.Sticker.newBuilder() - .setRowId(rowId) - .setLength(Util.toIntExact(size)) - .build()) - .build()); + write(outputStream, new BackupFrame.Builder() + .sticker(new Sticker.Builder() + .rowId(rowId) + .length(Util.toIntExact(size)) + .build()) + .build()); } catch (ArithmeticException e) { Log.w(TAG, "Unable to write sticker to backup", e); throw new InvalidBackupStreamException(); @@ -681,13 +694,13 @@ public void writeSticker(long rowId, @NonNull InputStream in, long size) throws } void writeDatabaseVersion(int version) throws IOException { - write(outputStream, BackupProtos.BackupFrame.newBuilder() - .setVersion(BackupProtos.DatabaseVersion.newBuilder().setVersion(version)) - .build()); + write(outputStream, new BackupFrame.Builder() + .version(new DatabaseVersion.Builder().version(version).build()) + .build()); } void writeEnd() throws IOException { - write(outputStream, BackupProtos.BackupFrame.newBuilder().setEnd(true).build()); + write(outputStream, new BackupFrame.Builder().end(true).build()); } /** @@ -728,12 +741,12 @@ private long writeStream(@NonNull InputStream inputStream) throws IOException { } } - private void write(@NonNull OutputStream out, @NonNull BackupProtos.BackupFrame frame) throws IOException { + private void write(@NonNull OutputStream out, @NonNull BackupFrame frame) throws IOException { try { Conversions.intToByteArray(iv, 0, counter++); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv)); - byte[] frameCiphertext = cipher.doFinal(frame.toByteArray()); + byte[] frameCiphertext = cipher.doFinal(frame.encode()); byte[] frameMac = mac.doFinal(frameCiphertext); byte[] length = Conversions.intToByteArray(frameCiphertext.length + 10); diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java index 57db1ecced..aecd50ffd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.java @@ -18,12 +18,14 @@ import org.signal.core.util.SqlUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.backup.BackupProtos.Attachment; -import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame; -import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion; -import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference; -import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement; -import org.thoughtcrime.securesms.backup.BackupProtos.Sticker; +import org.thoughtcrime.securesms.backup.proto.Attachment; +import org.thoughtcrime.securesms.backup.proto.Avatar; +import org.thoughtcrime.securesms.backup.proto.BackupFrame; +import org.thoughtcrime.securesms.backup.proto.DatabaseVersion; +import org.thoughtcrime.securesms.backup.proto.KeyValue; +import org.thoughtcrime.securesms.backup.proto.SharedPreference; +import org.thoughtcrime.securesms.backup.proto.SqlStatement; +import org.thoughtcrime.securesms.backup.proto.Sticker; import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.EncryptedPreferences; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; @@ -91,17 +93,17 @@ public static void importFile(@NonNull Context context, @NonNull AttachmentSecre BackupFrame frame; - while (!(frame = inputStream.readFrame()).getEnd()) { + while ((frame = inputStream.readFrame()).end != Boolean.TRUE) { if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count, 0)); count++; - if (frame.hasVersion()) processVersion(db, frame.getVersion()); - else if (frame.hasStatement()) tryProcessStatement(db, frame.getStatement()); - else if (frame.hasPreference()) processPreference(context, frame.getPreference()); - else if (frame.hasAttachment()) processAttachment(context, attachmentSecret, db, frame.getAttachment(), inputStream); - else if (frame.hasSticker()) processSticker(context, attachmentSecret, db, frame.getSticker(), inputStream); - else if (frame.hasAvatar()) processAvatar(context, db, frame.getAvatar(), inputStream); - else if (frame.hasKeyValue()) processKeyValue(frame.getKeyValue()); + if (frame.version != null) processVersion(db, frame.version); + else if (frame.statement != null) tryProcessStatement(db, frame.statement); + else if (frame.preference != null) processPreference(context, frame.preference); + else if (frame.attachment != null) processAttachment(context, attachmentSecret, db, frame.attachment, inputStream); + else if (frame.sticker != null) processSticker(context, attachmentSecret, db, frame.sticker, inputStream); + else if (frame.avatar != null) processAvatar(context, db, frame.avatar, inputStream); + else if (frame.keyValue != null) processKeyValue(frame.keyValue); else count--; } @@ -124,11 +126,11 @@ public static void importFile(@NonNull Context context, @NonNull AttachmentSecre } private static void processVersion(@NonNull SQLiteDatabase db, DatabaseVersion version) throws IOException { - if (version.getVersion() > db.getVersion()) { - throw new DatabaseDowngradeException(db.getVersion(), version.getVersion()); + if (version.version == null || version.version > db.getVersion()) { + throw new DatabaseDowngradeException(db.getVersion(), version.version != null ? version.version : -1); } - db.setVersion(version.getVersion()); + db.setVersion(version.version); } private static void tryProcessStatement(@NonNull SQLiteDatabase db, SqlStatement statement) { @@ -136,9 +138,9 @@ private static void tryProcessStatement(@NonNull SQLiteDatabase db, SqlStatement processStatement(db, statement); } catch (SQLiteConstraintException e) { String tableName = "?"; - String statementString = statement.getStatement(); + String statementString = statement.statement; - if (statementString.startsWith("INSERT INTO ")) { + if (statementString != null && statementString.startsWith("INSERT INTO ")) { int nameStart = "INSERT INTO ".length(); int nameEnd = statementString.indexOf(" ", "INSERT INTO ".length()); @@ -157,27 +159,32 @@ private static void tryProcessStatement(@NonNull SQLiteDatabase db, SqlStatement } private static void processStatement(@NonNull SQLiteDatabase db, SqlStatement statement) { - boolean isForMmsFtsSecretTable = statement.getStatement().contains(SearchTable.FTS_TABLE_NAME + "_"); - boolean isForEmojiSecretTable = statement.getStatement().contains(EmojiSearchTable.TABLE_NAME + "_"); - boolean isForSqliteSecretTable = statement.getStatement().toLowerCase().startsWith("create table sqlite_"); + if (statement.statement == null) { + Log.w(TAG, "Null statement!"); + return; + } + + boolean isForMmsFtsSecretTable = statement.statement.contains(SearchTable.FTS_TABLE_NAME + "_"); + boolean isForEmojiSecretTable = statement.statement.contains(EmojiSearchTable.TABLE_NAME + "_"); + boolean isForSqliteSecretTable = statement.statement.toLowerCase().startsWith("create table sqlite_"); if (isForMmsFtsSecretTable || isForEmojiSecretTable || isForSqliteSecretTable) { - Log.i(TAG, "Ignoring import for statement: " + statement.getStatement()); + Log.i(TAG, "Ignoring import for statement: " + statement.statement); return; } List parameters = new LinkedList<>(); - for (SqlStatement.SqlParameter parameter : statement.getParametersList()) { - if (parameter.hasStringParamter()) parameters.add(parameter.getStringParamter()); - else if (parameter.hasDoubleParameter()) parameters.add(parameter.getDoubleParameter()); - else if (parameter.hasIntegerParameter()) parameters.add(parameter.getIntegerParameter()); - else if (parameter.hasBlobParameter()) parameters.add(parameter.getBlobParameter().toByteArray()); - else if (parameter.hasNullparameter()) parameters.add(null); + for (SqlStatement.SqlParameter parameter : statement.parameters) { + if (parameter.stringParamter != null) parameters.add(parameter.stringParamter); + else if (parameter.doubleParameter != null) parameters.add(parameter.doubleParameter); + else if (parameter.integerParameter != null) parameters.add(parameter.integerParameter); + else if (parameter.blobParameter != null) parameters.add(parameter.blobParameter.toByteArray()); + else if (parameter.nullparameter != null) parameters.add(null); } - if (parameters.size() > 0) db.execSQL(statement.getStatement(), parameters.toArray()); - else db.execSQL(statement.getStatement()); + if (parameters.size() > 0) db.execSQL(statement.statement, parameters.toArray()); + else db.execSQL(statement.statement); } private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream) @@ -189,12 +196,12 @@ private static void processAttachment(@NonNull Context context, @NonNull Attachm ContentValues contentValues = new ContentValues(); try { - inputStream.readAttachmentTo(output.second, attachment.getLength()); + inputStream.readAttachmentTo(output.second, attachment.length); contentValues.put(AttachmentTable.DATA, dataFile.getAbsolutePath()); contentValues.put(AttachmentTable.DATA_RANDOM, output.first); } catch (BackupRecordInputStream.BadMacException e) { - Log.w(TAG, "Bad MAC for attachment " + attachment.getAttachmentId() + "! Can't restore it.", e); + Log.w(TAG, "Bad MAC for attachment " + attachment.attachmentId + "! Can't restore it.", e); dataFile.delete(); contentValues.put(AttachmentTable.DATA, (String) null); contentValues.put(AttachmentTable.DATA_RANDOM, (String) null); @@ -202,7 +209,7 @@ private static void processAttachment(@NonNull Context context, @NonNull Attachm db.update(AttachmentTable.TABLE_NAME, contentValues, AttachmentTable.ROW_ID + " = ? AND " + AttachmentTable.UNIQUE_ID + " = ?", - new String[] {String.valueOf(attachment.getRowId()), String.valueOf(attachment.getAttachmentId())}); + new String[] {String.valueOf(attachment.rowId), String.valueOf(attachment.attachmentId)}); } private static void processSticker(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Sticker sticker, BackupRecordInputStream inputStream) @@ -213,52 +220,57 @@ private static void processSticker(@NonNull Context context, @NonNull Attachment Pair output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false); - inputStream.readAttachmentTo(output.second, sticker.getLength()); + inputStream.readAttachmentTo(output.second, sticker.length); ContentValues contentValues = new ContentValues(); contentValues.put(StickerTable.FILE_PATH, dataFile.getAbsolutePath()); - contentValues.put(StickerTable.FILE_LENGTH, sticker.getLength()); + contentValues.put(StickerTable.FILE_LENGTH, sticker.length); contentValues.put(StickerTable.FILE_RANDOM, output.first); db.update(StickerTable.TABLE_NAME, contentValues, StickerTable._ID + " = ?", - new String[] {String.valueOf(sticker.getRowId())}); + new String[] {String.valueOf(sticker.rowId)}); } - private static void processAvatar(@NonNull Context context, @NonNull SQLiteDatabase db, @NonNull BackupProtos.Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException { - if (avatar.hasRecipientId()) { - RecipientId recipientId = RecipientId.from(avatar.getRecipientId()); - inputStream.readAttachmentTo(AvatarHelper.getOutputStream(context, recipientId, false), avatar.getLength()); + private static void processAvatar(@NonNull Context context, @NonNull SQLiteDatabase db, @NonNull Avatar avatar, @NonNull BackupRecordInputStream inputStream) throws IOException { + if (avatar.recipientId != null) { + RecipientId recipientId = RecipientId.from(avatar.recipientId); + inputStream.readAttachmentTo(AvatarHelper.getOutputStream(context, recipientId, false), avatar.length); } else { - if (avatar.hasName() && SqlUtil.tableExists(db, "recipient_preferences")) { + if (avatar.name != null && SqlUtil.tableExists(db, "recipient_preferences")) { Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar (legacy) so it can be fetched later."); - db.execSQL("UPDATE recipient_preferences SET signal_profile_avatar = NULL WHERE recipient_ids = ?", new String[] { avatar.getName() }); - } else if (avatar.hasName() && SqlUtil.tableExists(db, "recipient")) { + db.execSQL("UPDATE recipient_preferences SET signal_profile_avatar = NULL WHERE recipient_ids = ?", new String[] { avatar.name }); + } else if (avatar.name != null && SqlUtil.tableExists(db, "recipient")) { Log.w(TAG, "Avatar is missing a recipientId. Clearing signal_profile_avatar so it can be fetched later."); - db.execSQL("UPDATE recipient SET signal_profile_avatar = NULL WHERE phone = ?", new String[] { avatar.getName() }); + db.execSQL("UPDATE recipient SET signal_profile_avatar = NULL WHERE phone = ?", new String[] { avatar.name }); } else { Log.w(TAG, "Avatar is missing a recipientId. Skipping avatar restore."); } - inputStream.readAttachmentTo(new ByteArrayOutputStream(), avatar.getLength()); + inputStream.readAttachmentTo(new ByteArrayOutputStream(), avatar.length); } } - private static void processKeyValue(BackupProtos.KeyValue keyValue) { + private static void processKeyValue(KeyValue keyValue) { KeyValueDataSet dataSet = new KeyValueDataSet(); - if (keyValue.hasBlobValue()) { - dataSet.putBlob(keyValue.getKey(), keyValue.getBlobValue().toByteArray()); - } else if (keyValue.hasBooleanValue()) { - dataSet.putBoolean(keyValue.getKey(), keyValue.getBooleanValue()); - } else if (keyValue.hasFloatValue()) { - dataSet.putFloat(keyValue.getKey(), keyValue.getFloatValue()); - } else if (keyValue.hasIntegerValue()) { - dataSet.putInteger(keyValue.getKey(), keyValue.getIntegerValue()); - } else if (keyValue.hasLongValue()) { - dataSet.putLong(keyValue.getKey(), keyValue.getLongValue()); - } else if (keyValue.hasStringValue()) { - dataSet.putString(keyValue.getKey(), keyValue.getStringValue()); + if (keyValue.key == null) { + Log.w(TAG, "Null preference key!"); + return; + } + + if (keyValue.blobValue != null) { + dataSet.putBlob(keyValue.key, keyValue.blobValue.toByteArray()); + } else if (keyValue.booleanValue != null) { + dataSet.putBoolean(keyValue.key, keyValue.booleanValue); + } else if (keyValue.floatValue != null) { + dataSet.putFloat(keyValue.key, keyValue.floatValue); + } else if (keyValue.integerValue != null) { + dataSet.putInteger(keyValue.key, keyValue.integerValue); + } else if (keyValue.longValue != null) { + dataSet.putLong(keyValue.key, keyValue.longValue); + } else if (keyValue.stringValue != null) { + dataSet.putString(keyValue.key, keyValue.stringValue); } else { Log.i(TAG, "Unknown KeyValue backup value, skipping"); return; @@ -278,30 +290,30 @@ private static void processPreference(@NonNull Context context, SharedPreference SharedPreferences preferences; // Identity keys were moved from shared prefs into SignalStore. Need to handle importing backups made before the migration. - if ("SecureSMS-Preferences".equals(preference.getFile())) { - if ("pref_identity_public_v3".equals(preference.getKey()) && preference.hasValue()) { - SignalStore.account().restoreLegacyIdentityPublicKeyFromBackup(preference.getValue()); - } else if ("pref_identity_private_v3".equals(preference.getKey()) && preference.hasValue()) { - SignalStore.account().restoreLegacyIdentityPrivateKeyFromBackup(preference.getValue()); + if ("SecureSMS-Preferences".equals(preference.file_)) { + if ("pref_identity_public_v3".equals(preference.key) && preference.value_ != null) { + SignalStore.account().restoreLegacyIdentityPublicKeyFromBackup(preference.value_); + } else if ("pref_identity_private_v3".equals(preference.key) && preference.value_ != null) { + SignalStore.account().restoreLegacyIdentityPrivateKeyFromBackup(preference.value_); } return; } - if (defaultSharedPreferencesFileNames.contains(preference.getFile())) { + if (defaultSharedPreferencesFileNames.contains(preference.file_)) { preferences = SecurePreferenceManager.getSecurePreferences(context); } else { - preferences = EncryptedPreferences.create(context, preference.getFile()); + preferences = EncryptedPreferences.create(context, preference.file_); } - if (preference.hasValue()) { - preferences.edit().putString(preference.getKey(), preference.getValue()).commit(); - } else if (preference.hasBooleanValue()) { - preferences.edit().putBoolean(preference.getKey(), preference.getBooleanValue()).commit(); - } else if (preference.hasIsStringSetValue() && preference.getIsStringSetValue()) { - preferences.edit().putStringSet(preference.getKey(), new HashSet<>(preference.getStringSetValueList())).commit(); - } else if (preference.hasIntegerValue()) { - preferences.edit().putInt(preference.getKey(), preference.getIntegerValue()).commit(); + if (preference.value_ != null) { + preferences.edit().putString(preference.key, preference.value_).commit(); + } else if (preference.booleanValue != null) { + preferences.edit().putBoolean(preference.key, preference.booleanValue).commit(); + } else if (preference.isStringSetValue == Boolean.TRUE) { + preferences.edit().putStringSet(preference.key, new HashSet<>(preference.stringSetValue)).commit(); + } else if (preference.integerValue != null) { + preferences.edit().putInt(preference.key, preference.integerValue).commit(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt index 9a9d468d27..f5d3fb8968 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/models/Badge.kt @@ -130,7 +130,7 @@ data class Badge( .downsample(DownsampleStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE) .transform( - BadgeSpriteTransformation(BadgeSpriteTransformation.Size.BADGE_64, model.badge.imageDensity, ThemeUtil.isDarkTheme(context)), + BadgeSpriteTransformation(BadgeSpriteTransformation.Size.BADGE_64, model.badge.imageDensity, ThemeUtil.isDarkTheme(context)) ) .into(badge) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java index 7864e26ea3..2143327932 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java @@ -27,13 +27,15 @@ public class AlbumThumbnailView extends FrameLayout { private int currentSizeClass; + private final int[] corners = new int[4]; + private ViewGroup albumCellContainer; private Stub transferControls; private final SlideClickListener defaultThumbnailClickListener = (v, slide) -> { - if (thumbnailClickListener != null) { - thumbnailClickListener.onClick(v, slide); - } + if (thumbnailClickListener != null) { + thumbnailClickListener.onClick(v, slide); + } }; private final OnLongClickListener defaultLongClickListener = v -> this.performLongClick(); @@ -82,6 +84,7 @@ public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List } showSlides(glideRequests, slides); + applyCorners(); } public void setCellBackgroundColor(@ColorInt int color) { @@ -102,6 +105,15 @@ public void setDownloadClickListener(@Nullable SlidesClickedListener listener) { downloadClickListener = listener; } + public void setRadii(int topLeft, int topRight, int bottomRight, int bottomLeft) { + corners[0] = topLeft; + corners[1] = topRight; + corners[2] = bottomRight; + corners[3] = bottomLeft; + + applyCorners(); + } + private void inflateLayout(int sizeClass) { albumCellContainer.removeAllViews(); @@ -124,6 +136,83 @@ private void inflateLayout(int sizeClass) { } } + private void applyCorners() { + if (currentSizeClass < 2) { + return; + } + + switch (currentSizeClass) { + case 2: + applyCornersForSizeClass2(); + break; + case 3: + applyCornersForSizeClass3(); + break; + case 4: + applyCornersForSizeClass4(); + break; + case 5: + applyCornersForSizeClass5(); + break; + default: + applyCornersForManySizeClass(); + } + } + + private ThumbnailView[] getCells() { + ThumbnailView one = findViewById(R.id.album_cell_1); + ThumbnailView two = findViewById(R.id.album_cell_2); + ThumbnailView three = findViewById(R.id.album_cell_3); + ThumbnailView four = findViewById(R.id.album_cell_4); + ThumbnailView five = findViewById(R.id.album_cell_5); + + return new ThumbnailView[] { one, two, three, four, five }; + } + + private void applyCornersForSizeClass2() { + ThumbnailView[] cells = getCells(); + setRelativeRadii(cells[0], corners[0], 0, 0, corners[3]); + setRelativeRadii(cells[1], 0, corners[1], corners[2], 0); + } + + private void applyCornersForSizeClass3() { + ThumbnailView[] cells = getCells(); + setRelativeRadii(cells[0], corners[0], 0, 0, corners[3]); + setRelativeRadii(cells[1], 0, corners[1], 0, 0); + setRelativeRadii(cells[2], 0, 0, corners[2], 0); + } + + private void applyCornersForSizeClass4() { + ThumbnailView[] cells = getCells(); + setRelativeRadii(cells[0], corners[0], 0, 0, 0); + setRelativeRadii(cells[1], 0, corners[1], 0, 0); + setRelativeRadii(cells[2], 0, 0, 0, corners[3]); + setRelativeRadii(cells[3], 0, 0, corners[2], 0); + } + + private void applyCornersForSizeClass5() { + ThumbnailView[] cells = getCells(); + setRelativeRadii(cells[0], corners[0], 0, 0, 0); + setRelativeRadii(cells[1], 0, corners[1], 0, 0); + setRelativeRadii(cells[2], 0, 0, 0, corners[3]); + setRelativeRadii(cells[3], 0, 0, 0, 0); + setRelativeRadii(cells[4], 0, 0, corners[2], 0); + } + + private void setRelativeRadii(@NonNull ThumbnailView cell, int topLeft, int topRight, int bottomRight, int bottomLeft) { + boolean isLTR = getRootView().getLayoutDirection() == LAYOUT_DIRECTION_LTR; + cell.setRadii( + isLTR ? topLeft : topRight, + isLTR ? topRight : topLeft, + isLTR ? bottomRight : bottomLeft, + isLTR ? bottomLeft : bottomRight + ); + } + + private void applyCornersForManySizeClass() { + applyCornersForSizeClass5(); + } + private void showSlides(@NonNull GlideRequests glideRequests, @NonNull List slides) { setSlide(glideRequests, slides.get(0), R.id.album_cell_1); setSlide(glideRequests, slides.get(1), R.id.album_cell_2); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AlertView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AlertView.java index d57af4ae63..3e41116c19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AlertView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AlertView.java @@ -28,7 +28,6 @@ public AlertView(Context context, AttributeSet attrs) { initialize(attrs); } - @TargetApi(VERSION_CODES.HONEYCOMB) public AlertView(final Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(attrs); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AnimatingToggle.java b/app/src/main/java/org/thoughtcrime/securesms/components/AnimatingToggle.java index 41676c6a52..c049c07e8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AnimatingToggle.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AnimatingToggle.java @@ -54,7 +54,7 @@ public void addView(@NonNull View child, int index, ViewGroup.LayoutParams param } public void display(@Nullable View view) { - if (view == current) return; + if (view == current && current.getVisibility() == View.VISIBLE) return; if (current != null) ViewUtil.animateOut(current, outAnimation, View.GONE); if (view != null) ViewUtil.animateIn(view, inAnimation); @@ -62,7 +62,7 @@ public void display(@Nullable View view) { } public void displayQuick(@Nullable View view) { - if (view == current) return; + if (view == current && current.getVisibility() == View.VISIBLE) return; if (current != null) current.setVisibility(View.GONE); if (view != null) view.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java index 8fc9a3a8cc..a7b2ad13a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AudioView.java @@ -33,7 +33,7 @@ import org.greenrobot.eventbus.ThreadMode; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.audio.AudioWaveForm; +import org.thoughtcrime.securesms.audio.AudioWaveForms; import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState; import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.events.PartProgressEvent; @@ -43,6 +43,9 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.disposables.Disposable; + public final class AudioView extends FrameLayout { private static final String TAG = Log.tag(AudioView.class); @@ -77,6 +80,8 @@ public final class AudioView extends FrameLayout { private AudioSlide audioSlide; private Callbacks callbacks; + private Disposable disposable = Disposable.disposed(); + private final Observer playbackStateObserver = this::onPlaybackState; public AudioView(Context context) { @@ -155,6 +160,7 @@ protected void onAttachedToWindow() { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); EventBus.getDefault().unregister(this); + disposable.dispose(); } public void setProgressAndPlayBackgroundTint(@ColorInt int color) { @@ -170,6 +176,7 @@ public void setAudio(final @NonNull AudioSlide audio, final boolean showControls, final boolean forceHideDuration) { + this.disposable.dispose(); this.callbacks = callbacks; if (duration != null) { @@ -211,16 +218,26 @@ public void setAudio(final @NonNull AudioSlide audio, if (seekBar instanceof WaveFormSeekBarView) { WaveFormSeekBarView waveFormView = (WaveFormSeekBarView) seekBar; waveFormView.setColors(waveFormPlayedBarsColor, waveFormUnplayedBarsColor, waveFormThumbTint); - new AudioWaveForm(getContext(), audio).getWaveForm( - data -> { - durationMillis = data.getDuration(TimeUnit.MILLISECONDS); - updateProgress(0, 0); - if (!forceHideDuration && duration != null) { - duration.setVisibility(VISIBLE); - } - waveFormView.setWaveData(data.getWaveForm()); - }, - () -> waveFormView.setWaveMode(false)); + if (android.os.Build.VERSION.SDK_INT >= 23) { + disposable = AudioWaveForms.getWaveForm(getContext(), audioSlide.asAttachment()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + data -> { + durationMillis = data.getDuration(TimeUnit.MILLISECONDS); + updateProgress(0, 0); + if (!forceHideDuration && duration != null) { + duration.setVisibility(VISIBLE); + } + waveFormView.setWaveData(data.getWaveForm()); + }, + t -> waveFormView.setWaveMode(false) + ); + } else { + waveFormView.setWaveMode(false); + if (duration != null) { + duration.setVisibility(GONE); + } + } } if (forceHideDuration && duration != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java index 59f46bc381..534ea159b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java @@ -4,6 +4,7 @@ import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; +import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -58,10 +59,10 @@ public void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide) boolean showControls = slide.asAttachment().getUri() == null; if (slide.hasSticker()) { - image.setFit(new CenterInside()); + image.setScaleType(ImageView.ScaleType.FIT_CENTER); image.setImageResource(glideRequests, slide, showControls, false); } else { - image.setFit(new CenterCrop()); + image.setScaleType(ImageView.ScaleType.CENTER_CROP); image.setImageResource(glideRequests, slide, showControls, false, slide.asAttachment().getWidth(), slide.asAttachment().getHeight()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt index 08effd2e88..d3030626bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ClippedCardView.kt @@ -6,8 +6,8 @@ import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.util.AttributeSet -import androidx.cardview.widget.CardView import androidx.core.graphics.withClip +import com.google.android.material.card.MaterialCardView /** * Adds manual clipping around the card. This ensures that software rendering @@ -16,7 +16,7 @@ import androidx.core.graphics.withClip class ClippedCardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null -) : CardView(context, attrs) { +) : MaterialCardView(context, attrs) { private val bounds = Rect() private val boundsF = RectF() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java deleted file mode 100644 index 439b4d96be..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.Px; -import androidx.annotation.UiThread; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideClickListener; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; -import org.thoughtcrime.securesms.util.Projection; -import org.thoughtcrime.securesms.util.ViewUtil; - -import java.util.List; - -public class ConversationItemThumbnail extends FrameLayout { - - private ThumbnailView thumbnail; - private AlbumThumbnailView album; - private ImageView shade; - private ConversationItemFooter footer; - private CornerMask cornerMask; - private Outliner pulseOutliner; - private boolean borderless; - private int[] normalBounds; - private int[] gifBounds; - private int minimumThumbnailWidth; - private int maximumThumbnailHeight; - - public ConversationItemThumbnail(Context context) { - super(context); - init(null); - } - - public ConversationItemThumbnail(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs); - } - - public ConversationItemThumbnail(final Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(attrs); - } - - private void init(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.conversation_item_thumbnail, this); - - this.thumbnail = findViewById(R.id.conversation_thumbnail_image); - this.album = findViewById(R.id.conversation_thumbnail_album); - this.shade = findViewById(R.id.conversation_thumbnail_shade); - this.footer = findViewById(R.id.conversation_thumbnail_footer); - this.cornerMask = new CornerMask(this); - - int gifWidth = ViewUtil.dpToPx(260); - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0); - normalBounds = new int[]{ - typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0), - typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0), - typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0), - typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0) - }; - - gifWidth = typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_gifWidth, gifWidth); - typedArray.recycle(); - } else { - normalBounds = new int[]{0, 0, 0, 0}; - } - - gifBounds = new int[]{ - gifWidth, - gifWidth, - 1, - Integer.MAX_VALUE - }; - - minimumThumbnailWidth = -1; - maximumThumbnailHeight = -1; - } - - @SuppressWarnings("SuspiciousNameCombination") - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - if (!borderless) { - cornerMask.mask(canvas); - } - - if (pulseOutliner != null) { - pulseOutliner.draw(canvas); - } - } - - public void hideThumbnailView() { - thumbnail.setAlpha(0f); - } - - public void showThumbnailView() { - thumbnail.setAlpha(1f); - } - - public @NonNull Projection.Corners getCorners() { - return new Projection.Corners(cornerMask.getRadii()); - } - - public void setPulseOutliner(@NonNull Outliner outliner) { - this.pulseOutliner = outliner; - } - - @Override - public void setFocusable(boolean focusable) { - thumbnail.setFocusable(focusable); - album.setFocusable(focusable); - } - - @Override - public void setClickable(boolean clickable) { - thumbnail.setClickable(clickable); - album.setClickable(clickable); - } - - @Override - public void setOnLongClickListener(@Nullable OnLongClickListener l) { - thumbnail.setOnLongClickListener(l); - album.setOnLongClickListener(l); - } - - public void showShade(boolean show) { - shade.setVisibility(show ? VISIBLE : GONE); - forceLayout(); - } - - public void setCorners(int topLeft, int topRight, int bottomRight, int bottomLeft) { - cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft); - } - - public void setMinimumThumbnailWidth(@Px int width) { - minimumThumbnailWidth = width; - thumbnail.setMinimumThumbnailWidth(width); - } - - public void setMaximumThumbnailHeight(@Px int height) { - maximumThumbnailHeight = height; - thumbnail.setMaximumThumbnailHeight(height); - } - - public void setBorderless(boolean borderless) { - this.borderless = borderless; - } - - public ConversationItemFooter getFooter() { - return footer; - } - - @UiThread - public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull List slides, - boolean showControls, boolean isPreview) - { - if (slides.size() == 1) { - Slide slide = slides.get(0); - if (slide.isVideoGif()) { - setThumbnailBounds(gifBounds); - } else { - setThumbnailBounds(normalBounds); - - if (minimumThumbnailWidth != -1) { - thumbnail.setMinimumThumbnailWidth(minimumThumbnailWidth); - } - - if (maximumThumbnailHeight != -1) { - thumbnail.setMaximumThumbnailHeight(maximumThumbnailHeight); - } - } - - thumbnail.setVisibility(VISIBLE); - album.setVisibility(GONE); - - Attachment attachment = slides.get(0).asAttachment(); - thumbnail.setImageResource(glideRequests, slides.get(0), showControls, isPreview, attachment.getWidth(), attachment.getHeight()); - setTouchDelegate(thumbnail.getTouchDelegate()); - } else { - thumbnail.setVisibility(GONE); - album.setVisibility(VISIBLE); - - album.setSlides(glideRequests, slides, showControls); - setTouchDelegate(album.getTouchDelegate()); - } - } - - public void setConversationColor(@ColorInt int color) { - if (album.getVisibility() == VISIBLE) { - album.setCellBackgroundColor(color); - } - } - - public void setThumbnailClickListener(SlideClickListener listener) { - thumbnail.setThumbnailClickListener(listener); - album.setThumbnailClickListener(listener); - } - - public void setDownloadClickListener(SlidesClickedListener listener) { - thumbnail.setDownloadClickListener(listener); - album.setDownloadClickListener(listener); - } - - private void setThumbnailBounds(@NonNull int[] bounds) { - thumbnail.setBounds(bounds[0], bounds[1], bounds[2], bounds[3]); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt new file mode 100644 index 0000000000..67f97895e9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt @@ -0,0 +1,275 @@ +package org.thoughtcrime.securesms.components + +import android.content.Context +import android.graphics.Canvas +import android.os.Bundle +import android.os.Parcelable +import android.util.AttributeSet +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.Px +import androidx.annotation.UiThread +import androidx.core.os.bundleOf +import org.signal.core.util.dp +import org.signal.core.util.getParcelableCompat +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.mms.Slide +import org.thoughtcrime.securesms.mms.SlideClickListener +import org.thoughtcrime.securesms.mms.SlidesClickedListener +import org.thoughtcrime.securesms.util.Projection.Corners +import org.thoughtcrime.securesms.util.views.Stub + +class ConversationItemThumbnail @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : FrameLayout(context, attrs) { + + private var state: ConversationItemThumbnailState + private var thumbnail: Stub + private var album: Stub + private var shade: ImageView + var footer: Stub + private set + private var cornerMask: CornerMask + private var borderless = false + private var normalBounds: IntArray + private var gifBounds: IntArray + private var minimumThumbnailWidth = 0 + private var maximumThumbnailHeight = 0 + + init { + inflate(context, R.layout.conversation_item_thumbnail, this) + + thumbnail = Stub(findViewById(R.id.thumbnail_view_stub)) + album = Stub(findViewById(R.id.album_view_stub)) + shade = findViewById(R.id.conversation_thumbnail_shade) + footer = Stub(findViewById(R.id.footer_view_stub)) + cornerMask = CornerMask(this) + + var gifWidth = 260.dp + + if (attrs != null) { + val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0) + normalBounds = intArrayOf( + typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0), + typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0), + typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0), + typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0) + ) + + gifWidth = typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_gifWidth, gifWidth) + + typedArray.recycle() + } else { + normalBounds = intArrayOf(0, 0, 0, 0) + } + + gifBounds = intArrayOf( + gifWidth, + gifWidth, + 1, + Int.MAX_VALUE + ) + + minimumThumbnailWidth = -1 + maximumThumbnailHeight = -1 + + state = ConversationItemThumbnailState() + } + + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + if (!borderless) { + cornerMask.mask(canvas) + } + } + + override fun onSaveInstanceState(): Parcelable? { + val root = super.onSaveInstanceState() + return bundleOf( + STATE_ROOT to root, + STATE_STATE to state + ) + } + + override fun onRestoreInstanceState(state: Parcelable) { + if (state is Bundle && state.containsKey(STATE_STATE)) { + val root: Parcelable? = state.getParcelableCompat(STATE_ROOT, Parcelable::class.java) + this.state = state.getParcelableCompat(STATE_STATE, ConversationItemThumbnailState::class.java)!! + super.onRestoreInstanceState(root) + } else { + super.onRestoreInstanceState(state) + } + } + + override fun setFocusable(focusable: Boolean) { + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(focusable = focusable), + albumViewState = state.albumViewState.copy(focusable = focusable) + ) + + state.applyState(thumbnail, album) + } + + override fun setClickable(clickable: Boolean) { + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(clickable = clickable), + albumViewState = state.albumViewState.copy(clickable = clickable) + ) + + state.applyState(thumbnail, album) + } + + override fun setOnLongClickListener(l: OnLongClickListener?) { + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(longClickListener = l), + albumViewState = state.albumViewState.copy(longClickListener = l) + ) + + state.applyState(thumbnail, album) + } + + fun hideThumbnailView() { + state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 0f)) + state.thumbnailViewState.applyState(thumbnail) + } + + fun showThumbnailView() { + state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(alpha = 1f)) + state.thumbnailViewState.applyState(thumbnail) + } + + val corners: Corners + get() = Corners(cornerMask.radii) + + fun showShade(show: Boolean) { + shade.visibility = if (show) VISIBLE else GONE + forceLayout() + } + + fun setCorners(topLeft: Int, topRight: Int, bottomRight: Int, bottomLeft: Int) { + cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft) + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy( + cornerTopLeft = topLeft, + cornerTopRight = topRight, + cornerBottomRight = bottomRight, + cornerBottomLeft = bottomLeft + ), + albumViewState = state.albumViewState.copy( + cornerTopLeft = topLeft, + cornerTopRight = topRight, + cornerBottomRight = bottomRight, + cornerBottomLeft = bottomLeft + ) + ) + + state.applyState(thumbnail, album) + } + + fun setMinimumThumbnailWidth(@Px width: Int) { + minimumThumbnailWidth = width + state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(minWidth = width)) + state.thumbnailViewState.applyState(thumbnail) + } + + fun setMaximumThumbnailHeight(@Px height: Int) { + maximumThumbnailHeight = height + state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(maxHeight = height)) + state.thumbnailViewState.applyState(thumbnail) + } + + fun setBorderless(borderless: Boolean) { + this.borderless = borderless + } + + @UiThread + fun setImageResource( + glideRequests: GlideRequests, + slides: List, + showControls: Boolean, + isPreview: Boolean + ) { + if (slides.size == 1) { + val slide = slides[0] + + if (slide.isVideoGif) { + setThumbnailBounds(gifBounds) + } else { + setThumbnailBounds(normalBounds) + + if (minimumThumbnailWidth != -1) { + state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(minWidth = minimumThumbnailWidth)) + } + + if (maximumThumbnailHeight != -1) { + state = state.copy(thumbnailViewState = state.thumbnailViewState.copy(maxHeight = maximumThumbnailHeight)) + } + } + + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(visibility = VISIBLE), + albumViewState = state.albumViewState.copy(visibility = GONE) + ) + + state.applyState(thumbnail, album) + + val attachment = slides[0].asAttachment() + + thumbnail.get().setImageResource(glideRequests, slides[0], showControls, isPreview, attachment.width, attachment.height) + touchDelegate = thumbnail.get().touchDelegate + } else { + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(visibility = GONE), + albumViewState = state.albumViewState.copy(visibility = VISIBLE) + ) + + state.applyState(thumbnail, album) + album.get().setSlides(glideRequests, slides, showControls) + touchDelegate = album.get().touchDelegate + } + } + + fun setConversationColor(@ColorInt color: Int) { + state = state.copy(albumViewState = state.albumViewState.copy(cellBackgroundColor = color)) + state.albumViewState.applyState(album) + } + + fun setThumbnailClickListener(listener: SlideClickListener?) { + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(clickListener = listener), + albumViewState = state.albumViewState.copy(clickListener = listener) + ) + + state.applyState(thumbnail, album) + } + + fun setDownloadClickListener(listener: SlidesClickedListener?) { + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy(downloadClickListener = listener), + albumViewState = state.albumViewState.copy(downloadClickListener = listener) + ) + + state.applyState(thumbnail, album) + } + + private fun setThumbnailBounds(bounds: IntArray) { + val (minWidth, maxWidth, minHeight, maxHeight) = bounds + state = state.copy( + thumbnailViewState = state.thumbnailViewState.copy( + minWidth = minWidth, + maxWidth = maxWidth, + minHeight = minHeight, + maxHeight = maxHeight + ) + ) + state.thumbnailViewState.applyState(thumbnail) + } + + companion object { + private const val STATE_ROOT = "state.root" + private const val STATE_STATE = "state.state" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt new file mode 100644 index 0000000000..600efa2726 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt @@ -0,0 +1,101 @@ +package org.thoughtcrime.securesms.components + +import android.graphics.Color +import android.os.Parcelable +import android.view.View +import android.view.View.OnLongClickListener +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import org.thoughtcrime.securesms.mms.SlideClickListener +import org.thoughtcrime.securesms.mms.SlidesClickedListener +import org.thoughtcrime.securesms.util.views.Stub + +/** + * Parcelable state object for [ConversationItemThumbnail] + * This allows us to manage inputs for [ThumbnailView] and [AlbumThumbnailView] without + * actually having them inflated. When the views are finally inflated, we 'apply' + */ +@Parcelize +data class ConversationItemThumbnailState( + val thumbnailViewState: ThumbnailViewState = ThumbnailViewState(), + val albumViewState: AlbumViewState = AlbumViewState() +) : Parcelable { + + @Parcelize + data class ThumbnailViewState( + private val alpha: Float = 0f, + private val focusable: Boolean = true, + private val clickable: Boolean = true, + @IgnoredOnParcel + private val clickListener: SlideClickListener? = null, + @IgnoredOnParcel + private val downloadClickListener: SlidesClickedListener? = null, + @IgnoredOnParcel + private val longClickListener: OnLongClickListener? = null, + private val visibility: Int = View.GONE, + private val minWidth: Int = -1, + private val maxWidth: Int = -1, + private val minHeight: Int = -1, + private val maxHeight: Int = -1, + private val cornerTopLeft: Int = 0, + private val cornerTopRight: Int = 0, + private val cornerBottomRight: Int = 0, + private val cornerBottomLeft: Int = 0 + ) : Parcelable { + + fun applyState(thumbnailView: Stub) { + thumbnailView.visibility = visibility + if (visibility == View.GONE) { + return + } + + thumbnailView.get().alpha = alpha + thumbnailView.get().isFocusable = focusable + thumbnailView.get().isClickable = clickable + thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) + thumbnailView.get().setThumbnailClickListener(clickListener) + thumbnailView.get().setDownloadClickListener(downloadClickListener) + thumbnailView.get().setOnLongClickListener(longClickListener) + thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight) + } + } + + @Parcelize + data class AlbumViewState( + private val focusable: Boolean = true, + private val clickable: Boolean = true, + @IgnoredOnParcel + private val clickListener: SlideClickListener? = null, + @IgnoredOnParcel + private val downloadClickListener: SlidesClickedListener? = null, + @IgnoredOnParcel + private val longClickListener: OnLongClickListener? = null, + private val visibility: Int = View.GONE, + private val cellBackgroundColor: Int = Color.TRANSPARENT, + private val cornerTopLeft: Int = 0, + private val cornerTopRight: Int = 0, + private val cornerBottomRight: Int = 0, + private val cornerBottomLeft: Int = 0 + ) : Parcelable { + + fun applyState(albumView: Stub) { + albumView.visibility = visibility + if (visibility == View.GONE) { + return + } + + albumView.get().isFocusable = focusable + albumView.get().isClickable = clickable + albumView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) + albumView.get().setThumbnailClickListener(clickListener) + albumView.get().setDownloadClickListener(downloadClickListener) + albumView.get().setOnLongClickListener(longClickListener) + albumView.get().setCellBackgroundColor(cellBackgroundColor) + } + } + + fun applyState(thumbnailView: Stub, albumView: Stub) { + thumbnailViewState.applyState(thumbnailView) + albumViewState.applyState(albumView) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java index 92d42e8982..8f24938d85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/HidingLinearLayout.java @@ -22,7 +22,6 @@ public HidingLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public HidingLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java index 52671466ab..83830c6171 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java @@ -33,6 +33,7 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.animation.AnimationCompleteListener; +import org.thoughtcrime.securesms.animation.AnimationStartListener; import org.thoughtcrime.securesms.audio.AudioRecordingHandler; import org.thoughtcrime.securesms.components.emoji.EmojiEventListener; import org.thoughtcrime.securesms.components.emoji.EmojiToggle; @@ -573,11 +574,39 @@ private void fadeInNormalComposeViews() { } private void fadeIn(@NonNull View v) { - v.animate().alpha(1).setDuration(FADE_TIME).start(); + v.animate() + .setListener(new AnimationStartListener() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + v.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(@NonNull Animator animation) { + v.setVisibility(View.INVISIBLE); + } + }) + .alpha(1) + .setDuration(FADE_TIME) + .start(); } private void fadeOut(@NonNull View v) { - v.animate().alpha(0).setDuration(FADE_TIME).start(); + v.animate() + .setListener(new AnimationCompleteListener() { + @Override + public void onAnimationEnd(Animator animation) { + v.setVisibility(View.INVISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + v.setVisibility(View.VISIBLE); + } + }) + .alpha(0) + .setDuration(FADE_TIME) + .start(); } private void updateVisibility() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.java index 40e9c6ba6b..a3405ebc3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.java @@ -31,7 +31,6 @@ public InsetAwareConstraintLayout(@NonNull Context context, @Nullable AttributeS } @Override - @TargetApi(20) public WindowInsets onApplyWindowInsets(WindowInsets insets) { if (Build.VERSION.SDK_INT < 30) { return super.onApplyWindowInsets(insets); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java index 242bdde2a2..e2990df284 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java @@ -4,6 +4,8 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; +import android.os.Bundle; +import android.os.Parcelable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -22,6 +24,7 @@ import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.util.views.Stub; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -34,23 +37,27 @@ */ public class LinkPreviewView extends FrameLayout { + private static final String STATE_ROOT = "linkPreviewView.state.root"; + private static final String STATE_STATE = "linkPreviewView.state.state"; + private static final int TYPE_CONVERSATION = 0; private static final int TYPE_COMPOSE = 1; - private ViewGroup container; - private OutlinedThumbnailView thumbnail; - private TextView title; - private TextView description; - private TextView site; - private View divider; - private View closeButton; - private View spinner; - private TextView noPreview; - - private int type; - private int defaultRadius; - private CornerMask cornerMask; - private CloseClickedListener closeClickedListener; + private ViewGroup container; + private Stub thumbnail; + private TextView title; + private TextView description; + private TextView site; + private View divider; + private View closeButton; + private View spinner; + private TextView noPreview; + + private int type; + private int defaultRadius; + private CornerMask cornerMask; + private CloseClickedListener closeClickedListener; + private LinkPreviewViewThumbnailState thumbnailState = new LinkPreviewViewThumbnailState(); public LinkPreviewView(Context context) { super(context); @@ -66,7 +73,7 @@ private void init(@Nullable AttributeSet attrs) { inflate(getContext(), R.layout.link_preview, this); container = findViewById(R.id.linkpreview_container); - thumbnail = findViewById(R.id.linkpreview_thumbnail); + thumbnail = new Stub<>(findViewById(R.id.linkpreview_thumbnail)); title = findViewById(R.id.linkpreview_title); description = findViewById(R.id.linkpreview_description); site = findViewById(R.id.linkpreview_site); @@ -101,6 +108,30 @@ private void init(@Nullable AttributeSet attrs) { setWillNotDraw(false); } + @Override + protected @NonNull Parcelable onSaveInstanceState() { + Parcelable root = super.onSaveInstanceState(); + Bundle bundle = new Bundle(); + + bundle.putParcelable(STATE_ROOT, root); + bundle.putParcelable(STATE_STATE, thumbnailState); + + return bundle; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Parcelable root = ((Bundle) state).getParcelable(STATE_ROOT); + thumbnailState = ((Bundle) state).getParcelable(STATE_STATE); + + thumbnailState.applyState(thumbnail); + super.onRestoreInstanceState(root); + } else { + super.onRestoreInstanceState(state); + } + } + @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); @@ -173,8 +204,9 @@ public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPr if (showThumbnail && linkPreview.getThumbnail().isPresent()) { thumbnail.setVisibility(VISIBLE); - thumbnail.setImageResource(glideRequests, new ImageSlide(getContext(), linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false); - thumbnail.showDownloadText(false); + thumbnailState.applyState(thumbnail); + thumbnail.get().setImageResource(glideRequests, new ImageSlide(getContext(), linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false); + thumbnail.get().showDownloadText(false); } else { thumbnail.setVisibility(GONE); } @@ -183,10 +215,24 @@ public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPr public void setCorners(int topStart, int topEnd) { if (ViewUtil.isRtl(this)) { cornerMask.setRadii(topEnd, topStart, 0, 0); - thumbnail.setCorners(defaultRadius, topEnd, defaultRadius, defaultRadius); + thumbnailState = thumbnailState.copy( + defaultRadius, + topEnd, + defaultRadius, + defaultRadius, + thumbnailState.getDownloadListener() + ); + thumbnailState.applyState(thumbnail); } else { cornerMask.setRadii(topStart, topEnd, 0, 0); - thumbnail.setCorners(topStart, defaultRadius, defaultRadius, defaultRadius); + thumbnailState.copy( + topStart, + defaultRadius, + defaultRadius, + defaultRadius, + thumbnailState.getDownloadListener() + ); + thumbnailState.applyState(thumbnail); } postInvalidate(); } @@ -196,7 +242,8 @@ public void setCloseClickedListener(@Nullable CloseClickedListener closeClickedL } public void setDownloadClickedListener(SlidesClickedListener listener) { - thumbnail.setDownloadClickListener(listener); + thumbnailState = thumbnailState.withDownloadListener(listener); + thumbnailState.applyState(thumbnail); } private @StringRes static int getLinkPreviewErrorString(@Nullable LinkPreviewRepository.Error customError) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt new file mode 100644 index 0000000000..6f18e4e975 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt @@ -0,0 +1,28 @@ +package org.thoughtcrime.securesms.components + +import android.os.Parcelable +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import org.thoughtcrime.securesms.mms.SlidesClickedListener +import org.thoughtcrime.securesms.util.views.Stub + +@Parcelize +data class LinkPreviewViewThumbnailState( + val cornerTopLeft: Int = 0, + val cornerTopRight: Int = 0, + val cornerBottomRight: Int = 0, + val cornerBottomLeft: Int = 0, + @IgnoredOnParcel + val downloadListener: SlidesClickedListener? = null +) : Parcelable { + fun withDownloadListener(downloadListener: SlidesClickedListener?): LinkPreviewViewThumbnailState { + return copy(downloadListener = downloadListener) + } + + fun applyState(thumbnail: Stub) { + if (thumbnail.resolved()) { + thumbnail.get().setCorners(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) + thumbnail.get().setDownloadClickListener(downloadListener) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java b/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java index dda8d30d7c..03b464c2fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/RecentPhotoViewRail.java @@ -133,14 +133,12 @@ public void onBindItemViewHolder(RecentPhotoViewHolder viewHolder, @NonNull Curs } - @TargetApi(16) @SuppressWarnings("SuspiciousNameCombination") private String getWidthColumn(int orientation) { if (orientation == 0 || orientation == 180) return MediaStore.Images.ImageColumns.WIDTH; else return MediaStore.Images.ImageColumns.HEIGHT; } - @TargetApi(16) @SuppressWarnings("SuspiciousNameCombination") private String getHeightColumn(int orientation) { if (orientation == 0 || orientation == 180) return MediaStore.Images.ImageColumns.HEIGHT; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java b/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java index b5160e4fe3..b7cd654867 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java @@ -28,7 +28,7 @@ * fill the bounds with a gradient. * * If you wish to apply clipping to this drawable, it is recommended to either use it with - * a CardView or utilize {@link org.thoughtcrime.securesms.util.CustomDrawWrapperKt#customizeOnDraw(Drawable, Function2)} + * a MaterialCardView or utilize {@link org.thoughtcrime.securesms.util.CustomDrawWrapperKt#customizeOnDraw(Drawable, Function2)} */ public final class RotatableGradientDrawable extends Drawable { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SquareFrameLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/SquareFrameLayout.java index f2cfe5f161..94be5f1e06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/SquareFrameLayout.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SquareFrameLayout.java @@ -23,7 +23,7 @@ public SquareFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } - @TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused") + @SuppressWarnings("unused") public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SquareLinearLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/SquareLinearLayout.java deleted file mode 100644 index cdd2ada357..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/SquareLinearLayout.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.annotation.TargetApi; -import android.content.Context; -import android.util.AttributeSet; -import android.widget.LinearLayout; - -public class SquareLinearLayout extends LinearLayout { - @SuppressWarnings("unused") - public SquareLinearLayout(Context context) { - super(context); - } - - @SuppressWarnings("unused") - public SquareLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @TargetApi(11) @SuppressWarnings("unused") - public SquareLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @TargetApi(21) @SuppressWarnings("unused") - public SquareLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - //noinspection SuspiciousNameCombination - super.onMeasure(widthMeasureSpec, widthMeasureSpec); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java index 7c16f9f8c9..d27a5f1f7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThreadPhotoRailView.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MediaTable; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.util.MediaUtil; @@ -97,7 +98,8 @@ public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Curs } imageView.setOnClickListener(v -> { - if (clickedListener != null) clickedListener.onItemClicked(mediaRecord); + MediaPreviewCache.INSTANCE.setDrawable(imageView.getImageDrawable()); + if (clickedListener != null) clickedListener.onItemClicked(imageView, mediaRecord); }); } @@ -118,6 +120,6 @@ static class ThreadPhotoViewHolder extends RecyclerView.ViewHolder { } public interface OnItemClickedListener { - void onItemClicked(MediaTable.MediaRecord mediaRecord); + void onItemClicked(View itemView, MediaTable.MediaRecord mediaRecord); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java index 851145ee29..a09727f3d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -3,10 +3,12 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -21,10 +23,6 @@ import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.load.resource.bitmap.FitCenter; -import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.Request; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; @@ -44,14 +42,13 @@ import org.thoughtcrime.securesms.stories.StoryTextPostModel; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Util; -import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture; +import org.thoughtcrime.securesms.util.views.Stub; import java.util.Collections; import java.util.Locale; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.ExecutionException; import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; @@ -67,24 +64,25 @@ public class ThumbnailView extends FrameLayout { private static final int MAX_HEIGHT = 3; private final ImageView image; - private final ImageView blurhash; + private final ImageView blurHash; private final View playOverlay; private final View captionIcon; private final AppCompatImageView errorImage; - private OnClickListener parentClickListener; + private OnClickListener parentClickListener; private final int[] dimens = new int[2]; private final int[] bounds = new int[4]; private final int[] measureDimens = new int[2]; - private Optional transferControls = Optional.empty(); - private SlideClickListener thumbnailClickListener = null; - private SlidesClickedListener downloadClickListener = null; - private Slide slide = null; - private BitmapTransformation fit = new CenterCrop(); + private final CornerMask cornerMask; + + private ThumbnailViewTransferControlsState transferControlsState = new ThumbnailViewTransferControlsState(); + private Stub transferControlViewStub; + private SlideClickListener thumbnailClickListener = null; + private SlidesClickedListener downloadClickListener = null; + private Slide slide = null; - private int radius; public ThumbnailView(Context context) { this(context, null); @@ -99,11 +97,13 @@ public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) { inflate(context, R.layout.thumbnail_view, this); - this.image = findViewById(R.id.thumbnail_image); - this.blurhash = findViewById(R.id.thumbnail_blurhash); - this.playOverlay = findViewById(R.id.play_overlay); - this.captionIcon = findViewById(R.id.thumbnail_caption_icon); - this.errorImage = findViewById(R.id.thumbnail_error); + this.image = findViewById(R.id.thumbnail_image); + this.blurHash = findViewById(R.id.thumbnail_blurhash); + this.playOverlay = findViewById(R.id.play_overlay); + this.captionIcon = findViewById(R.id.thumbnail_caption_icon); + this.errorImage = findViewById(R.id.thumbnail_error); + this.cornerMask = new CornerMask(this); + this.transferControlViewStub = new Stub<>(findViewById(R.id.transfer_controls_stub)); super.setOnClickListener(new ThumbnailClickDispatcher()); @@ -113,8 +113,9 @@ public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) { bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0); bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0); bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0); - radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius)); - fit = typedArray.getInt(R.styleable.ThumbnailView_thumbnail_fit, 0) == 1 ? new FitCenter() : new CenterCrop(); + + float radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius)); + cornerMask.setRadius((int) radius); int transparentOverlayColor = typedArray.getColor(R.styleable.ThumbnailView_transparent_overlay_color, -1); if (transparentOverlayColor > 0) { @@ -125,7 +126,8 @@ public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) { typedArray.recycle(); } else { - radius = getResources().getDimensionPixelSize(R.dimen.message_corner_collapse_radius); + float radius = getResources().getDimensionPixelSize(R.dimen.message_corner_collapse_radius); + cornerMask.setRadius((int) radius); image.setColorFilter(null); } } @@ -145,6 +147,7 @@ protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasure MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY)); } + @SuppressWarnings("SpellCheckingInspection") @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); @@ -155,7 +158,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (playOverlayWidth * 2 > getWidth()) { playOverlayScale /= 2; - captionIconScale = 0; + captionIconScale = 0; } playOverlay.setScaleX(playOverlayScale); @@ -165,6 +168,13 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { captionIcon.setScaleY(captionIconScale); } + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + cornerMask.mask(canvas); + } + public void setMinimumThumbnailWidth(@Px int width) { bounds[MIN_WIDTH] = width; invalidate(); @@ -186,7 +196,7 @@ private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds } if (dimensAreInvalid || dimensFilledCount == 0 || boundsFilledCount == 0) { - targetDimens[WIDTH] = 0; + targetDimens[WIDTH] = 0; targetDimens[HEIGHT] = 0; return; } @@ -218,10 +228,10 @@ private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds if (maxWidthRatio > 1 || maxHeightRatio > 1) { if (maxWidthRatio >= maxHeightRatio) { - measuredWidth /= maxWidthRatio; + measuredWidth /= maxWidthRatio; measuredHeight /= maxWidthRatio; } else { - measuredWidth /= maxHeightRatio; + measuredWidth /= maxHeightRatio; measuredHeight /= maxHeightRatio; } @@ -230,10 +240,10 @@ private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds } else if (minWidthRatio < 1 || minHeightRatio < 1) { if (minWidthRatio <= minHeightRatio) { - measuredWidth /= minWidthRatio; + measuredWidth /= minWidthRatio; measuredHeight /= minWidthRatio; } else { - measuredWidth /= minHeightRatio; + measuredWidth /= minHeightRatio; measuredHeight /= minHeightRatio; } @@ -246,9 +256,9 @@ private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds targetDimens[HEIGHT] = (int) measuredHeight; } - private int getNonZeroCount(int[] vals) { + private int getNonZeroCount(int[] values) { int count = 0; - for (int val : vals) { + for (int val : values) { if (val > 0) { count++; } @@ -264,20 +274,19 @@ public void setOnClickListener(OnClickListener l) { @Override public void setFocusable(boolean focusable) { super.setFocusable(focusable); - if (transferControls.isPresent()) transferControls.get().setFocusable(focusable); + transferControlsState = transferControlsState.withFocusable(focusable); + transferControlsState.applyState(transferControlViewStub); } @Override public void setClickable(boolean clickable) { super.setClickable(clickable); - if (transferControls.isPresent()) transferControls.get().setClickable(clickable); + transferControlsState = transferControlsState.withClickable(clickable); + transferControlsState.applyState(transferControlViewStub); } - private TransferControlView getTransferControls() { - if (!transferControls.isPresent()) { - transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub)); - } - return transferControls.get(); + public @Nullable Drawable getImageDrawable() { + return image.getDrawable(); } public void setBounds(int minWidth, int maxWidth, int minHeight, int maxHeight) { @@ -291,10 +300,10 @@ public void setBounds(int minWidth, int maxWidth, int minHeight, int maxHeight) public void setImageDrawable(@NonNull GlideRequests glideRequests, @Nullable Drawable drawable) { glideRequests.clear(image); - glideRequests.clear(blurhash); + glideRequests.clear(blurHash); image.setImageDrawable(drawable); - blurhash.setImageDrawable(null); + blurHash.setImageDrawable(null); } @UiThread @@ -312,11 +321,11 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe if (slide.asAttachment().isPermanentlyFailed()) { this.slide = slide; - transferControls.ifPresent(c -> c.setVisibility(View.GONE)); + transferControlViewStub.setVisibility(View.GONE); playOverlay.setVisibility(View.GONE); - glideRequests.clear(blurhash); - blurhash.setImageDrawable(null); + glideRequests.clear(blurHash); + blurHash.setImageDrawable(null); glideRequests.clear(image); image.setImageDrawable(null); @@ -338,10 +347,18 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe } if (showControls) { - getTransferControls().setSlide(slide); - getTransferControls().setDownloadClickListener(new DownloadClickDispatcher()); - } else if (transferControls.isPresent()) { - getTransferControls().setVisibility(View.GONE); + int transferState = TransferControlView.getTransferState(Collections.singletonList(slide)); + if (transferState == AttachmentTable.TRANSFER_PROGRESS_DONE || transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE) { + transferControlViewStub.setVisibility(View.GONE); + } else { + transferControlViewStub.setVisibility(View.VISIBLE); + } + + transferControlsState = transferControlsState.withSlide(slide) + .withDownloadClickListener(new DownloadClickDispatcher()); + transferControlsState.applyState(transferControlViewStub); + } else { + transferControlViewStub.setVisibility(View.GONE); } if (slide.getUri() != null && slide.hasPlayOverlay() && @@ -357,7 +374,7 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe return new SettableFuture<>(false); } - if (this.slide != null && this.slide.getFastPreflightId() != null && + if (this.slide != null && this.slide.getFastPreflightId() != null && (!slide.hasVideo() || Util.equals(this.slide.getUri(), slide.getUri())) && Util.equals(this.slide.getFastPreflightId(), slide.getFastPreflightId())) { @@ -370,7 +387,7 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe + ", progress " + slide.getTransferState() + ", fast preflight id: " + slide.asAttachment().getFastPreflightId()); - BlurHash previousBlurhash = this.slide != null ? this.slide.getPlaceholderBlur() : null; + BlurHash previousBlurHash = this.slide != null ? this.slide.getPlaceholderBlur() : null; this.slide = slide; @@ -384,19 +401,19 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe SettableFuture result = new SettableFuture<>(); boolean resultHandled = false; - if (slide.hasPlaceholder() && (previousBlurhash == null || !Objects.equals(slide.getPlaceholderBlur(), previousBlurhash))) { - buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideBitmapListeningTarget(blurhash, result)); + if (slide.hasPlaceholder() && (previousBlurHash == null || !Objects.equals(slide.getPlaceholderBlur(), previousBlurHash))) { + buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideBitmapListeningTarget(blurHash, result)); resultHandled = true; } else if (!slide.hasPlaceholder()) { - glideRequests.clear(blurhash); - blurhash.setImageDrawable(null); + glideRequests.clear(blurHash); + blurHash.setImageDrawable(null); } if (slide.getUri() != null) { if (!MediaUtil.isJpegType(slide.getContentType()) && !MediaUtil.isVideoType(slide.getContentType())) { SettableFuture thumbnailFuture = new SettableFuture<>(); thumbnailFuture.deferTo(result); - thumbnailFuture.addListener(new BlurhashClearListener(glideRequests, blurhash)); + thumbnailFuture.addListener(new BlurHashClearListener(glideRequests, blurHash)); } buildThumbnailGlideRequest(glideRequests, slide).into(new GlideDrawableListeningTarget(image, result)); @@ -425,7 +442,7 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height, boolean animate, @Nullable ThumbnailRequestListener listener) { SettableFuture future = new SettableFuture<>(); - if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); + transferControlViewStub.setVisibility(View.GONE); GlideRequest request = glideRequests.load(new DecryptableUri(uri)) .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -439,15 +456,9 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe request = request.override(width, height); } - if (radius > 0) { - request = request.transforms(new CenterCrop(), new RoundedCorners(radius)); - } else { - request = request.transforms(new CenterCrop()); - } - - GlideDrawableListeningTarget target = new GlideDrawableListeningTarget(image, future); - Request previousRequest = target.getRequest(); - boolean previousRequestRunning = previousRequest != null && previousRequest.isRunning(); + GlideDrawableListeningTarget target = new GlideDrawableListeningTarget(image, future); + Request previousRequest = target.getRequest(); + boolean previousRequestRunning = previousRequest != null && previousRequest.isRunning(); request.into(target); if (listener != null) { listener.onLoadScheduled(); @@ -456,7 +467,7 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe } } - blurhash.setImageDrawable(null); + blurHash.setImageDrawable(null); return future; } @@ -464,25 +475,19 @@ public ListenableFuture setImageResource(@NonNull GlideRequests glideRe public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull StoryTextPostModel model, int width, int height) { SettableFuture future = new SettableFuture<>(); - if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); + transferControlViewStub.setVisibility(View.GONE); - GlideRequest request = glideRequests.load(model) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .placeholder(model.getPlaceholder()) - .transition(withCrossFade()); + GlideRequest request = glideRequests.load(model) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .placeholder(model.getPlaceholder()) + .transition(withCrossFade()); if (width > 0 && height > 0) { request = request.override(width, height); } - if (radius > 0) { - request = request.transforms(new CenterCrop(), new RoundedCorners(radius)); - } else { - request = request.transforms(new CenterCrop()); - } - request.into(new GlideDrawableListeningTarget(image, future)); - blurhash.setImageDrawable(null); + blurHash.setImageDrawable(null); return future; } @@ -499,42 +504,54 @@ public void clear(GlideRequests glideRequests) { glideRequests.clear(image); image.setImageDrawable(null); - if (transferControls.isPresent()) { - getTransferControls().clear(); + if (transferControlViewStub.resolved()) { + transferControlViewStub.get().clear(); } - glideRequests.clear(blurhash); - blurhash.setImageDrawable(null); + glideRequests.clear(blurHash); + blurHash.setImageDrawable(null); slide = null; } public void showDownloadText(boolean showDownloadText) { - getTransferControls().setShowDownloadText(showDownloadText); + transferControlsState = transferControlsState.withDownloadText(showDownloadText); + transferControlsState.applyState(transferControlViewStub); } public void showProgressSpinner() { - getTransferControls().showProgressSpinner(); + transferControlViewStub.get().showProgressSpinner(); } - public void setFit(@NonNull BitmapTransformation fit) { - this.fit = fit; + public void setScaleType(@NonNull ImageView.ScaleType scaleType) { + image.setScaleType(scaleType); } protected void setRadius(int radius) { - this.radius = radius; + cornerMask.setRadius(radius); + invalidate(); + } + + public void setRadii(int topLeft, int topRight, int bottomRight, int bottomLeft) { + cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft); + invalidate(); } - private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { - GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getUri())) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .transition(withCrossFade()), fit); + private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { + GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(Objects.requireNonNull(slide.getUri()))) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .transition(withCrossFade())); - if (slide.isInProgress()) return request; - else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)); + boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23; + + if (slide.isInProgress() || doNotShowMissingThumbnailImage) { + return request; + } else { + return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)); + } } - private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { + private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { GlideRequest bitmap = glideRequests.asBitmap(); BlurHash placeholderBlur = slide.getPlaceholderBlur(); @@ -544,10 +561,10 @@ private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glide bitmap = bitmap.load(slide.getPlaceholderRes(getContext().getTheme())); } - return applySizing(bitmap.diskCacheStrategy(DiskCacheStrategy.NONE), new CenterCrop()); + return applySizing(bitmap.diskCacheStrategy(DiskCacheStrategy.NONE)); } - private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapTransformation fitting) { + private GlideRequest applySizing(@NonNull GlideRequest request) { int[] size = new int[2]; fillTargetDimensions(size, dimens, bounds); if (size[WIDTH] == 0 && size[HEIGHT] == 0) { @@ -555,13 +572,7 @@ private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapT size[HEIGHT] = getDefaultHeight(); } - request = request.override(size[WIDTH], size[HEIGHT]); - - if (radius > 0) { - return request.transforms(fitting, new RoundedCorners(radius)); - } else { - return request.transforms(fitting); - } + return request.override(size[WIDTH], size[HEIGHT]); } private int getDefaultWidth() { @@ -582,6 +593,7 @@ private int getDefaultHeight() { public interface ThumbnailRequestListener extends RequestListener { void onLoadCanceled(); + void onLoadScheduled(); } @@ -609,31 +621,31 @@ public void onClick(View view) { if (downloadClickListener != null && slide != null) { downloadClickListener.onClick(view, Collections.singletonList(slide)); } else { - Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + String.valueOf(slide) + " downloadClickListener: " + String.valueOf(downloadClickListener)); + Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + slide + " downloadClickListener: " + downloadClickListener); } } } - private static class BlurhashClearListener implements ListenableFuture.Listener { + private static class BlurHashClearListener implements ListenableFuture.Listener { private final GlideRequests glideRequests; - private final ImageView blurhash; + private final ImageView blurHash; - private BlurhashClearListener(@NonNull GlideRequests glideRequests, @NonNull ImageView blurhash) { + private BlurHashClearListener(@NonNull GlideRequests glideRequests, @NonNull ImageView blurHash) { this.glideRequests = glideRequests; - this.blurhash = blurhash; + this.blurHash = blurHash; } @Override public void onSuccess(Boolean result) { - glideRequests.clear(blurhash); - blurhash.setImageDrawable(null); + glideRequests.clear(blurHash); + blurHash.setImageDrawable(null); } @Override public void onFailure(ExecutionException e) { - glideRequests.clear(blurhash); - blurhash.setImageDrawable(null); + glideRequests.clear(blurHash); + blurHash.setImageDrawable(null); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt new file mode 100644 index 0000000000..9973fee2df --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.components + +import android.view.View.OnClickListener +import org.thoughtcrime.securesms.mms.Slide +import org.thoughtcrime.securesms.util.views.Stub + +/** + * State object for transfer controls. + */ +data class ThumbnailViewTransferControlsState( + val isFocusable: Boolean = true, + val isClickable: Boolean = true, + val slide: Slide? = null, + val downloadClickedListener: OnClickListener? = null, + val showDownloadText: Boolean = true +) { + + fun withFocusable(isFocusable: Boolean): ThumbnailViewTransferControlsState = copy(isFocusable = isFocusable) + fun withClickable(isClickable: Boolean): ThumbnailViewTransferControlsState = copy(isClickable = isClickable) + fun withSlide(slide: Slide?): ThumbnailViewTransferControlsState = copy(slide = slide) + fun withDownloadClickListener(downloadClickedListener: OnClickListener): ThumbnailViewTransferControlsState = copy(downloadClickedListener = downloadClickedListener) + fun withDownloadText(showDownloadText: Boolean): ThumbnailViewTransferControlsState = copy(showDownloadText = showDownloadText) + + fun applyState(transferControlView: Stub) { + if (transferControlView.resolved()) { + transferControlView.get().isFocusable = isFocusable + transferControlView.get().isClickable = isClickable + if (slide != null) { + transferControlView.get().setSlide(slide) + } + transferControlView.get().setDownloadClickListener(downloadClickedListener) + transferControlView.get().setShowDownloadText(showDownloadText) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TimeDurationPickerDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/components/TimeDurationPickerDialog.kt new file mode 100644 index 0000000000..70acfed324 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TimeDurationPickerDialog.kt @@ -0,0 +1,104 @@ +package org.thoughtcrime.securesms.components + +import android.app.Dialog +import android.os.Bundle +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.setFragmentResult +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.databinding.TimeDurationPickerDialogBinding +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +/** + * Time duration dialog for selection a duration of hours and minutes. Currently + * designed specifically for screen lock but could easily be generalized in the future + * if needed. + * + * Uses [setFragmentResult] to pass the provided duration back in milliseconds. + */ +class TimeDurationPickerDialog : DialogFragment(), NumericKeyboardView.Listener { + + private var _binding: TimeDurationPickerDialogBinding? = null + private val binding: TimeDurationPickerDialogBinding + get() = _binding!! + + private var duration: String = "0000000" + private var full: Boolean = false + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + _binding = TimeDurationPickerDialogBinding.inflate(layoutInflater) + + binding.durationKeyboard.listener = this + + setDuration(requireArguments().getLong(ARGUMENT_DURATION_SECONDS).seconds) + + return MaterialAlertDialogBuilder(requireContext()) + .setView(binding.root) + .setPositiveButton(R.string.TimeDurationPickerDialog_positive_button) { _, _ -> + setFragmentResult( + RESULT_DURATION, + bundleOf( + RESULT_KEY_DURATION_SECONDS to getDuration().inWholeSeconds + ) + ) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + override fun onKeyPress(keyCode: Int) { + if (full && keyCode != -1) { + return + } + + duration = if (keyCode == -1) { + "0" + duration.substring(0, 6) + } else { + duration.substring(1) + keyCode + } + + updateDuration() + } + + private fun updateDuration() { + binding.durationHour.text = duration.substring(0, 3) + binding.durationMinute.text = duration.substring(3, 5) + binding.durationSecond.text = duration.substring(5, 7) + full = duration.toInt() > 1000000 + } + + private fun setDuration(duration: Duration) { + duration.toComponents { hours, minutes, seconds, _ -> + this.duration = String.format("%03d%02d%02d", hours, minutes, seconds) + } + updateDuration() + } + + private fun getDuration(): Duration { + val hours = duration.substring(0, 3).toInt() + val minutes = duration.substring(3, 5).toInt() + val seconds = duration.substring(5, 7).toInt() + + return hours.hours.plus(minutes.minutes).plus(seconds.seconds).coerceAtMost(30.days) + } + + companion object { + const val RESULT_DURATION = "RESULT_DURATION" + const val RESULT_KEY_DURATION_SECONDS = "RESULT_KEY_DURATION_SECONDS" + + private const val ARGUMENT_DURATION_SECONDS = "ARGUMENT_DURATION_SECONDS" + + fun create(duration: Duration): TimeDurationPickerDialog { + return TimeDurationPickerDialog().apply { + arguments = bundleOf( + ARGUMENT_DURATION_SECONDS to duration.inWholeSeconds + ) + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java index 0deb3b2a85..9ba9d25484 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java @@ -182,7 +182,7 @@ private boolean isUpdateToExistingSet(@NonNull List slides) { return true; } - private int getTransferState(@NonNull List slides) { + static int getTransferState(@NonNull List slides) { int transferState = AttachmentTable.TRANSFER_PROGRESS_DONE; boolean allFailed = true; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index 45c4002e5f..d5fb7630ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -329,9 +329,13 @@ private void ellipsizeEmojiTextForMaxLines() { .append(Optional.ofNullable(overflowText).orElse("")); EmojiParser.CandidateList newCandidates = isInEditMode() ? null : EmojiProvider.getCandidates(newContent); - CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji); - super.setText(emojified, BufferType.SPANNABLE); + if (useSystemEmoji || newCandidates == null || newCandidates.size() == 0) { + super.setText(newContent, BufferType.SPANNABLE); + } else { + CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, isJumbomoji || forceJumboEmoji); + super.setText(emojified, BufferType.SPANNABLE); + } } else if (maxLength > 0) { ellipsizeAnyTextForMaxLength(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/location/SignalMapView.java b/app/src/main/java/org/thoughtcrime/securesms/components/location/SignalMapView.java index 9d3d5cd19f..26e7e39168 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/location/SignalMapView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/location/SignalMapView.java @@ -38,7 +38,6 @@ public SignalMapView(Context context, AttributeSet attrs) { initialize(context); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public SignalMapView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialize(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt index 2b7bdfe951..a7fd9758ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt @@ -11,5 +11,5 @@ data class ActionItem @JvmOverloads constructor( @DrawableRes val iconRes: Int, val title: CharSequence, @ColorRes val tintRes: Int = R.color.signal_colorOnSurface, - val action: Runnable, + val action: Runnable ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt index ee9e240a54..4d5e01f759 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt @@ -65,7 +65,7 @@ class ContextMenuList(recyclerView: RecyclerView, onItemClick: () -> Unit) { private class ItemViewHolder( itemView: View, - private val onItemClick: () -> Unit, + private val onItemClick: () -> Unit ) : MappingViewHolder(itemView) { val icon: ImageView = itemView.findViewById(R.id.signal_context_menu_item_icon) val title: TextView = itemView.findViewById(R.id.signal_context_menu_item_title) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt index 650a0d5b31..b01a3acc93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/SignalContextMenu.kt @@ -36,7 +36,7 @@ class SignalContextMenu private constructor( private val contextMenuList = ContextMenuList( recyclerView = contentView.findViewById(R.id.signal_context_menu_list), - onItemClick = { dismiss() }, + onItemClick = { dismiss() } ) init { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/registration/ActionCountDownButton.kt b/app/src/main/java/org/thoughtcrime/securesms/components/registration/ActionCountDownButton.kt index 0c9994ce0f..703f359948 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/registration/ActionCountDownButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/registration/ActionCountDownButton.kt @@ -2,8 +2,8 @@ package org.thoughtcrime.securesms.components.registration import android.content.Context import android.util.AttributeSet +import androidx.annotation.StringRes import com.google.android.material.button.MaterialButton -import org.thoughtcrime.securesms.R import java.util.concurrent.TimeUnit class ActionCountDownButton @JvmOverloads constructor( @@ -11,6 +11,12 @@ class ActionCountDownButton @JvmOverloads constructor( attrs: AttributeSet? = null, defStyle: Int = 0 ) : MaterialButton(context, attrs, defStyle) { + @StringRes + private var enabledText = 0 + + @StringRes + private var disabledText = 0 + private var countDownToTime: Long = 0 private var listener: Listener? = null @@ -24,8 +30,8 @@ class ActionCountDownButton @JvmOverloads constructor( } } - fun setCallEnabled() { - setText(R.string.RegistrationActivity_call) + private fun setActionEnabled() { + setText(enabledText) isEnabled = true alpha = 1.0f } @@ -38,11 +44,11 @@ class ActionCountDownButton @JvmOverloads constructor( val totalRemainingSeconds = TimeUnit.MILLISECONDS.toSeconds(remainingMillis).toInt() val minutesRemaining = totalRemainingSeconds / 60 val secondsRemaining = totalRemainingSeconds % 60 - text = resources.getString(R.string.RegistrationActivity_call_me_instead_available_in, minutesRemaining, secondsRemaining) + text = resources.getString(disabledText, minutesRemaining, secondsRemaining) listener?.onRemaining(this, totalRemainingSeconds) postDelayed({ updateCountDown() }, 250) } else { - setCallEnabled() + setActionEnabled() } } @@ -50,6 +56,11 @@ class ActionCountDownButton @JvmOverloads constructor( this.listener = listener } + fun setTextResources(@StringRes enabled: Int, @StringRes disabled: Int) { + enabledText = enabled + disabledText = disabled + } + interface Listener { fun onRemaining(view: ActionCountDownButton, secondsRemaining: Int) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/registration/VerificationCodeView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/registration/VerificationCodeView.kt index 3e449b677c..395d2b1a12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/registration/VerificationCodeView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/registration/VerificationCodeView.kt @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.components.registration import android.content.Context +import android.text.Editable +import android.text.TextWatcher import android.util.AttributeSet import android.widget.FrameLayout import com.google.android.material.textfield.TextInputLayout @@ -9,6 +11,7 @@ import org.thoughtcrime.securesms.R class VerificationCodeView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { private val containers: MutableList = ArrayList(6) + private val textWatcher = PasteTextWatcher() private var listener: OnCodeEnteredListener? = null private var index = 0 @@ -22,6 +25,7 @@ class VerificationCodeView @JvmOverloads constructor(context: Context, attrs: At containers.add(findViewById(R.id.container_five)) containers.forEach { it.editText?.showSoftInputOnFocus = false } + containers.forEach { it.editText?.addTextChangedListener(textWatcher) } } fun setOnCompleteListener(listener: OnCodeEnteredListener?) { @@ -41,18 +45,46 @@ class VerificationCodeView @JvmOverloads constructor(context: Context, attrs: At } fun delete() { - if (index <= 0) return - containers[--index].editText?.setText("") + if (index < 0) return + val editText = if (index == 0) containers[index].editText else containers[--index].editText + editText?.setText("") + containers[index].editText?.requestFocus() } fun clear() { if (index != 0) { containers.forEach { it.editText?.setText("") } index = 0 + containers[index].editText?.requestFocus() } } interface OnCodeEnteredListener { fun onCodeComplete(code: String) } + + inner class PasteTextWatcher : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable?) { + if (s == null) { + return + } + + if (s.length > 1) { + val enteredText = s.toList() + enteredText.forEach { + val castInt = it.digitToIntOrNull() + if (castInt == null) { + s.clear() + return@forEach + } else { + append(castInt) + } + } + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UsernameOutOfSyncReminder.kt b/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UsernameOutOfSyncReminder.kt new file mode 100644 index 0000000000..fa7b36825c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UsernameOutOfSyncReminder.kt @@ -0,0 +1,36 @@ +package org.thoughtcrime.securesms.components.reminder + +import android.content.Context +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.FeatureFlags + +/** + * Displays a reminder message when the local username gets out of sync with + * what the server thinks our username is. + */ +class UsernameOutOfSyncReminder(context: Context) : Reminder( + null, + context.getString(R.string.UsernameOutOfSyncReminder__something_went_wrong) +) { + + init { + addAction( + Action( + context.getString(R.string.UsernameOutOfSyncReminder__fix_now), + R.id.reminder_action_fix_username + ) + ) + } + + override fun isDismissable(): Boolean { + return false + } + + companion object { + @JvmStatic + fun isEligible(): Boolean { + return FeatureFlags.usernames() && SignalStore.phoneNumberPrivacy().isUsernameOutOfSync + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/SegmentedProgressBar.kt b/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/SegmentedProgressBar.kt index af2acd54cb..a572f6fe46 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/SegmentedProgressBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/SegmentedProgressBar.kt @@ -211,6 +211,7 @@ class SegmentedProgressBar : View, ViewPager.OnPageChangeListener, View.OnTouchL corners[6] = radius.toFloat() corners[7] = radius.toFloat() } + segments.lastIndex -> { corners.indices.forEach { corners[it] = 0f } corners[2] = radius.toFloat() @@ -227,6 +228,7 @@ class SegmentedProgressBar : View, ViewPager.OnPageChangeListener, View.OnTouchL path.addRoundRect(rectangle, corners, Path.Direction.CW) canvas?.drawPath(path, drawingComponents.second[drawingIndex]) } + else -> canvas?.drawRect( rectangle, drawingComponents.second[drawingIndex] @@ -325,12 +327,14 @@ class SegmentedProgressBar : View, ViewPager.OnPageChangeListener, View.OnTouchL segments.mapIndexed { index, segment -> if (offset > 0) { - if (index < nextSegmentIndex) segment.animationState = - Segment.AnimationState.ANIMATED + if (index < nextSegmentIndex) { + segment.animationState = Segment.AnimationState.ANIMATED + } } else if (offset < 0) { - if (index > nextSegmentIndex - 1) segment.animationState = - Segment.AnimationState.IDLE - } else if (offset == 0) { + if (index > nextSegmentIndex - 1) { + segment.animationState = Segment.AnimationState.IDLE + } + } else { if (index == nextSegmentIndex) segment.animationState = Segment.AnimationState.IDLE } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/Utils.kt b/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/Utils.kt index bd8974b694..cf2b888a0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/Utils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/segmentedprogressbar/Utils.kt @@ -38,7 +38,6 @@ fun SegmentedProgressBar.getDrawingComponents( segment: Segment, segmentIndex: Int ): Pair, MutableList> { - val rectangles = mutableListOf() val paints = mutableListOf() val segmentWidth = segmentWidth diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt index 136427f88c..6ef032ccfb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings import android.os.Bundle import androidx.activity.OnBackPressedCallback +import androidx.annotation.Discouraged import androidx.navigation.NavController import androidx.navigation.Navigation import androidx.navigation.fragment.NavHostFragment @@ -10,6 +11,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme import org.thoughtcrime.securesms.util.DynamicTheme +@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") open class DSLSettingsActivity : PassphraseRequiredActivity() { protected open val dynamicTheme: DynamicTheme = DynamicNoActionBarTheme() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt index 3fda7366b3..06a679123e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt @@ -10,6 +10,7 @@ import android.widget.ImageView import android.widget.RadioButton import android.widget.TextView import androidx.annotation.CallSuper +import androidx.annotation.Discouraged import androidx.core.content.ContextCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.switchmaterial.SwitchMaterial @@ -27,6 +28,7 @@ import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder import org.thoughtcrime.securesms.util.views.LearnMoreTextView import org.thoughtcrime.securesms.util.visible +@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") class DSLSettingsAdapter : MappingAdapter() { init { registerFactory(ClickPreference::class.java, LayoutFactory(::ClickPreferenceViewHolder, R.layout.dsl_preference_item)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt index 6a69b7abb4..564dcedf43 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EdgeEffect +import androidx.annotation.Discouraged import androidx.annotation.LayoutRes import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager @@ -14,6 +15,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.util.WindowUtil +@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") abstract class DSLSettingsBottomSheetFragment( @LayoutRes private val layoutId: Int = R.layout.dsl_settings_bottom_sheet, val layoutManagerProducer: (Context) -> RecyclerView.LayoutManager = { context -> LinearLayoutManager(context) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt index d20b4d027b..36dba7a561 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import android.widget.EdgeEffect import androidx.annotation.CallSuper +import androidx.annotation.Discouraged import androidx.annotation.LayoutRes import androidx.annotation.MenuRes import androidx.annotation.StringRes @@ -19,6 +20,7 @@ import org.thoughtcrime.securesms.util.Material3OnScrollHelper import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import java.lang.UnsupportedOperationException +@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") abstract class DSLSettingsFragment( @StringRes private val titleId: Int = -1, @MenuRes private val menuId: Int = -1, @@ -86,8 +88,9 @@ abstract class DSLSettingsFragment( } override fun onDestroyView() { - super.onDestroyView() recyclerView = null + toolbar = null + super.onDestroyView() } fun setTitle(@StringRes resId: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsIcon.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsIcon.kt index fb0b2431ef..69f4bb0d25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsIcon.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsIcon.kt @@ -32,7 +32,7 @@ sealed class DSLSettingsIcon { @ColorRes private val iconTintId: Int, @DrawableRes private val backgroundId: Int, @ColorRes private val backgroundTint: Int, - @Px private val insetPx: Int, + @Px private val insetPx: Int ) : DSLSettingsIcon() { override fun resolve(context: Context): Drawable { return LayerDrawable( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt index a315b84e76..cfc48450a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt @@ -53,6 +53,7 @@ class AppSettingsActivity : DSLSettingsActivity() { EditNotificationProfileScheduleFragmentArgs.fromBundle(intent.getBundleExtra(START_ARGUMENTS)!!).profileId ) StartLocation.PRIVACY -> AppSettingsFragmentDirections.actionDirectToPrivacy() + StartLocation.LINKED_DEVICES -> AppSettingsFragmentDirections.actionDirectToDevices() } } @@ -163,6 +164,9 @@ class AppSettingsActivity : DSLSettingsActivity() { .putExtra(START_ARGUMENTS, arguments) } + @JvmStatic + fun linkedDevices(context: Context): Intent = getIntentForStartLocation(context, StartLocation.LINKED_DEVICES) + private fun getIntentForStartLocation(context: Context, startLocation: StartLocation): Intent { return Intent(context, AppSettingsActivity::class.java) .putExtra(ARG_NAV_GRAPH, R.navigation.app_settings) @@ -183,7 +187,8 @@ class AppSettingsActivity : DSLSettingsActivity() { NOTIFICATION_PROFILES(9), CREATE_NOTIFICATION_PROFILE(10), NOTIFICATION_PROFILE_DETAILS(11), - PRIVACY(12); + PRIVACY(12), + LINKED_DEVICES(13); companion object { fun fromCode(code: Int?): StartLocation { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt index 4e83bebcdb..5d6b1abf90 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt @@ -38,7 +38,6 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men private fun getConfiguration(state: AppSettingsState): DSLConfiguration { return configure { - customPref( BioPreference(state.self) { findNavController().safeNavigate(R.id.action_appSettingsFragment_to_manageProfileActivity) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt index 4bbf337856..14381f8b18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt @@ -41,7 +41,6 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag private fun getConfiguration(state: AccountSettingsState): DSLConfiguration { return configure { - sectionHeaderPref(R.string.preferences_app_protection__signal_pin) @Suppress("DEPRECATION") diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt index 09576e8be0..5b3b6326ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt @@ -45,7 +45,7 @@ class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_num task.addOnSuccessListener { Log.i(TAG, "Successfully registered SMS listener.") - navigateToVerify() + navigateToVerify(smsListenerEnabled = true) } task.addOnFailureListener { e -> @@ -57,8 +57,8 @@ class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_num } } - private fun navigateToVerify() { - findNavController().safeNavigate(R.id.action_changePhoneNumberConfirmFragment_to_changePhoneNumberVerifyFragment) + private fun navigateToVerify(smsListenerEnabled: Boolean = false) { + findNavController().safeNavigate(R.id.action_changePhoneNumberConfirmFragment_to_changePhoneNumberVerifyFragment, ChangeNumberVerifyFragmentArgs.Builder().setSmsListenerEnabled(smsListenerEnabled).build().toBundle()) } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt index 65609ac218..f7f32563b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterPhoneNumberFragment.kt @@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.LabeledEditText import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getViewModel import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberViewModel.ContinueStatus +import org.thoughtcrime.securesms.databinding.FragmentChangeNumberEnterPhoneNumberBinding import org.thoughtcrime.securesms.registration.fragments.CountryPickerFragment import org.thoughtcrime.securesms.registration.fragments.CountryPickerFragmentArgs import org.thoughtcrime.securesms.registration.util.ChangeNumberInputController @@ -25,19 +26,30 @@ private const val NEW_NUMBER_COUNTRY_SELECT = "new_number_country" class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_change_number_enter_phone_number) { - private lateinit var scrollView: ScrollView + private var binding: FragmentChangeNumberEnterPhoneNumberBinding? = null - private lateinit var oldNumberCountrySpinner: Spinner - private lateinit var oldNumberCountryCode: LabeledEditText - private lateinit var oldNumber: LabeledEditText + private val scrollView: ScrollView + get() = binding!!.changeNumberEnterPhoneNumberScroll - private lateinit var newNumberCountrySpinner: Spinner - private lateinit var newNumberCountryCode: LabeledEditText - private lateinit var newNumber: LabeledEditText + private val oldNumberCountrySpinner: Spinner + get() = binding!!.changeNumberEnterPhoneNumberOldNumberSpinner + private val oldNumberCountryCode: LabeledEditText + get() = binding!!.changeNumberEnterPhoneNumberOldNumberCountryCode + private val oldNumber: LabeledEditText + get() = binding!!.changeNumberEnterPhoneNumberOldNumberNumber + + private val newNumberCountrySpinner: Spinner + get() = binding!!.changeNumberEnterPhoneNumberNewNumberSpinner + private val newNumberCountryCode: LabeledEditText + get() = binding!!.changeNumberEnterPhoneNumberNewNumberCountryCode + private val newNumber: LabeledEditText + get() = binding!!.changeNumberEnterPhoneNumberNewNumberNumber private lateinit var viewModel: ChangeNumberViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding = FragmentChangeNumberEnterPhoneNumberBinding.bind(view) + viewModel = getViewModel(this) val toolbar: Toolbar = view.findViewById(R.id.toolbar) @@ -48,12 +60,6 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c onContinue() } - scrollView = view.findViewById(R.id.change_number_enter_phone_number_scroll) - - oldNumberCountrySpinner = view.findViewById(R.id.change_number_enter_phone_number_old_number_spinner) - oldNumberCountryCode = view.findViewById(R.id.change_number_enter_phone_number_old_number_country_code) - oldNumber = view.findViewById(R.id.change_number_enter_phone_number_old_number_number) - val oldController = ChangeNumberInputController( requireContext(), oldNumberCountryCode, @@ -87,10 +93,6 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c } ) - newNumberCountrySpinner = view.findViewById(R.id.change_number_enter_phone_number_new_number_spinner) - newNumberCountryCode = view.findViewById(R.id.change_number_enter_phone_number_new_number_country_code) - newNumber = view.findViewById(R.id.change_number_enter_phone_number_new_number_number) - val newController = ChangeNumberInputController( requireContext(), newNumberCountryCode, @@ -136,6 +138,11 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c viewModel.getLiveNewNumber().observe(viewLifecycleOwner, newController::updateNumber) } + override fun onDestroyView() { + binding = null + super.onDestroyView() + } + private fun onContinue() { if (TextUtils.isEmpty(oldNumberCountryCode.text)) { Toast.makeText(context, getString(R.string.ChangeNumberEnterPhoneNumberFragment__you_must_specify_your_old_number_country_code), Toast.LENGTH_LONG).show() @@ -161,7 +168,9 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c ContinueStatus.CAN_CONTINUE -> findNavController().safeNavigate(R.id.action_enterPhoneNumberChangeFragment_to_changePhoneNumberConfirmFragment) ContinueStatus.INVALID_NUMBER -> { Dialogs.showAlertDialog( - context, getString(R.string.RegistrationActivity_invalid_number), String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), viewModel.number.e164Number) + context, + getString(R.string.RegistrationActivity_invalid_number), + String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), viewModel.number.e164Number) ) } ContinueStatus.OLD_NUMBER_DOESNT_MATCH -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index 746fc7cd08..56aca551de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata import org.thoughtcrime.securesms.database.model.toProtoByteString import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobs.RefreshAttributesJob import org.thoughtcrime.securesms.keyvalue.CertificateType import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.pin.KbsRepository @@ -37,6 +38,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignedPreKeyEntity import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.push.OutgoingPushMessage +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage import org.whispersystems.signalservice.internal.push.VerifyAccountResponse import org.whispersystems.signalservice.internal.push.WhoAmIResponse @@ -85,16 +87,31 @@ class ChangeNumberRepository( fun ensureDecryptionsDrained(): Completable { return Completable.create { emitter -> - ApplicationDependencies - .getIncomingMessageObserver() - .addDecryptionDrainedListener { + val drainedListener = object : Runnable { + override fun run() { emitter.onComplete() + ApplicationDependencies + .getIncomingMessageObserver() + .removeDecryptionDrainedListener(this) } + } + + emitter.setCancellable { + ApplicationDependencies + .getIncomingMessageObserver() + .removeDecryptionDrainedListener(drainedListener) + } + + ApplicationDependencies + .getIncomingMessageObserver() + .addDecryptionDrainedListener(drainedListener) }.subscribeOn(Schedulers.single()) .timeout(15, TimeUnit.SECONDS) } - fun changeNumber(code: String, newE164: String, pniUpdateMode: Boolean = false): Single> { + fun changeNumber(sessionId: String? = null, recoveryPassword: String? = null, newE164: String, pniUpdateMode: Boolean = false): Single> { + check((sessionId != null && recoveryPassword == null) || (sessionId == null && recoveryPassword != null)) + return Single.fromCallable { var completed = false var attempts = 0 @@ -102,9 +119,9 @@ class ChangeNumberRepository( while (!completed && attempts < 5) { val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest( - code = code, + sessionId = sessionId, + recoveryPassword = recoveryPassword, newE164 = newE164, - registrationLock = null, pniUpdateMode = pniUpdateMode ) @@ -127,7 +144,7 @@ class ChangeNumberRepository( } fun changeNumber( - code: String, + sessionId: String, newE164: String, pin: String, tokenData: TokenData @@ -153,10 +170,9 @@ class ChangeNumberRepository( while (!completed && attempts < 5) { val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest( - code = code, + sessionId = sessionId, newE164 = newE164, - registrationLock = registrationLock, - pniUpdateMode = false + registrationLock = registrationLock ) SignalStore.misc().setPendingChangeNumberMetadata(metadata) @@ -253,6 +269,8 @@ class ChangeNumberRepository( ApplicationDependencies.closeConnections() ApplicationDependencies.getIncomingMessageObserver() + ApplicationDependencies.getJobManager().add(RefreshAttributesJob()) + return rotateCertificates() } @@ -280,10 +298,11 @@ class ChangeNumberRepository( @Suppress("UsePropertyAccessSyntax") @WorkerThread private fun createChangeNumberRequest( - code: String, + sessionId: String? = null, + recoveryPassword: String? = null, newE164: String, - registrationLock: String?, - pniUpdateMode: Boolean + registrationLock: String? = null, + pniUpdateMode: Boolean = false ): ChangeNumberRequestData { val selfIdentifier: String = SignalStore.account().requireAci().toString() val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci() @@ -336,8 +355,9 @@ class ChangeNumberRepository( } val request = ChangePhoneNumberRequest( + sessionId, + recoveryPassword, newE164, - code, registrationLock, pniIdentity.publicKey, deviceMessages, @@ -355,5 +375,11 @@ class ChangeNumberRepository( return ChangeNumberRequestData(request, metadata) } + fun verifyAccount(sessionId: String, code: String): Single> { + return Single.fromCallable { + accountManager.verifyAccount(code, sessionId) + }.subscribeOn(Schedulers.io()) + } + data class ChangeNumberRequestData(val changeNumberRequest: ChangePhoneNumberRequest, val pendingChangeNumberMetadata: PendingChangeNumberMetadata) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt index c5fd14b092..dc4d12d23e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt @@ -7,13 +7,17 @@ import android.widget.Toast import androidx.appcompat.widget.Toolbar import androidx.navigation.fragment.findNavController import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.changeNumberSuccess import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getCaptchaArguments import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getViewModel +import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor import org.thoughtcrime.securesms.registration.VerifyAccountRepository import org.thoughtcrime.securesms.util.LifecycleDisposable +import org.thoughtcrime.securesms.util.dualsim.MccMncProducer import org.thoughtcrime.securesms.util.navigation.safeNavigate private val TAG: String = Log.tag(ChangeNumberVerifyFragment::class.java) @@ -48,18 +52,32 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon } private fun requestCode() { + val mode = if (ChangeNumberVerifyFragmentArgs.fromBundle(requireArguments()).smsListenerEnabled) VerifyAccountRepository.Mode.SMS_WITH_LISTENER else VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER + val mccMncProducer = MccMncProducer(requireContext()) lifecycleDisposable += viewModel .ensureDecryptionsDrained() .onErrorComplete() - .andThen(viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER)) + .andThen(viewModel.changeNumberWithRecoveryPassword()) + .flatMap { changed -> + if (changed) { + Single.just(RequestCodeResult.RecoveryPasswordWorked) + } else { + viewModel.requestVerificationCode(mode, mccMncProducer.mcc, mccMncProducer.mnc) + .map { p -> RequestCodeResult.RequestedVerificationCode(p) } + } + } .observeOn(AndroidSchedulers.mainThread()) - .subscribe { processor -> + .subscribe { result -> + if (result is RequestCodeResult.RecoveryPasswordWorked) { + changeNumberSuccess() + return@subscribe + } + + val processor: RegistrationSessionProcessor = (result as RequestCodeResult.RequestedVerificationCode).processor + if (processor.hasResult()) { findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment) - } else if (processor.localRateLimit()) { - Log.i(TAG, "Unable to request sms code due to local rate limit") - findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment) - } else if (processor.captchaRequired()) { + } else if (processor.captchaRequired(viewModel.excludedChallenges)) { Log.i(TAG, "Unable to request sms code due to captcha required") findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_captchaFragment, getCaptchaArguments()) requestingCaptcha = true @@ -74,4 +92,9 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon } } } + + private sealed interface RequestCodeResult { + object RecoveryPasswordWorked : RequestCodeResult + class RequestedVerificationCode(val processor: RegistrationSessionProcessor) : RequestCodeResult + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt index 931e7dc06f..ef9e406144 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt @@ -9,13 +9,16 @@ import androidx.lifecycle.ViewModel import androidx.savedstate.SavedStateRegistryOwner import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.pin.KbsRepository import org.thoughtcrime.securesms.pin.TokenData +import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor import org.thoughtcrime.securesms.registration.SmsRetrieverReceiver import org.thoughtcrime.securesms.registration.VerifyAccountRepository import org.thoughtcrime.securesms.registration.VerifyResponse @@ -26,6 +29,7 @@ import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewMod import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState import org.thoughtcrime.securesms.util.DefaultValueLiveData import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException import org.whispersystems.signalservice.internal.ServiceResponse import java.util.Objects @@ -152,11 +156,32 @@ class ChangeNumberViewModel( } override fun verifyAccountWithoutRegistrationLock(): Single> { - return changeNumberRepository.changeNumber(textCodeEntered, number.e164Number) + val sessionId = sessionId ?: throw IllegalStateException("No valid registration session") + + return changeNumberRepository.verifyAccount(sessionId, textCodeEntered) + .map { RegistrationSessionProcessor.RegistrationSessionProcessorForVerification(it) } + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess { + if (it.hasResult()) { + setCanSmsAtTime(it.getNextCodeViaSmsAttempt()) + setCanCallAtTime(it.getNextCodeViaCallAttempt()) + } + } + .observeOn(Schedulers.io()) + .flatMap { processor -> + if (processor.isAlreadyVerified() || processor.hasResult() && processor.isVerified()) { + changeNumberRepository.changeNumber(sessionId = sessionId, newE164 = number.e164Number) + } else if (processor.error == null) { + Single.just>(ServiceResponse.forApplicationError(IncorrectCodeException(), 403, null)) + } else { + Single.just>(ServiceResponse.coerceError(processor.response)) + } + } } override fun verifyAccountWithRegistrationLock(pin: String, kbsTokenData: TokenData): Single> { - return changeNumberRepository.changeNumber(textCodeEntered, number.e164Number, pin, kbsTokenData) + val sessionId = sessionId ?: throw IllegalStateException("No valid registration session") + return changeNumberRepository.changeNumber(sessionId, number.e164Number, pin, kbsTokenData) } @WorkerThread @@ -178,6 +203,24 @@ class ChangeNumberViewModel( } } + fun changeNumberWithRecoveryPassword(): Single { + val recoveryPassword = SignalStore.kbsValues().recoveryPassword + + return if (SignalStore.kbsValues().hasPin() && recoveryPassword != null) { + changeNumberRepository.changeNumber(recoveryPassword = recoveryPassword, newE164 = number.e164Number) + .map { r -> VerifyResponseWithoutKbs(r) } + .flatMap { p -> + if (p.hasResult()) { + onVerifySuccess(p).map { true } + } else { + Single.just(false) + } + } + } else { + Single.just(false) + } + } + class Factory(owner: SavedStateRegistryOwner) : AbstractSavedStateViewModelFactory(owner, null) { override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt index 9124d2be50..e79d514bb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt @@ -29,7 +29,6 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch private fun getConfiguration(state: ChatsSettingsState): DSLConfiguration { return configure { - switchPref( title = DSLSettingsText.from(R.string.preferences__generate_link_previews), summary = DSLSettingsText.from(R.string.preferences__retrieve_link_previews_from_websites_for_messages), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt index c24ee21d84..66a6262b5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/data/DataAndStorageSettingsRepository.kt @@ -11,7 +11,7 @@ class DataAndStorageSettingsRepository { fun getTotalStorageUse(consumer: (Long) -> Unit) { SignalExecutors.BOUNDED.execute { - val breakdown = SignalDatabase.media.storageBreakdown + val breakdown = SignalDatabase.media.getStorageBreakdown() consumer(listOf(breakdown.audioSize, breakdown.documentSize, breakdown.photoSize, breakdown.videoSize).sum()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt index 5076e6c465..49d5830970 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt @@ -18,6 +18,7 @@ import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController +import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLConfiguration @@ -62,10 +63,10 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == MESSAGE_SOUND_SELECT && resultCode == Activity.RESULT_OK && data != null) { - val uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java) viewModel.setMessageNotificationsSound(uri) } else if (requestCode == CALL_RINGTONE_SELECT && resultCode == Activity.RESULT_OK && data != null) { - val uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java) viewModel.setCallRingtone(uri) } } @@ -108,7 +109,6 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__ } ) } else { - clickPref( title = DSLSettingsText.from(R.string.preferences__sound), summary = DSLSettingsText.from(getRingtoneSummary(state.messageNotificationsState.sound)), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/manual/NotificationProfileSelectionFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/manual/NotificationProfileSelectionFragment.kt index bef69a3256..390d970b41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/manual/NotificationProfileSelectionFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/manual/NotificationProfileSelectionFragment.kt @@ -41,7 +41,6 @@ class NotificationProfileSelectionFragment : DSLSettingsBottomSheetFragment() { val activeProfile: NotificationProfile? = NotificationProfiles.getActiveProfile(state.notificationProfiles) return configure { - state.notificationProfiles.sortedDescending().forEach { profile -> customPref( NotificationProfileSelection.Entry( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileFragment.kt index c4d5f01908..26cad02fe0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileFragment.kt @@ -148,7 +148,7 @@ class EditNotificationProfileFragment : DSLSettingsFragment(layoutId = R.layout. NotificationProfileNamePreset.Model("\uD83D\uDE34", R.string.EditNotificationProfileFragment__sleep, onClick), NotificationProfileNamePreset.Model("\uD83D\uDE97", R.string.EditNotificationProfileFragment__driving, onClick), NotificationProfileNamePreset.Model("\uD83D\uDE0A", R.string.EditNotificationProfileFragment__downtime, onClick), - NotificationProfileNamePreset.Model("\uD83D\uDCA1", R.string.EditNotificationProfileFragment__focus, onClick), + NotificationProfileNamePreset.Model("\uD83D\uDCA1", R.string.EditNotificationProfileFragment__focus, onClick) ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileScheduleFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileScheduleFragment.kt index fb3bef6c03..6493384dbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileScheduleFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/EditNotificationProfileScheduleFragment.kt @@ -41,7 +41,7 @@ private val DAY_TO_STARTING_LETTER: Map = mapOf( DayOfWeek.WEDNESDAY to R.string.EditNotificationProfileSchedule__wednesday_first_letter, DayOfWeek.THURSDAY to R.string.EditNotificationProfileSchedule__thursday_first_letter, DayOfWeek.FRIDAY to R.string.EditNotificationProfileSchedule__friday_first_letter, - DayOfWeek.SATURDAY to R.string.EditNotificationProfileSchedule__saturday_first_letter, + DayOfWeek.SATURDAY to R.string.EditNotificationProfileSchedule__saturday_first_letter ) /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/NotificationProfileDetailsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/NotificationProfileDetailsFragment.kt index 0face1436b..1c03801944 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/NotificationProfileDetailsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/profiles/NotificationProfileDetailsFragment.kt @@ -95,7 +95,6 @@ class NotificationProfileDetailsFragment : DSLSettingsFragment() { val (profile: NotificationProfile, recipients: List, isOn: Boolean, expanded: Boolean) = state return configure { - customPref( NotificationProfilePreference.Model( title = DSLSettingsText.from(profile.name), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt index 1b19638f3f..d89aecdd6c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt @@ -1,34 +1,27 @@ package org.thoughtcrime.securesms.components.settings.app.privacy import android.content.ActivityNotFoundException -import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.os.Build import android.provider.Settings import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.style.TextAppearanceSpan import android.view.View import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi -import androidx.annotation.StringRes import androidx.biometric.BiometricManager import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProvider import androidx.navigation.Navigation import androidx.navigation.fragment.NavHostFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import mobi.upod.timedurationpicker.TimeDurationPicker -import mobi.upod.timedurationpicker.TimeDurationPickerDialog import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.ChangePassphraseDialogFragment import org.thoughtcrime.securesms.PassphraseActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.ScreenLockController import org.thoughtcrime.securesms.biometric.BiometricDialogFragment +import org.thoughtcrime.securesms.components.TimeDurationPickerDialog import org.thoughtcrime.securesms.components.settings.ClickPreference import org.thoughtcrime.securesms.components.settings.ClickPreferenceViewHolder import org.thoughtcrime.securesms.components.settings.DSLConfiguration @@ -37,8 +30,6 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsText import org.thoughtcrime.securesms.components.settings.PreferenceModel import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder import org.thoughtcrime.securesms.components.settings.configure -import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues -import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode import org.thoughtcrime.securesms.preferences.widgets.PassphraseLockTriggerPreference import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.ConversationUtil @@ -51,6 +42,7 @@ import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.navigation.safeNavigate import java.util.concurrent.TimeUnit +import kotlin.time.Duration.Companion.seconds private val TAG = Log.tag(PrivacySettingsFragment::class.java) @@ -144,14 +136,13 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac summary = DSLSettingsText.from(getDeviceLockTimeoutSummary(state.passphraseLockTimeout)), isEnabled = state.passphraseLock && PassphraseLockTriggerPreference(state.passphraseLockTriggerValues).isTimeoutEnabled, onClick = { - TimeDurationPickerDialog( - context, - { _: TimeDurationPicker?, duration: Long -> - val timeoutSeconds = TimeUnit.MILLISECONDS.toSeconds(duration) - viewModel.setPassphraseLockTimeout(timeoutSeconds) - }, - 0, TimeDurationPicker.HH_MM_SS - ).show() + childFragmentManager.clearFragmentResult(TimeDurationPickerDialog.RESULT_DURATION) + childFragmentManager.clearFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION) + childFragmentManager.setFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION, this@PrivacySettingsFragment) { _, bundle -> + val timeoutSeconds = bundle.getLong(TimeDurationPickerDialog.RESULT_KEY_DURATION_SECONDS) + viewModel.setPassphraseLockTimeout(timeoutSeconds) + } + TimeDurationPickerDialog.create(state.passphraseLockTimeout.seconds).show(childFragmentManager, null) } ) @@ -179,30 +170,21 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac dividerPref() - if (FeatureFlags.phoneNumberPrivacy()) { - sectionHeaderPref(R.string.preferences_app_protection__who_can) - - clickPref( - title = DSLSettingsText.from(R.string.preferences_app_protection__see_my_phone_number), - summary = DSLSettingsText.from(getWhoCanSeeMyPhoneNumberSummary(state.seeMyPhoneNumber)), - onClick = { - onSeeMyPhoneNumberClicked(state.seeMyPhoneNumber) - } - ) + sectionHeaderPref(R.string.PrivacySettingsFragment__messaging) + if (FeatureFlags.phoneNumberPrivacy()) { clickPref( - title = DSLSettingsText.from(R.string.preferences_app_protection__find_me_by_phone_number), - summary = DSLSettingsText.from(getWhoCanFindMeByPhoneNumberSummary(state.findMeByPhoneNumber)), + title = DSLSettingsText.from(R.string.preferences_app_protection__phone_number), + summary = DSLSettingsText.from(R.string.preferences_app_protection__choose_who_can_see), onClick = { - onFindMyPhoneNumberClicked(state.findMeByPhoneNumber) + Navigation.findNavController(requireView()) + .safeNavigate(R.id.action_privacySettingsFragment_to_phoneNumberPrivacySettingsFragment) } ) dividerPref() } - sectionHeaderPref(R.string.PrivacySettingsFragment__messaging) - switchPref( title = DSLSettingsText.from(R.string.preferences__read_receipts), summary = DSLSettingsText.from(R.string.preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts), @@ -272,7 +254,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac ) textPref( - summary = DSLSettingsText.from(incognitoSummary), + summary = DSLSettingsText.from(incognitoSummary) ) dividerPref() @@ -345,10 +327,6 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac } private fun getDeviceLockTimeoutSummary(timeoutSeconds: Long): String { - val hours = TimeUnit.SECONDS.toHours(timeoutSeconds) - val minutes = TimeUnit.SECONDS.toMinutes(timeoutSeconds) - hours * 60 - val seconds = TimeUnit.SECONDS.toSeconds(timeoutSeconds) - minutes * 60 - hours * 3600 - return if (timeoutSeconds <= 0) { getString(R.string.AppProtectionPreferenceFragment_instant) } else { @@ -356,117 +334,6 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac } } - @StringRes - private fun getWhoCanSeeMyPhoneNumberSummary(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode): Int { - return when (phoneNumberSharingMode) { - PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE -> R.string.PhoneNumberPrivacy_everyone - PhoneNumberPrivacyValues.PhoneNumberSharingMode.CONTACTS -> R.string.PhoneNumberPrivacy_my_contacts - PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY -> R.string.PhoneNumberPrivacy_nobody - } - } - - @StringRes - private fun getWhoCanFindMeByPhoneNumberSummary(phoneNumberListingMode: PhoneNumberListingMode): Int { - return when (phoneNumberListingMode) { - PhoneNumberListingMode.LISTED -> R.string.PhoneNumberPrivacy_everyone - PhoneNumberListingMode.UNLISTED -> R.string.PhoneNumberPrivacy_nobody - } - } - - private fun onSeeMyPhoneNumberClicked(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) { - val value = arrayOf(phoneNumberSharingMode) - val items = items(requireContext()) - val modes: List = ArrayList(items.keys) - val modeStrings = items.values.toTypedArray() - val selectedMode = modes.indexOf(value[0]) - - MaterialAlertDialogBuilder(requireActivity()).apply { - setTitle(R.string.preferences_app_protection__see_my_phone_number) - setCancelable(true) - setSingleChoiceItems( - modeStrings, - selectedMode - ) { _: DialogInterface?, which: Int -> value[0] = modes[which] } - setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> - val newSharingMode = value[0] - Log.i( - TAG, - String.format( - "PhoneNumberSharingMode changed to %s. Scheduling storage value sync", - newSharingMode - ) - ) - viewModel.setPhoneNumberSharingMode(value[0]) - } - setNegativeButton(android.R.string.cancel, null) - show() - } - } - - private fun items(context: Context): Map { - val map: MutableMap = LinkedHashMap() - map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.EVERYONE] = titleAndDescription( - context, - context.getString(R.string.PhoneNumberPrivacy_everyone), - context.getString(R.string.PhoneNumberPrivacy_everyone_see_description) - ) - map[PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY] = - context.getString(R.string.PhoneNumberPrivacy_nobody) - return map - } - - private fun titleAndDescription( - context: Context, - header: String, - description: String - ): CharSequence { - return SpannableStringBuilder().apply { - append("\n") - append(header) - append("\n") - setSpan( - TextAppearanceSpan(context, android.R.style.TextAppearance_Small), - length, - length, - Spanned.SPAN_INCLUSIVE_INCLUSIVE - ) - append(description) - append("\n") - } - } - - fun onFindMyPhoneNumberClicked(phoneNumberListingMode: PhoneNumberListingMode) { - val context = requireContext() - val value = arrayOf(phoneNumberListingMode) - MaterialAlertDialogBuilder(requireActivity()).apply { - setTitle(R.string.preferences_app_protection__find_me_by_phone_number) - setCancelable(true) - setSingleChoiceItems( - arrayOf( - titleAndDescription( - context, - context.getString(R.string.PhoneNumberPrivacy_everyone), - context.getString(R.string.PhoneNumberPrivacy_everyone_find_description) - ), - context.getString(R.string.PhoneNumberPrivacy_nobody) - ), - value[0].ordinal - ) { _: DialogInterface?, which: Int -> value[0] = PhoneNumberListingMode.values()[which] } - setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> - Log.i( - TAG, - String.format( - "PhoneNumberListingMode changed to %s. Scheduling storage value sync", - value[0] - ) - ) - viewModel.setPhoneNumberListingMode(value[0]) - } - setNegativeButton(android.R.string.cancel, null) - show() - } - } - private class ValueClickPreference( val value: DSLSettingsText, val clickPreference: ClickPreference diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt index eb298e5c1b..24437e45c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt @@ -1,12 +1,8 @@ package org.thoughtcrime.securesms.components.settings.app.privacy -import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues - data class PrivacySettingsState( val blockedCount: Int, val blockUnknown: Boolean, - val seeMyPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberSharingMode, - val findMeByPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberListingMode, val readReceipts: Boolean, val typingIndicators: Boolean, val passphraseLock: Boolean, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt index 2ead397546..fe2eb4c0be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt @@ -7,11 +7,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import org.thoughtcrime.securesms.ScreenLockController import org.thoughtcrime.securesms.dependencies.ApplicationDependencies -import org.thoughtcrime.securesms.jobs.RefreshAttributesJob -import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob -import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.livedata.Store @@ -77,19 +73,6 @@ class PrivacySettingsViewModel( refresh() } - fun setPhoneNumberSharingMode(phoneNumberSharingMode: PhoneNumberPrivacyValues.PhoneNumberSharingMode) { - SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = phoneNumberSharingMode - StorageSyncHelper.scheduleSyncForDataChange() - refresh() - } - - fun setPhoneNumberListingMode(phoneNumberListingMode: PhoneNumberPrivacyValues.PhoneNumberListingMode) { - SignalStore.phoneNumberPrivacy().phoneNumberListingMode = phoneNumberListingMode - StorageSyncHelper.scheduleSyncForDataChange() - ApplicationDependencies.getJobManager().startChain(RefreshAttributesJob()).then(RefreshOwnProfileJob()).enqueue() - refresh() - } - fun setIncognitoKeyboard(enabled: Boolean) { sharedPreferences.edit().putBoolean(TextSecurePreferences.INCOGNITO_KEYBORAD_PREF, enabled).apply() refresh() @@ -111,8 +94,6 @@ class PrivacySettingsViewModel( biometricScreenLock = TextSecurePreferences.isBiometricScreenLockEnabled(application), screenSecurity = TextSecurePreferences.isScreenSecurityEnabled(application), incognitoKeyboard = TextSecurePreferences.isIncognitoKeyboardEnabled(application), - seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode, - findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode, universalExpireTimer = SignalStore.settings().universalExpireTimer ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt index 724a4142b8..511cf32eab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsFragment.kt @@ -107,7 +107,6 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences private fun getConfiguration(state: AdvancedPrivacySettingsState): DSLConfiguration { return configure { - switchPref( title = DSLSettingsText.from(R.string.preferences__signal_messages_and_calls), summary = DSLSettingsText.from(getPushToggleSummary(state.isPushEnabled)), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt index 1116cd2e7e..14714cc62f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsState.kt @@ -25,5 +25,5 @@ enum class CensorshipCircumventionState(val available: Boolean) { AVAILABLE_AUTOMATICALLY_ENABLED(true), /** The setting is generically available */ - AVAILABLE(true), + AVAILABLE(true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/expire/ExpireTimerSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/expire/ExpireTimerSettingsState.kt index 732a083073..f8d399e666 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/expire/ExpireTimerSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/expire/ExpireTimerSettingsState.kt @@ -7,7 +7,7 @@ data class ExpireTimerSettingsState( val userSetTimer: Int? = null, val saveState: ProcessState = ProcessState.Idle(), val isGroupCreate: Boolean = false, - val isForRecipient: Boolean = isGroupCreate, + val isForRecipient: Boolean = isGroupCreate ) { val currentTimer: Int get() = userSetTimer ?: initialTimer diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt new file mode 100644 index 0000000000..cd0926aa26 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt @@ -0,0 +1,127 @@ +package org.thoughtcrime.securesms.components.settings.app.privacy.pnp + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import org.signal.core.ui.Dividers +import org.signal.core.ui.Rows +import org.signal.core.ui.Scaffolds +import org.signal.core.ui.Texts +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.compose.ComposeFragment +import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode +import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberSharingMode + +class PhoneNumberPrivacySettingsFragment : ComposeFragment() { + + private val viewModel: PhoneNumberPrivacySettingsViewModel by viewModels() + + @Composable + override fun SheetContent() { + val state: PhoneNumberPrivacySettingsState by viewModel.state + val onNavigationClick: () -> Unit = remember { + { findNavController().popBackStack() } + } + + Scaffolds.Settings( + title = stringResource(id = R.string.preferences_app_protection__phone_number), + onNavigationClick = onNavigationClick, + navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24), + navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close) + ) { contentPadding -> + Box(modifier = Modifier.padding(contentPadding)) { + LazyColumn { + item { + Texts.SectionHeader( + text = stringResource(id = R.string.PhoneNumberPrivacySettingsFragment__who_can_see_my_number) + ) + } + + item { + Rows.RadioRow( + selected = state.seeMyPhoneNumber == PhoneNumberSharingMode.EVERYONE, + text = stringResource(id = R.string.PhoneNumberPrivacy_everyone), + modifier = Modifier.clickable(onClick = viewModel::setEveryoneCanSeeMyNumber) + ) + } + + item { + Rows.RadioRow( + selected = state.seeMyPhoneNumber == PhoneNumberSharingMode.NOBODY, + text = stringResource(id = R.string.PhoneNumberPrivacy_nobody), + modifier = Modifier.clickable(onClick = viewModel::setNobodyCanSeeMyNumber) + ) + } + + item { + Text( + text = stringResource( + id = when (state.seeMyPhoneNumber) { + PhoneNumberSharingMode.EVERYONE -> R.string.PhoneNumberPrivacySettingsFragment__your_phone_number + PhoneNumberSharingMode.NOBODY -> R.string.PhoneNumberPrivacySettingsFragment__nobody_will_see + else -> error("Unexpected state $state") + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Dividers.Default() + } + + item { + Texts.SectionHeader(text = stringResource(id = R.string.PhoneNumberPrivacySettingsFragment__who_can_find_me_by_number)) + } + + item { + Rows.RadioRow( + selected = state.findMeByPhoneNumber == PhoneNumberListingMode.LISTED, + text = stringResource(id = R.string.PhoneNumberPrivacy_everyone), + modifier = Modifier.clickable(onClick = viewModel::setEveryoneCanFindMeByMyNumber) + ) + } + + if (state.seeMyPhoneNumber == PhoneNumberSharingMode.NOBODY) { + item { + Rows.RadioRow( + selected = state.findMeByPhoneNumber == PhoneNumberListingMode.UNLISTED, + text = stringResource(id = R.string.PhoneNumberPrivacy_nobody), + modifier = Modifier.clickable(onClick = viewModel::setNobodyCanFindMeByMyNumber) + ) + } + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + } + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsState.kt new file mode 100644 index 0000000000..184726abb5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsState.kt @@ -0,0 +1,8 @@ +package org.thoughtcrime.securesms.components.settings.app.privacy.pnp + +import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues + +data class PhoneNumberPrivacySettingsState( + val seeMyPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberSharingMode, + val findMeByPhoneNumber: PhoneNumberPrivacyValues.PhoneNumberListingMode +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsViewModel.kt new file mode 100644 index 0000000000..6d8a7b54a2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsViewModel.kt @@ -0,0 +1,64 @@ +package org.thoughtcrime.securesms.components.settings.app.privacy.pnp + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobs.RefreshAttributesJob +import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob +import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode +import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberSharingMode +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.storage.StorageSyncHelper + +class PhoneNumberPrivacySettingsViewModel : ViewModel() { + + private val _state = mutableStateOf( + PhoneNumberPrivacySettingsState( + seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode, + findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode + ) + ) + + val state: State = _state + + fun setNobodyCanSeeMyNumber() { + setPhoneNumberSharingMode(PhoneNumberSharingMode.NOBODY) + } + + fun setEveryoneCanSeeMyNumber() { + setPhoneNumberSharingMode(PhoneNumberSharingMode.EVERYONE) + setPhoneNumberListingMode(PhoneNumberListingMode.LISTED) + } + + fun setNobodyCanFindMeByMyNumber() { + setPhoneNumberListingMode(PhoneNumberListingMode.UNLISTED) + } + + fun setEveryoneCanFindMeByMyNumber() { + setPhoneNumberListingMode(PhoneNumberListingMode.LISTED) + } + + private fun setPhoneNumberSharingMode(phoneNumberSharingMode: PhoneNumberSharingMode) { + SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = phoneNumberSharingMode + SignalDatabase.recipients.markNeedsSync(Recipient.self().id) + StorageSyncHelper.scheduleSyncForDataChange() + refresh() + } + + private fun setPhoneNumberListingMode(phoneNumberListingMode: PhoneNumberListingMode) { + SignalStore.phoneNumberPrivacy().phoneNumberListingMode = phoneNumberListingMode + StorageSyncHelper.scheduleSyncForDataChange() + ApplicationDependencies.getJobManager().startChain(RefreshAttributesJob()).then(RefreshOwnProfileJob()).enqueue() + refresh() + } + + fun refresh() { + _state.value = PhoneNumberPrivacySettingsState( + seeMyPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode, + findMeByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt index 81a5b0b177..a087a6224b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/wrapped/SettingsWrapperFragment.kt @@ -18,7 +18,6 @@ abstract class SettingsWrapperFragment : Fragment(R.layout.settings_wrapper_frag private set override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - toolbar = view.findViewById(R.id.toolbar) toolbar.setNavigationOnClickListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsActivity.kt index 9aa5c7c390..efa1196d5d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsActivity.kt @@ -8,6 +8,7 @@ import android.view.View import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat import androidx.core.util.Pair +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity import org.thoughtcrime.securesms.groups.GroupId @@ -22,6 +23,7 @@ class ConversationSettingsActivity : DSLSettingsActivity(), ConversationSettings override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { ActivityCompat.postponeEnterTransition(this) + setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback()) super.onCreate(savedInstanceState, ready) } @@ -55,7 +57,7 @@ class ConversationSettingsActivity : DSLSettingsActivity(), ConversationSettings ActivityOptionsCompat.makeSceneTransitionAnimation( context, avatar, - "avatar", + "avatar" ).toBundle() } else { null diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt index 526a0357eb..8c772b2e63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.components.settings.conversation +import android.app.ActivityOptions import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -25,6 +26,7 @@ import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import org.signal.core.util.DimensionUnit +import org.signal.core.util.getParcelableArrayListExtraCompat import org.thoughtcrime.securesms.AvatarPreviewActivity import org.thoughtcrime.securesms.BlockUnblockDialog import org.thoughtcrime.securesms.InviteActivity @@ -186,7 +188,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_CODE_ADD_MEMBERS_TO_GROUP -> if (data != null) { - val selected: List = requireNotNull(data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS)) + val selected: List = requireNotNull(data.getParcelableArrayListExtraCompat(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS, RecipientId::class.java)) val progress: SimpleProgressDialog.DismissibleDialog = SimpleProgressDialog.showDelayed(requireContext()) viewModel.onAddToGroupComplete(selected) { @@ -542,10 +544,13 @@ class ConversationSettingsFragment : DSLSettingsFragment( SharedMediaPreference.Model( mediaCursor = state.sharedMedia, mediaIds = state.sharedMediaIds, - onMediaRecordClick = { mediaRecord, isLtr -> + onMediaRecordClick = { view, mediaRecord, isLtr -> + view.transitionName = "thumb" + val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), view, "thumb") startActivityForResult( MediaIntentFactory.intentFromMediaRecord(requireContext(), mediaRecord, isLtr, allMediaInRail = true), - REQUEST_CODE_RETURN_FROM_MEDIA + REQUEST_CODE_RETURN_FROM_MEDIA, + options.toBundle() ) } ) @@ -575,7 +580,6 @@ class ConversationSettingsFragment : DSLSettingsFragment( } if (recipientSettingsState.selfHasGroups && !state.recipient.isReleaseNotes) { - dividerPref() val groupsInCommonCount = recipientSettingsState.allGroupsInCommon.size diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt index 42fc7b7895..c9e937d874 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt @@ -20,7 +20,7 @@ data class ConversationSettingsState( val sharedMediaIds: List = listOf(), val displayInternalRecipientDetails: Boolean = false, private val sharedMediaLoaded: Boolean = false, - private val specificSettingsState: SpecificSettingsState, + private val specificSettingsState: SpecificSettingsState ) { val isLoaded: Boolean = recipient != Recipient.UNKNOWN && sharedMediaLoaded && specificSettingsState.isLoaded diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt index 1eb21f92a6..a25cc5d93b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt @@ -34,7 +34,7 @@ import java.util.Optional sealed class ConversationSettingsViewModel( private val repository: ConversationSettingsRepository, - specificSettingsState: SpecificSettingsState, + specificSettingsState: SpecificSettingsState ) : ViewModel() { private val openedMediaCursors = HashSet() @@ -414,7 +414,6 @@ sealed class ConversationSettingsViewModel( override fun onAddToGroup() { repository.getGroupCapacity(groupId) { capacityResult -> if (capacityResult.getRemainingCapacity() > 0) { - internalEvents.onNext( ConversationSettingsEvent.AddMembersToGroup( groupId, @@ -489,7 +488,7 @@ sealed class ConversationSettingsViewModel( class Factory( private val recipientId: RecipientId? = null, private val groupId: GroupId? = null, - private val repository: ConversationSettingsRepository, + private val repository: ConversationSettingsRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt index e175be0c5a..87d0c7c848 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt @@ -9,6 +9,8 @@ import androidx.lifecycle.ViewModelProvider import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.signal.core.util.Hex import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.isAbsent +import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.thoughtcrime.securesms.MainActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLConfiguration @@ -220,7 +222,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( sectionHeaderPref(DSLSettingsText.from("PNP")) clickPref( - title = DSLSettingsText.from("Split contact"), + title = DSLSettingsText.from("Split and create threads"), summary = DSLSettingsText.from("Splits this contact into two recipients and two threads so that you can test merging them together. This will remain the 'primary' recipient."), onClick = { MaterialAlertDialogBuilder(requireContext()) @@ -257,6 +259,41 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( .show() } ) + + clickPref( + title = DSLSettingsText.from("Split without creating threads"), + summary = DSLSettingsText.from("Splits this contact into two recipients so you can test merging them together. This will become the PNI-based recipient. Another recipient will be made with this ACI and profile key. Doing a CDS refresh should allow you to see a Session Switchover Event, as long as you had a session with this PNI."), + isEnabled = FeatureFlags.phoneNumberPrivacy(), + onClick = { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Are you sure?") + .setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() } + .setPositiveButton(android.R.string.ok) { _, _ -> + if (recipient.pni.isAbsent()) { + Toast.makeText(context, "Recipient doesn't have a PNI! Can't split.", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + + if (recipient.serviceId.isAbsent()) { + Toast.makeText(context, "Recipient doesn't have a serviceId! Can't split.", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + + SignalDatabase.recipients.debugRemoveAci(recipient.id) + + val aciRecipientId: RecipientId = SignalDatabase.recipients.getAndPossiblyMergePnpVerified(recipient.requireServiceId(), null, null) + + recipient.profileKey?.let { profileKey -> + SignalDatabase.recipients.setProfileKey(aciRecipientId, ProfileKey(profileKey)) + } + + SignalDatabase.recipients.debugClearProfileData(recipient.id) + + Toast.makeText(context, "Done! Split the ACI and profile key off into $aciRecipientId", Toast.LENGTH_SHORT).show() + } + .show() + } + ) } } @@ -278,7 +315,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( ", ", colorize("ChangeNumber", capabilities.changeNumberCapability), ", ", - colorize("Stories", capabilities.storiesCapability), + colorize("Stories", capabilities.storiesCapability) ) } else { "Recipient not found!" diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/permissions/PermissionsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/permissions/PermissionsSettingsFragment.kt index 7ba7361e09..b13efebdf3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/permissions/PermissionsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/permissions/PermissionsSettingsFragment.kt @@ -48,7 +48,6 @@ class PermissionsSettingsFragment : DSLSettingsFragment( private fun getConfiguration(state: PermissionsSettingsState): DSLConfiguration { return configure { - radioListPref( title = DSLSettingsText.from(R.string.PermissionsSettingsFragment__add_members), isEnabled = state.selfCanEditSettings, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt index 5ede859119..a7cfd5a876 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/InternalPreference.kt @@ -16,7 +16,7 @@ object InternalPreference { class Model( private val recipient: Recipient, - val onInternalDetailsClicked: () -> Unit, + val onInternalDetailsClicked: () -> Unit ) : PreferenceModel() { override fun areItemsTheSame(newItem: Model): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/SharedMediaPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/SharedMediaPreference.kt index 27870f4386..484e2be188 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/SharedMediaPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/SharedMediaPreference.kt @@ -24,7 +24,7 @@ object SharedMediaPreference { class Model( val mediaCursor: Cursor, val mediaIds: List, - val onMediaRecordClick: (MediaTable.MediaRecord, Boolean) -> Unit + val onMediaRecordClick: (View, MediaTable.MediaRecord, Boolean) -> Unit ) : PreferenceModel() { override fun areItemsTheSame(newItem: Model): Boolean { return true @@ -42,8 +42,8 @@ object SharedMediaPreference { override fun bind(model: Model) { rail.setCursor(GlideApp.with(rail), model.mediaCursor) - rail.setListener { - model.onMediaRecordClick(it, ViewUtil.isLtr(rail)) + rail.setListener { v, m -> + model.onMediaRecordClick(v, m, ViewUtil.isLtr(rail)) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt index fbd53fbeb4..1965832a79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/SoundsAndNotificationsSettingsFragment.kt @@ -48,7 +48,6 @@ class SoundsAndNotificationsSettingsFragment : DSLSettingsFragment( private fun getConfiguration(state: SoundsAndNotificationsSettingsState): DSLConfiguration { return configure { - val muteSummary = if (state.muteUntil > 0) { state.muteUntil.formatMutedUntil(requireContext()) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt index 146f04168f..a03ebb4e6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsFragment.kt @@ -11,6 +11,7 @@ import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.viewModels +import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLConfiguration @@ -67,14 +68,13 @@ class CustomNotificationsSettingsFragment : DSLSettingsFragment(R.string.CustomN val data = result.data if (resultCode == Activity.RESULT_OK && data != null) { - val uri: Uri? = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + val uri: Uri? = data.getParcelableExtraCompat(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java) resultHandler(uri) } } private fun getConfiguration(state: CustomNotificationsSettingsState): DSLConfiguration { return configure { - sectionHeaderPref(R.string.CustomNotificationsDialogFragment__messages) if (NotificationChannels.supported()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt index 953a9d76f7..312e0dbb3e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/sounds/custom/CustomNotificationsSettingsState.kt @@ -14,5 +14,5 @@ data class CustomNotificationsSettingsState( val messageSound: Uri? = null, val callVibrateState: RecipientTable.VibrateState = RecipientTable.VibrateState.DEFAULT, val callSound: Uri? = null, - val showCallingOptions: Boolean = false, + val showCallingOptions: Boolean = false ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt index 9d64b6fcf2..414051e6a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/dsl.kt @@ -1,6 +1,9 @@ +@file:Suppress("ktlint:filename") + package org.thoughtcrime.securesms.components.settings import androidx.annotation.CallSuper +import androidx.annotation.Discouraged import androidx.annotation.Px import androidx.annotation.StringRes import org.thoughtcrime.securesms.components.settings.models.AsyncSwitch @@ -10,12 +13,14 @@ import org.thoughtcrime.securesms.components.settings.models.Text import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel import org.thoughtcrime.securesms.util.adapter.mapping.MappingModelList +@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") fun configure(init: DSLConfiguration.() -> Unit): DSLConfiguration { val configuration = DSLConfiguration() configuration.init() return configuration } +@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") class DSLConfiguration { private val children = arrayListOf>() @@ -212,7 +217,7 @@ abstract class PreferenceModel>( open val summary: DSLSettingsText? = null, open val icon: DSLSettingsIcon? = null, open val iconEnd: DSLSettingsIcon? = null, - open val isEnabled: Boolean = true, + open val isEnabled: Boolean = true ) : MappingModel { override fun areItemsTheSame(newItem: T): Boolean { return when { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/Text.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/Text.kt index 37e4cc9315..7cf8a73743 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/Text.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/models/Text.kt @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder * A Text without any padding, allowing for exact padding to be handed in at runtime. */ data class Text( - val text: DSLSettingsText, + val text: DSLSettingsText ) { companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java index 617d22b04f..ff341da3cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaController.java @@ -16,6 +16,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -33,9 +34,9 @@ /** * Encapsulates control of voice note playback from an Activity component. - * + *

* This class assumes that it will be created within the scope of Activity#onCreate - * + *

* The workhorse of this repository is the ProgressEventHandler, which will supply a * steady stream of update events to the set callback. */ @@ -54,15 +55,17 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver { private MutableLiveData voiceNotePlaybackState = new MutableLiveData<>(VoiceNotePlaybackState.NONE); private LiveData> voiceNotePlayerViewState; private VoiceNoteProximityWakeLockManager voiceNoteProximityWakeLockManager; + private boolean isMediaBrowserCreationPostponed; private final MediaControllerCompatCallback mediaControllerCompatCallback = new MediaControllerCompatCallback(); public VoiceNoteMediaController(@NonNull FragmentActivity activity) { - this.activity = activity; - this.mediaBrowser = new MediaBrowserCompat(activity, - new ComponentName(activity, VoiceNotePlaybackService.class), - new ConnectionCallback(), - null); + this(activity, false); + } + + public VoiceNoteMediaController(@NonNull FragmentActivity activity, boolean postponeMediaBrowserCreation) { + this.activity = activity; + this.isMediaBrowserCreationPostponed = postponeMediaBrowserCreation; activity.getLifecycle().addObserver(this); @@ -71,9 +74,9 @@ public VoiceNoteMediaController(@NonNull FragmentActivity activity) { VoiceNotePlaybackState.ClipType.Message message = (VoiceNotePlaybackState.ClipType.Message) playbackState.getClipType(); LiveRecipient sender = Recipient.live(message.getSenderId()); LiveRecipient threadRecipient = Recipient.live(message.getThreadRecipientId()); - LiveData name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(), - threadRecipient.getLiveDataResolved(), - (s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null)); + LiveData name = LiveDataUtil.combineLatest(sender.getLiveDataResolved(), + threadRecipient.getLiveDataResolved(), + (s, t) -> VoiceNoteMediaItemFactory.getTitle(activity, s, t, null)); return Transformations.map(name, displayName -> Optional.of( new VoiceNotePlayerView.State( @@ -95,6 +98,17 @@ public VoiceNoteMediaController(@NonNull FragmentActivity activity) { }); } + public void ensureMediaBrowser() { + if (mediaBrowser != null) { + return; + } + + mediaBrowser = new MediaBrowserCompat(activity, + new ComponentName(activity, VoiceNotePlaybackService.class), + new ConnectionCallback(), + null); + } + public LiveData getVoiceNotePlaybackState() { return voiceNotePlaybackState; } @@ -103,8 +117,22 @@ public LiveData> getVoiceNotePlayerViewState return voiceNotePlayerViewState; } + public void finishPostpone() { + isMediaBrowserCreationPostponed = false; + if (activity != null && mediaBrowser == null && activity.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + ensureMediaBrowser(); + mediaBrowser.disconnect(); + mediaBrowser.connect(); + } + } + @Override public void onResume(@NonNull LifecycleOwner owner) { + if (mediaBrowser == null && isMediaBrowserCreationPostponed) { + return; + } + + ensureMediaBrowser(); mediaBrowser.disconnect(); mediaBrowser.connect(); } @@ -117,7 +145,9 @@ public void onPause(@NonNull LifecycleOwner owner) { MediaControllerCompat.getMediaController(activity).unregisterCallback(mediaControllerCompatCallback); } - mediaBrowser.disconnect(); + if (mediaBrowser != null) { + mediaBrowser.disconnect(); + } } @Override @@ -201,8 +231,8 @@ private void startPlayback(@NonNull Uri audioSlideUri, long messageId, long thre * Tells the Media service to resume playback of a given audio slide. If the audio slide is not * currently paused, playback will be started from the beginning. * - * @param audioSlideUri The Uri of the desired audio slide - * @param messageId The Message id of the given audio slide + * @param audioSlideUri The Uri of the desired audio slide + * @param messageId The Message id of the given audio slide */ public void resumePlayback(@NonNull Uri audioSlideUri, long messageId) { if (getMediaController() == null) { @@ -390,8 +420,8 @@ private void cleanUpOldProximityWakeLockManager() { } private static boolean canExtractPlaybackInformationFromMetadata(@Nullable MediaMetadataCompat mediaMetadataCompat) { - return mediaMetadataCompat != null && - mediaMetadataCompat.getDescription() != null && + return mediaMetadataCompat != null && + mediaMetadataCompat.getDescription() != null && mediaMetadataCompat.getDescription().getMediaUri() != null; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java index ac71d759b8..a800982266 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackService.java @@ -260,7 +260,7 @@ private void sendViewedReceiptForCurrentWindowIndex() { if (extras == null) { return; } - long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID); + long messageId = extras.getLong(VoiceNoteMediaItemFactory.EXTRA_MESSAGE_ID); RecipientId recipientId = RecipientId.from(extras.getString(VoiceNoteMediaItemFactory.EXTRA_INDIVIDUAL_RECIPIENT_ID)); MessageTable messageDatabase = SignalDatabase.messages(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayerView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayerView.kt index 26234606ea..5d20e083a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayerView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlayerView.kt @@ -4,11 +4,15 @@ import android.content.Context import android.net.Uri import android.util.AttributeSet import android.view.View +import android.view.ViewGroup import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat +import androidx.core.view.doOnNextLayout +import androidx.core.view.updateLayoutParams +import androidx.core.widget.doOnTextChanged import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieProperty import com.airbnb.lottie.SimpleColorFilter @@ -56,6 +60,17 @@ class VoiceNotePlayerView @JvmOverloads constructor( closeButton = findViewById(R.id.voice_note_player_close) infoView.isSelected = true + infoView.doOnTextChanged { _, _, _, _ -> + infoView.updateLayoutParams { + width = ViewGroup.LayoutParams.WRAP_CONTENT + + infoView.doOnNextLayout { + infoView.updateLayoutParams { + width = infoView.measuredWidth + } + } + } + } val speedTouchTarget: View = findViewById(R.id.voice_note_player_speed_touch_target) speedTouchTarget.setOnClickListener { @@ -111,6 +126,7 @@ class VoiceNotePlayerView @JvmOverloads constructor( } fun setState(state: State) { + val prevName = lastState?.name this.lastState = state if (state.isPaused) { @@ -119,7 +135,7 @@ class VoiceNotePlayerView @JvmOverloads constructor( animateToggleToPause() } - if (infoView.text != state.name) { + if (prevName != state.name) { infoView.text = state.name } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java index 0c3928c745..a6d2835e14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsLayout.java @@ -7,10 +7,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.cardview.widget.CardView; import com.google.android.flexbox.AlignItems; import com.google.android.flexbox.FlexboxLayout; +import com.google.android.material.card.MaterialCardView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.events.CallParticipant; @@ -116,7 +116,7 @@ private void updateChildrenCount(int count) { private void update(int index, int count, @NonNull CallParticipant participant) { View view = getChildAt(index); - CardView cardView = view.findViewById(R.id.group_call_participant_card_wrapper); + MaterialCardView cardView = view.findViewById(R.id.group_call_participant_card_wrapper); CallParticipantView callParticipantView = view.findViewById(R.id.group_call_participant); callParticipantView.setCallParticipant(participant); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt index 87f6ba2319..3cbbe7971f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt @@ -186,7 +186,6 @@ data class CallParticipantsState( webRtcViewModel: WebRtcViewModel, enableVideo: Boolean ): CallParticipantsState { - var newShowVideoForOutgoing: Boolean = oldState.showVideoForOutgoing if (enableVideo) { newShowVideoForOutgoing = webRtcViewModel.state == WebRtcViewModel.State.CALL_OUTGOING @@ -281,7 +280,6 @@ data class CallParticipantsState( isViewingFocusedParticipant: Boolean = oldState.isViewingFocusedParticipant, isExpanded: Boolean = oldState.localRenderState == WebRtcLocalRenderState.EXPANDED ): WebRtcLocalRenderState { - val displayLocal: Boolean = (numberOfRemoteParticipants == 0 || !isInPip) && (isNonIdleGroupCall || localParticipant.isVideoEnabled) var localRenderState: WebRtcLocalRenderState = WebRtcLocalRenderState.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/compose/ComposeBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/compose/ComposeBottomSheetDialogFragment.kt index a7aad49e72..d313d35873 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/compose/ComposeBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/compose/ComposeBottomSheetDialogFragment.kt @@ -54,7 +54,7 @@ abstract class ComposeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetD * ``` */ @Composable - fun Handle() { + protected fun Handle() { Box( modifier = Modifier .size(width = 48.dp, height = 22.dp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChipViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChipViewModel.kt index 33e9b20b5b..23c7ef21cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChipViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChipViewModel.kt @@ -42,7 +42,7 @@ class ContactChipViewModel : ViewModel() { disposables += getOrCreateRecipientId(selectedContact).map { Recipient.resolved(it) }.observeOn(Schedulers.io()).subscribe { recipient -> store.update { it + SelectedContacts.Model(selectedContact, recipient) } disposableMap[recipient.id]?.dispose() - disposableMap[recipient.id] = store.update(recipient.live().asObservable().toFlowable(BackpressureStrategy.LATEST)) { changedRecipient, state -> + disposableMap[recipient.id] = store.update(recipient.live().observable().toFlowable(BackpressureStrategy.LATEST)) { changedRecipient, state -> val index = state.indexOfFirst { it.selectedContact.matches(selectedContact) } when { index == 0 -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java deleted file mode 100644 index 53b8d0524e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java +++ /dev/null @@ -1,298 +0,0 @@ -package org.thoughtcrime.securesms.contacts; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.text.SpannableStringBuilder; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; -import android.widget.CheckBox; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.content.ContextCompat; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.badges.BadgeImageView; -import org.thoughtcrime.securesms.components.AvatarImageView; -import org.thoughtcrime.securesms.components.FromTextView; -import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto; -import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; -import org.thoughtcrime.securesms.conversation.colors.AvatarColor; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; -import org.thoughtcrime.securesms.profiles.manage.UsernameState; -import org.thoughtcrime.securesms.recipients.LiveRecipient; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.SpanUtil; -import org.thoughtcrime.securesms.util.Util; -import org.thoughtcrime.securesms.util.ViewUtil; - -import java.util.Optional; - - -public class ContactSelectionListItem extends ConstraintLayout implements RecipientForeverObserver { - - @SuppressWarnings("unused") - private static final String TAG = Log.tag(ContactSelectionListItem.class); - - private AvatarImageView contactPhotoImage; - private TextView numberView; - private FromTextView nameView; - private TextView labelView; - private CheckBox checkBox; - private View smsTag; - private BadgeImageView badge; - - private String number; - private String chipName; - private int contactType; - private String contactName; - private String contactNumber; - private String contactLabel; - private String contactAbout; - private LiveRecipient recipient; - private GlideRequests glideRequests; - - private final UsernameFallbackPhotoProvider usernameFallbackPhotoProvider = new UsernameFallbackPhotoProvider(); - - public ContactSelectionListItem(Context context) { - super(context); - } - - public ContactSelectionListItem(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - this.contactPhotoImage = findViewById(R.id.contact_photo_image); - this.numberView = findViewById(R.id.number); - this.labelView = findViewById(R.id.label); - this.nameView = findViewById(R.id.name); - this.checkBox = findViewById(R.id.check_box); - this.smsTag = findViewById(R.id.sms_tag); - this.badge = findViewById(R.id.contact_badge); - - ViewUtil.setTextViewGravityStart(this.nameView, getContext()); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (recipient != null) { - recipient.observeForever(this); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - unbind(); - } - - public void set(@NonNull GlideRequests glideRequests, - @Nullable RecipientId recipientId, - int type, - String name, - String number, - String label, - String about, - boolean checkboxVisible) - { - this.glideRequests = glideRequests; - this.number = number; - this.contactType = type; - this.contactName = name; - this.contactNumber = number; - this.contactLabel = label; - this.contactAbout = about; - - this.contactPhotoImage.setFallbackPhotoProvider(null); - if (type == ContactRepository.NEW_PHONE_TYPE || type == ContactRepository.NEW_USERNAME_TYPE) { - this.recipient = null; - this.contactPhotoImage.setFallbackPhotoProvider(usernameFallbackPhotoProvider); - this.contactPhotoImage.setFallbackPhotoColor(AvatarColor.ON_SURFACE_VARIANT); - this.contactPhotoImage.setAvatar(glideRequests, null, false); - } else if (recipientId != null) { - if (this.recipient != null) { - this.recipient.removeForeverObserver(this); - } - this.recipient = Recipient.live(recipientId); - this.recipient.observeForever(this); - } - - Recipient recipientSnapshot = recipient != null ? recipient.get() : null; - - if (recipientSnapshot != null && !recipientSnapshot.isResolving() && !recipientSnapshot.isMyStory()) { - contactName = recipientSnapshot.getDisplayName(getContext()); - name = contactName; - } else if (recipient != null) { - name = ""; - } - - if (recipientSnapshot == null || recipientSnapshot.isResolving() || recipientSnapshot.isRegistered() || recipientSnapshot.isDistributionList()) { - smsTag.setVisibility(GONE); - } else { - smsTag.setVisibility(VISIBLE); - } - - if (recipientSnapshot == null || recipientSnapshot.isResolving()) { - this.contactPhotoImage.setAvatar(glideRequests, null, false); - setText(null, type, name, number, label, about); - } else if (recipientSnapshot.isMyStory()) { - this.contactPhotoImage.setRecipient(Recipient.self(), false); - setText(recipientSnapshot, type, name, number, label, about); - } else { - this.contactPhotoImage.setAvatar(glideRequests, recipientSnapshot, false); - setText(recipientSnapshot, type, name, number, label, about); - } - - this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE); - - if (recipientSnapshot == null || recipientSnapshot.isSelf()) { - badge.setBadge(null); - } else { - badge.setBadgeFromRecipient(recipientSnapshot); - } - } - - public void setChecked(boolean selected, boolean animate) { - checkBox.setChecked(selected); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - this.checkBox.setEnabled(enabled); - } - - public void unbind() { - if (recipient != null) { - recipient.removeForeverObserver(this); - } - } - - @SuppressLint("SetTextI18n") - private void setText(@Nullable Recipient recipient, int type, String name, String number, String label, @Nullable String about) { - this.numberView.setVisibility(View.VISIBLE); - - if (number == null || number.isEmpty()) { - this.nameView.setEnabled(false); - this.numberView.setText(""); - this.labelView.setVisibility(View.GONE); - } else if (recipient != null && recipient.isGroup()) { - this.nameView.setEnabled(false); - this.numberView.setText(getGroupMemberCount(recipient)); - this.labelView.setVisibility(View.GONE); - } else if (type == ContactRepository.PUSH_TYPE) { - this.numberView.setText(!Util.isEmpty(about) ? about : number); - this.nameView.setEnabled(true); - this.labelView.setVisibility(View.GONE); - } else if (type == ContactRepository.NEW_USERNAME_TYPE) { - this.numberView.setVisibility(View.GONE); - this.nameView.setEnabled(true); - this.labelView.setVisibility(View.GONE); - } else if (recipient != null && recipient.isDistributionList()) { - this.numberView.setText(getViewerCount(number)); - this.labelView.setVisibility(View.GONE); - } else { - this.numberView.setText(!Util.isEmpty(about) ? about : number); - this.nameView.setEnabled(true); - this.labelView.setText(label != null && !label.equals("null") ? getResources().getString(R.string.ContactSelectionListItem__dot_s, label) : ""); - this.labelView.setVisibility(View.VISIBLE); - } - - if (recipient != null) { - this.nameView.setText(recipient); - chipName = recipient.getShortDisplayName(getContext()); - } else if (type == ContactRepository.NEW_USERNAME_TYPE && number != null) { - this.nameView.setText(presentUsername(number)); - } else { - this.nameView.setText(name); - chipName = name; - } - } - - public String getNumber() { - return number; - } - - public String getChipName() { - return chipName; - } - - private String getGroupMemberCount(@NonNull Recipient recipient) { - if (!recipient.isGroup()) { - throw new AssertionError(); - } - int memberCount = recipient.getParticipantIds().size(); - return getContext().getResources().getQuantityString(R.plurals.contact_selection_list_item__number_of_members, memberCount, memberCount); - } - - private String getViewerCount(@NonNull String number) { - int viewerCount = Integer.parseInt(number); - return getContext().getResources().getQuantityString(R.plurals.contact_selection_list_item__number_of_viewers, viewerCount, viewerCount); - } - - private CharSequence presentUsername(@NonNull String username) { - if (username.contains(UsernameState.DELIMITER)) { - return username; - } else { - return new SpannableStringBuilder(username).append(SpanUtil.color(ContextCompat.getColor(getContext(), R.color.signal_colorOutline), UsernameState.DELIMITER)); - } - } - - public @Nullable LiveRecipient getRecipient() { - return recipient; - } - - public boolean isUsernameType() { - return contactType == ContactRepository.NEW_USERNAME_TYPE; - } - - public Optional getRecipientId() { - return recipient != null ? Optional.of(recipient.getId()) : Optional.empty(); - } - - @Override - public void onRecipientChanged(@NonNull Recipient recipient) { - if (this.recipient != null && this.recipient.getId().equals(recipient.getId())) { - contactName = recipient.getDisplayName(getContext()); - contactAbout = recipient.getCombinedAboutAndEmoji(); - - if (recipient.isGroup() && recipient.getGroupId().isPresent()) { - contactNumber = recipient.getGroupId().get().toString(); - } else if (recipient.hasE164()) { - contactNumber = PhoneNumberFormatter.prettyPrint(recipient.getE164().orElse("")); - } else if (!recipient.isDistributionList()) { - contactNumber = recipient.getEmail().orElse(""); - } - - if (recipient.isMyStory()) { - contactPhotoImage.setRecipient(Recipient.self(), false); - } else { - contactPhotoImage.setAvatar(glideRequests, recipient, false); - } - - setText(recipient, contactType, contactName, contactNumber, contactLabel, contactAbout); - smsTag.setVisibility(recipient.isRegistered() || recipient.isDistributionList() ? GONE : VISIBLE); - badge.setBadgeFromRecipient(recipient); - } else { - Log.w(TAG, "Bad change! Local recipient doesn't match. Ignoring. Local: " + (this.recipient == null ? "null" : this.recipient.getId()) + ", Changed: " + recipient.getId()); - } - } - - private static class UsernameFallbackPhotoProvider extends Recipient.FallbackPhotoProvider { - @Override - public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() { - return new ResourceContactPhoto(R.drawable.ic_search_24, R.drawable.ic_search_24, R.drawable.ic_search_24); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt index 93e5faaace..be65b4feae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter @@ -38,7 +39,7 @@ open class ContactSearchAdapter( fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: DisplaySmsTag, - displayPhoneNumber: DisplayPhoneNumber, + displaySecondaryInformation: DisplaySecondaryInformation, onClickCallbacks: ClickCallbacks, longClickCallbacks: LongClickCallbacks, storyContextMenuCallbacks: StoryContextMenuCallbacks @@ -46,7 +47,7 @@ open class ContactSearchAdapter( init { registerStoryItems(this, displayCheckBox, onClickCallbacks::onStoryClicked, storyContextMenuCallbacks) - registerKnownRecipientItems(this, fixedContacts, displayCheckBox, displaySmsTag, displayPhoneNumber, onClickCallbacks::onKnownRecipientClicked, longClickCallbacks::onKnownRecipientLongClick) + registerKnownRecipientItems(this, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, onClickCallbacks::onKnownRecipientClicked, longClickCallbacks::onKnownRecipientLongClick) registerHeaders(this) registerExpands(this, onClickCallbacks::onExpandClicked) registerFactory(UnknownRecipientModel::class.java, LayoutFactory({ UnknownRecipientViewHolder(it, onClickCallbacks::onUnknownRecipientClicked, displayCheckBox) }, R.layout.contact_search_unknown_item)) @@ -83,13 +84,13 @@ open class ContactSearchAdapter( fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: DisplaySmsTag, - displayPhoneNumber: DisplayPhoneNumber, + displaySecondaryInformation: DisplaySecondaryInformation, recipientListener: OnClickedCallback, recipientLongClickCallback: OnLongClickedCallback ) { mappingAdapter.registerFactory( RecipientModel::class.java, - LayoutFactory({ KnownRecipientViewHolder(it, fixedContacts, displayCheckBox, displaySmsTag, displayPhoneNumber, recipientListener, recipientLongClickCallback) }, R.layout.contact_search_item) + LayoutFactory({ KnownRecipientViewHolder(it, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, recipientListener, recipientLongClickCallback) }, R.layout.contact_search_item) ) } @@ -350,7 +351,7 @@ open class ContactSearchAdapter( private val fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: DisplaySmsTag, - private val displayPhoneNumber: DisplayPhoneNumber, + private val displaySecondaryInformation: DisplaySecondaryInformation, onClick: OnClickedCallback, private val onLongClick: OnLongClickedCallback ) : BaseRecipientViewHolder(itemView, displayCheckBox, displaySmsTag, onClick), LetterHeaderDecoration.LetterHeaderItem { @@ -369,8 +370,11 @@ open class ContactSearchAdapter( val count = recipient.participantIds.size number.text = context.resources.getQuantityString(R.plurals.ContactSearchItems__group_d_members, count, count) number.visible = true - } else if (displayPhoneNumber == DisplayPhoneNumber.ALWAYS && recipient.hasE164()) { - number.text = recipient.requireE164() + } else if (displaySecondaryInformation == DisplaySecondaryInformation.ALWAYS && recipient.combinedAboutAndEmoji != null) { + number.text = recipient.combinedAboutAndEmoji + number.visible = true + } else if (displaySecondaryInformation == DisplaySecondaryInformation.ALWAYS && recipient.hasE164()) { + number.text = PhoneNumberFormatter.prettyPrint(recipient.requireE164()) number.visible = true } else { super.bindNumberField(model) @@ -380,9 +384,18 @@ open class ContactSearchAdapter( } override fun bindCheckbox(model: RecipientModel) { + super.bindCheckbox(model) + + if (fixedContacts.contains(model.knownRecipient.contactSearchKey)) { + checkbox.isChecked = true + } checkbox.isEnabled = !fixedContacts.contains(model.knownRecipient.contactSearchKey) } + override fun isEnabled(model: RecipientModel): Boolean { + return !fixedContacts.contains(model.knownRecipient.contactSearchKey) + } + override fun getHeaderLetter(): String? { return headerLetter } @@ -411,11 +424,12 @@ open class ContactSearchAdapter( protected val smsTag: View = itemView.findViewById(R.id.sms_tag) override fun bind(model: T) { - checkbox.visible = displayCheckBox - checkbox.isChecked = isSelected(model) + if (isEnabled(model)) { + itemView.setOnClickListener { onClick.onClicked(avatar, getData(model), isSelected(model)) } + bindLongPress(model) + } - itemView.setOnClickListener { onClick.onClicked(avatar, getData(model), isSelected(model)) } - bindLongPress(model) + bindCheckbox(model) if (payload.isNotEmpty()) { return @@ -430,7 +444,12 @@ open class ContactSearchAdapter( bindSmsTagField(model) } - protected open fun bindCheckbox(model: T) = Unit + protected open fun bindCheckbox(model: T) { + checkbox.visible = displayCheckBox + checkbox.isChecked = isSelected(model) + } + + protected open fun isEnabled(model: T): Boolean = true protected open fun bindAvatar(model: T) { avatar.setAvatar(getRecipient(model)) @@ -607,7 +626,11 @@ open class ContactSearchAdapter( NEVER } - enum class DisplayPhoneNumber { + /** + * Whether or not we should display a recipient's 'about' or e164, if either are + * available. + */ + enum class DisplaySecondaryInformation { NEVER, ALWAYS } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchMediator.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchMediator.kt index 9ea4ac772e..d91b345530 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchMediator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchMediator.kt @@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit * @param selectionLimits [SelectionLimits] describing how large the result set can be. * @param displayCheckBox Whether or not to display checkboxes on items. * @param displaySmsTag Whether or not to display the SMS tag on items. - * @param displayPhoneNumber Whether or not to display phone numbers on known contacts. + * @param displaySecondaryInformation Whether or not to display phone numbers on known contacts. * @param mapStateToConfiguration Maps a [ContactSearchState] to a [ContactSearchConfiguration] * @param callbacks Hooks to help process, filter, and react to selection * @param performSafetyNumberChecks Whether to perform safety number checks for selected users @@ -44,12 +44,12 @@ class ContactSearchMediator( selectionLimits: SelectionLimits, displayCheckBox: Boolean, displaySmsTag: ContactSearchAdapter.DisplaySmsTag, - displayPhoneNumber: ContactSearchAdapter.DisplayPhoneNumber, + displaySecondaryInformation: ContactSearchAdapter.DisplaySecondaryInformation, mapStateToConfiguration: (ContactSearchState) -> ContactSearchConfiguration, private val callbacks: Callbacks = SimpleCallbacks(), performSafetyNumberChecks: Boolean = true, adapterFactory: AdapterFactory = DefaultAdapterFactory, - arbitraryRepository: ArbitraryRepository? = null, + arbitraryRepository: ArbitraryRepository? = null ) { private val queryDebouncer = Debouncer(300, TimeUnit.MILLISECONDS) @@ -71,7 +71,7 @@ class ContactSearchMediator( fixedContacts = fixedContacts, displayCheckBox = displayCheckBox, displaySmsTag = displaySmsTag, - displayPhoneNumber = displayPhoneNumber, + displaySecondaryInformation = displaySecondaryInformation, callbacks = object : ContactSearchAdapter.ClickCallbacks { override fun onStoryClicked(view: View, story: ContactSearchData.Story, isSelected: Boolean) { toggleStorySelection(view, story, isSelected) @@ -232,7 +232,7 @@ class ContactSearchMediator( fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: ContactSearchAdapter.DisplaySmsTag, - displayPhoneNumber: ContactSearchAdapter.DisplayPhoneNumber, + displaySecondaryInformation: ContactSearchAdapter.DisplaySecondaryInformation, callbacks: ContactSearchAdapter.ClickCallbacks, longClickCallbacks: ContactSearchAdapter.LongClickCallbacks, storyContextMenuCallbacks: ContactSearchAdapter.StoryContextMenuCallbacks @@ -245,12 +245,12 @@ class ContactSearchMediator( fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: ContactSearchAdapter.DisplaySmsTag, - displayPhoneNumber: ContactSearchAdapter.DisplayPhoneNumber, + displaySecondaryInformation: ContactSearchAdapter.DisplaySecondaryInformation, callbacks: ContactSearchAdapter.ClickCallbacks, longClickCallbacks: ContactSearchAdapter.LongClickCallbacks, storyContextMenuCallbacks: ContactSearchAdapter.StoryContextMenuCallbacks ): PagingMappingAdapter { - return ContactSearchAdapter(context, fixedContacts, displayCheckBox, displaySmsTag, displayPhoneNumber, callbacks, longClickCallbacks, storyContextMenuCallbacks) + return ContactSearchAdapter(context, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, callbacks, longClickCallbacks, storyContextMenuCallbacks) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchRepository.kt index 0f872586db..b24584a315 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchRepository.kt @@ -20,6 +20,7 @@ class ContactSearchRepository { contactSearchKeys.map { val isSelectable = when (it) { is ContactSearchKey.RecipientSearchKey -> canSelectRecipient(it.recipientId) + is ContactSearchKey.UnknownRecipientKey -> it.sectionKey == ContactSearchConfiguration.SectionKey.PHONE_NUMBER else -> false } ContactSearchSelectionResult(it, isSelectable) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchViewModel.kt index 56548e456e..55d457464b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchViewModel.kt @@ -28,12 +28,13 @@ class ContactSearchViewModel( private val selectionLimits: SelectionLimits, private val contactSearchRepository: ContactSearchRepository, private val performSafetyNumberChecks: Boolean, - private val safetyNumberRepository: SafetyNumberRepository = SafetyNumberRepository(), private val arbitraryRepository: ArbitraryRepository?, private val searchRepository: SearchRepository, private val contactSearchPagedDataSourceRepository: ContactSearchPagedDataSourceRepository ) : ViewModel() { + private val safetyNumberRepository: SafetyNumberRepository by lazy { SafetyNumberRepository() } + private val disposables = CompositeDisposable() private val pagingConfig = PagingConfig.Builder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/GroupsInCommon.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/GroupsInCommon.kt index 3a908744c1..b8641bb5bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/GroupsInCommon.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/GroupsInCommon.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.contacts.paged import android.content.Context +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R /** @@ -12,12 +13,22 @@ data class GroupsInCommon( ) { fun toDisplayText(context: Context): String { return when (total) { + 0 -> { + Log.w(TAG, "Member with no groups in common!") + return "" + } 1 -> context.getString(R.string.MessageRequestProfileView_member_of_one_group, names[0]) 2 -> context.getString(R.string.MessageRequestProfileView_member_of_two_groups, names[0], names[1]) else -> context.getString( - R.string.MessageRequestProfileView_member_of_many_groups, names[0], names[1], + R.string.MessageRequestProfileView_member_of_many_groups, + names[0], + names[1], context.resources.getQuantityString(R.plurals.MessageRequestProfileView_member_of_d_additional_groups, total - 2, total - 2) ) } } + + companion object { + private val TAG = Log.tag(GroupsInCommon::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt index 180c54949a..579323a08a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt @@ -58,7 +58,7 @@ object ContactDiscovery { if (!SignalStore.registrationValues().isRegistrationComplete) { Log.w(TAG, "Registration is not yet complete. Skipping, but running a routine to possibly mark it complete.") - RegistrationUtil.maybeMarkRegistrationComplete(context) + RegistrationUtil.maybeMarkRegistrationComplete() return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt index 1cd9e989d7..f187dd3ffc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.os.Bundle import android.view.MotionEvent import android.view.View +import android.view.Window import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar import org.thoughtcrime.securesms.PassphraseRequiredActivity @@ -11,9 +12,11 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.HidingLinearLayout import org.thoughtcrime.securesms.components.reminder.ReminderView import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.util.Debouncer import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme import org.thoughtcrime.securesms.util.DynamicTheme import org.thoughtcrime.securesms.util.views.Stub +import java.util.concurrent.TimeUnit open class ConversationActivity : PassphraseRequiredActivity(), ConversationParentFragment.Callback { @@ -21,6 +24,7 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare private const val STATE_WATERMARK = "share_data_watermark" } + private val transitionDebouncer: Debouncer = Debouncer(150, TimeUnit.MILLISECONDS) private lateinit var fragment: ConversationParentFragment private var shareDataTimestamp: Long = -1L @@ -30,6 +34,10 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare } override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + supportPostponeEnterTransition() + transitionDebouncer.publish { supportStartPostponedEnterTransition() } + window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) + if (savedInstanceState != null) { shareDataTimestamp = savedInstanceState.getLong(STATE_WATERMARK, -1L) } else if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) { @@ -44,6 +52,11 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare } } + override fun onDestroy() { + super.onDestroy() + transitionDebouncer.clear() + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(STATE_WATERMARK, shareDataTimestamp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationContextMenu.kt index 82344f59a0..a2c257d493 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationContextMenu.kt @@ -18,14 +18,14 @@ import org.thoughtcrime.securesms.components.menu.ContextMenuList class ConversationContextMenu(private val anchor: View, items: List) : PopupWindow( LayoutInflater.from(anchor.context).inflate(R.layout.signal_context_menu, null), ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT ) { val context: Context = anchor.context private val contextMenuList = ContextMenuList( recyclerView = contentView.findViewById(R.id.signal_context_menu_list), - onItemClick = { dismiss() }, + onItemClick = { dismiss() } ) init { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 938d032488..17f7274ce4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -21,6 +21,7 @@ import android.animation.LayoutTransition; import android.animation.ValueAnimator; import android.annotation.SuppressLint; +import android.app.ActivityOptions; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -38,6 +39,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.Window; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; @@ -68,6 +70,7 @@ import com.annimon.stream.Stream; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback; import org.jetbrains.annotations.NotNull; import org.signal.core.util.DimensionUnit; @@ -131,6 +134,8 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.longmessage.LongMessageFragment; import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder; +import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity; import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment; import org.thoughtcrime.securesms.messagerequests.MessageRequestState; import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel; @@ -721,6 +726,7 @@ public void onItemRangeInserted(int positionStart, int itemCount) { startupStopwatch.split("first-render"); startupStopwatch.stop(TAG); SignalLocalMetrics.ConversationOpen.onRenderFinished(); + listener.onFirstRender(); }); } }); @@ -1455,6 +1461,7 @@ void handleReaction(@NonNull ConversationMessage conversationMessage, @NonNull ConversationReactionOverlay.OnHideListener onHideListener); void onCursorChanged(); void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); + void onFirstRender(); void onVoiceNotePause(@NonNull Uri uri); void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress); void onVoiceNoteResume(@NonNull Uri uri, long messageId); @@ -1463,6 +1470,7 @@ void handleReaction(@NonNull ConversationMessage conversationMessage, void onRegisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); void onUnregisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); void onInviteToSignal(); + boolean isInBubble(); } private class ConversationScrollListener extends OnScrollListener { @@ -1638,8 +1646,11 @@ public void onItemLongClick(View itemView, MultiselectPart item) { listener.onVoiceNoteResume(selectedConversationModel.getAudioUri(), messageRecord.getId()); } - WindowUtil.setLightStatusBarFromTheme(requireActivity()); - WindowUtil.setLightNavigationBarFromTheme(requireActivity()); + if (getActivity() != null) { + WindowUtil.setLightStatusBarFromTheme(requireActivity()); + WindowUtil.setLightNavigationBarFromTheme(requireActivity()); + } + clearFocusedItem(); if (finalMp4Holder != null) { @@ -2049,6 +2060,29 @@ public void onInviteToSignalClicked() { listener.onInviteToSignal(); } + @Override + public void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args) { + if (listener.isInBubble()) { + requireActivity().startActivity(MediaIntentFactory.create(requireActivity(), args)); + return; + } + + if (args.isVideoGif()) { + int adapterPosition = list.getChildAdapterPosition(parent); + GiphyMp4ProjectionPlayerHolder holder = giphyMp4ProjectionRecycler.getCurrentHolder(adapterPosition); + + if (holder != null) { + parent.showProjectionArea(); + holder.hide(); + } + } + + sharedElement.setTransitionName(MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME); + requireActivity().setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback()); + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), sharedElement, MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME); + requireActivity().startActivity(MediaIntentFactory.create(requireActivity(), args), options.toBundle()); + } + @Override public void onActivatePaymentsClicked() { Intent intent = new Intent(requireContext(), PaymentsActivity.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index c6301a056a..760c14bc67 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -82,6 +82,7 @@ import org.thoughtcrime.securesms.components.PlaybackSpeedToggleTextView; import org.thoughtcrime.securesms.components.QuoteView; import org.thoughtcrime.securesms.components.SharedContactView; +import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.contactshare.Contact; @@ -105,6 +106,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache; import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.ImageSlide; @@ -154,7 +156,6 @@ * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. * * @author Moxie Marlinspike - * */ public final class ConversationItem extends RelativeLayout implements BindableConversationItem, @@ -162,11 +163,11 @@ public final class ConversationItem extends RelativeLayout implements BindableCo { private static final String TAG = Log.tag(ConversationItem.class); - private static final int MAX_MEASURE_CALLS = 3; + private static final int MAX_MEASURE_CALLS = 3; private static final Rect SWIPE_RECT = new Rect(); - public static final float LONG_PRESS_SCALE_FACTOR = 0.95f; + public static final float LONG_PRESS_SCALE_FACTOR = 0.95f; private static final int SHRINK_BUBBLE_DELAY_MILLIS = 100; private static final long MAX_CLUSTERING_TIME_DIFF = TimeUnit.MINUTES.toMillis(3); private static final int CONDENSED_MODE_MAX_LINES = 3; @@ -183,24 +184,24 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private Optional previousMessage; private ConversationItemDisplayMode displayMode; - protected ConversationItemBodyBubble bodyBubble; - protected View reply; - protected View replyIcon; + protected ConversationItemBodyBubble bodyBubble; + protected View reply; + protected View replyIcon; @Nullable protected ViewGroup contactPhotoHolder; @Nullable private QuoteView quoteView; - private EmojiTextView bodyText; - private ConversationItemFooter footer; + private EmojiTextView bodyText; + private ConversationItemFooter footer; @Nullable private ConversationItemFooter stickerFooter; @Nullable private TextView groupSender; @Nullable private View groupSenderHolder; - private AvatarImageView contactPhoto; - private AlertView alertView; - protected ReactionsConversationView reactionsView; - protected BadgeImageView badgeImageView; - private View storyReactionLabelWrapper; - private TextView storyReactionLabel; - protected View quotedIndicator; - protected View scheduledIndicator; + private AvatarImageView contactPhoto; + private AlertView alertView; + protected ReactionsConversationView reactionsView; + protected BadgeImageView badgeImageView; + private View storyReactionLabelWrapper; + private TextView storyReactionLabel; + protected View quotedIndicator; + protected View scheduledIndicator; private @NonNull Set batchSelected = new HashSet<>(); private @NonNull Outliner outliner = new Outliner(); @@ -296,33 +297,33 @@ protected void onFinishInflate() { initializeAttributes(); - this.bodyText = findViewById(R.id.conversation_item_body); - this.footer = findViewById(R.id.conversation_item_footer); - this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer); - this.groupSender = findViewById(R.id.group_message_sender); - this.alertView = findViewById(R.id.indicators_parent); - this.contactPhoto = findViewById(R.id.contact_photo); - this.contactPhotoHolder = findViewById(R.id.contact_photo_container); - this.bodyBubble = findViewById(R.id.body_bubble); + this.bodyText = findViewById(R.id.conversation_item_body); + this.footer = findViewById(R.id.conversation_item_footer); + this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer); + this.groupSender = findViewById(R.id.group_message_sender); + this.alertView = findViewById(R.id.indicators_parent); + this.contactPhoto = findViewById(R.id.contact_photo); + this.contactPhotoHolder = findViewById(R.id.contact_photo_container); + this.bodyBubble = findViewById(R.id.body_bubble); this.mediaThumbnailStub = new NullableStub<>(findViewById(R.id.image_view_stub)); - this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub)); - this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub)); - this.sharedContactStub = new Stub<>(findViewById(R.id.shared_contact_view_stub)); - this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub)); - this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub)); - this.revealableStub = new Stub<>(findViewById(R.id.revealable_view_stub)); - this.callToActionStub = ViewUtil.findStubById(this, R.id.conversation_item_call_to_action_stub); - this.groupSenderHolder = findViewById(R.id.group_sender_holder); - this.quoteView = findViewById(R.id.quote_view); - this.reply = findViewById(R.id.reply_icon_wrapper); - this.replyIcon = findViewById(R.id.reply_icon); - this.reactionsView = findViewById(R.id.reactions_view); - this.badgeImageView = findViewById(R.id.badge); - this.storyReactionLabelWrapper = findViewById(R.id.story_reacted_label_holder); - this.storyReactionLabel = findViewById(R.id.story_reacted_label); - this.quotedIndicator = findViewById(R.id.quoted_indicator); - this.paymentViewStub = new Stub<>(findViewById(R.id.payment_view_stub)); - this.scheduledIndicator = findViewById(R.id.scheduled_indicator); + this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub)); + this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub)); + this.sharedContactStub = new Stub<>(findViewById(R.id.shared_contact_view_stub)); + this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub)); + this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub)); + this.revealableStub = new Stub<>(findViewById(R.id.revealable_view_stub)); + this.callToActionStub = ViewUtil.findStubById(this, R.id.conversation_item_call_to_action_stub); + this.groupSenderHolder = findViewById(R.id.group_sender_holder); + this.quoteView = findViewById(R.id.quote_view); + this.reply = findViewById(R.id.reply_icon_wrapper); + this.replyIcon = findViewById(R.id.reply_icon); + this.reactionsView = findViewById(R.id.reactions_view); + this.badgeImageView = findViewById(R.id.badge); + this.storyReactionLabelWrapper = findViewById(R.id.story_reacted_label_holder); + this.storyReactionLabel = findViewById(R.id.story_reacted_label); + this.quotedIndicator = findViewById(R.id.quoted_indicator); + this.paymentViewStub = new Stub<>(findViewById(R.id.payment_view_stub)); + this.scheduledIndicator = findViewById(R.id.scheduled_indicator); setOnClickListener(new ClickListener(null)); @@ -355,20 +356,20 @@ public void bind(@NonNull LifecycleOwner lifecycleOwner, conversationRecipient = conversationRecipient.resolve(); - this.conversationMessage = conversationMessage; - this.messageRecord = conversationMessage.getMessageRecord(); - this.nextMessageRecord = nextMessageRecord; - this.locale = locale; - this.glideRequests = glideRequests; - this.batchSelected = batchSelected; - this.conversationRecipient = conversationRecipient.live(); - this.groupThread = conversationRecipient.isGroup(); - this.recipient = messageRecord.getIndividualRecipient().live(); - this.canPlayContent = false; - this.mediaItem = null; - this.colorizer = colorizer; - this.displayMode = displayMode; - this.previousMessage = previousMessageRecord; + this.conversationMessage = conversationMessage; + this.messageRecord = conversationMessage.getMessageRecord(); + this.nextMessageRecord = nextMessageRecord; + this.locale = locale; + this.glideRequests = glideRequests; + this.batchSelected = batchSelected; + this.conversationRecipient = conversationRecipient.live(); + this.groupThread = conversationRecipient.isGroup(); + this.recipient = messageRecord.getIndividualRecipient().live(); + this.canPlayContent = false; + this.mediaItem = null; + this.colorizer = colorizer; + this.displayMode = displayMode; + this.previousMessage = previousMessageRecord; this.recipient.observeForever(this); this.conversationRecipient.observeForever(this); @@ -492,7 +493,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (quoteWidth != availableWidth) { quoteView.getLayoutParams().width = availableWidth; - needsMeasure = true; + needsMeasure = true; } } @@ -500,15 +501,15 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int defaultBottomMargin = readDimen(R.dimen.message_bubble_bottom_padding); int collapsedBottomMargin = readDimen(R.dimen.message_bubble_collapsed_bottom_padding); - if (!updatingFooter && - getActiveFooter(messageRecord) == footer && - !hasAudio(messageRecord) && - !isStoryReaction(messageRecord) && + if (!updatingFooter && + getActiveFooter(messageRecord) == footer && + !hasAudio(messageRecord) && + !isStoryReaction(messageRecord) && isFooterVisible(messageRecord, nextMessageRecord, groupThread) && - !bodyText.isJumbomoji() && - conversationMessage.getBottomButton() == null && - !StringUtil.hasMixedTextDirection(bodyText.getText()) && - !messageRecord.isRemoteDelete() && + !bodyText.isJumbomoji() && + conversationMessage.getBottomButton() == null && + !StringUtil.hasMixedTextDirection(bodyText.getText()) && + !messageRecord.isRemoteDelete() && bodyText.getLastLineWidth() > 0) { TextView dateView = footer.getDateView(); @@ -557,7 +558,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (contactWidth != availableWidth) { sharedContactStub.get().getLayoutParams().width = availableWidth; - needsMeasure = true; + needsMeasure = true; } } @@ -567,13 +568,13 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (activeFooter.getVisibility() != GONE && activeFooter.getMeasuredWidth() != availableWidth) { activeFooter.getLayoutParams().width = availableWidth; - needsMeasure = true; + needsMeasure = true; } int desiredWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get()); if (bodyBubble.getMeasuredWidth() != desiredWidth) { bodyBubble.getLayoutParams().width = desiredWidth; - needsMeasure = true; + needsMeasure = true; } } @@ -585,7 +586,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.w(TAG, "Hit measure() cap of " + MAX_MEASURE_CALLS); } } else { - measureCalls = 0; + measureCalls = 0; updatingFooter = false; } } @@ -689,7 +690,7 @@ public void unbind() { private boolean isTouchBelowBoundary(@NonNull View child) { Projection childProjection = Projection.relativeToParent(this, child, null); - float childBoundary = childProjection.getY() + childProjection.getHeight(); + float childBoundary = childProjection.getY() + childProjection.getHeight(); return lastYDownRelativeToThis > childBoundary; } @@ -721,14 +722,14 @@ public int getTopBoundaryOfMultiselectPart(@NonNull MultiselectPart multiselectP private static int getProjectionTop(@NonNull View child) { Projection projection = Projection.relativeToViewRoot(child, null); - int y = (int) projection.getY(); + int y = (int) projection.getY(); projection.release(); return y; } private static int getProjectionBottom(@NonNull View child) { Projection projection = Projection.relativeToViewRoot(child, null); - int bottom = (int) projection.getY() + projection.getHeight(); + int bottom = (int) projection.getY() + projection.getHeight(); projection.release(); return bottom; } @@ -940,7 +941,7 @@ private boolean hasSharedContact(MessageRecord messageRecord) { return MessageRecordUtil.hasSharedContact(messageRecord); } - private boolean hasLinkPreview(MessageRecord messageRecord) { + private boolean hasLinkPreview(MessageRecord messageRecord) { return MessageRecordUtil.hasLinkPreview(messageRecord); } @@ -966,13 +967,13 @@ private void setBodyText(@NonNull MessageRecord messageRecord, bodyText.setMovementMethod(LongClickMovementMethod.getInstance(getContext())); if (messageRecord.isRemoteDelete()) { - String deletedMessage = context.getString(messageRecord.isOutgoing() ? R.string.ConversationItem_you_deleted_this_message : R.string.ConversationItem_this_message_was_deleted); - SpannableString italics = new SpannableString(deletedMessage); + String deletedMessage = context.getString(messageRecord.isOutgoing() ? R.string.ConversationItem_you_deleted_this_message : R.string.ConversationItem_this_message_was_deleted); + SpannableString italics = new SpannableString(deletedMessage); italics.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, deletedMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); italics.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.signal_text_primary)), - 0, - deletedMessage.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + 0, + deletedMessage.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); bodyText.setText(italics); bodyText.setVisibility(View.VISIBLE); @@ -1023,13 +1024,13 @@ private void setBodyText(@NonNull MessageRecord messageRecord, } } - private void setMediaAttributes(@NonNull MessageRecord messageRecord, - @NonNull Optional previousRecord, - @NonNull Optional nextRecord, - boolean isGroupThread, - boolean hasWallpaper, - boolean messageRequestAccepted, - boolean allowedToPlayInline) + private void setMediaAttributes(@NonNull MessageRecord messageRecord, + @NonNull Optional previousRecord, + @NonNull Optional nextRecord, + boolean isGroupThread, + boolean hasWallpaper, + boolean messageRequestAccepted, + boolean allowedToPlayInline) { boolean showControls = !messageRecord.isFailed() && !MessageRecordUtil.isScheduled(messageRecord); @@ -1048,11 +1049,11 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe if (isViewOnceMessage(messageRecord) && !messageRecord.isRemoteDelete()) { revealableStub.get().setVisibility(VISIBLE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); revealableStub.get().setMessage((MmsMessageRecord) messageRecord, hasWallpaper); @@ -1064,12 +1065,12 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe footer.setVisibility(VISIBLE); } else if (hasSharedContact(messageRecord)) { sharedContactStub.get().setVisibility(VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale); @@ -1084,12 +1085,12 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe footer.setVisibility(GONE); } else if (hasLinkPreview(messageRecord) && messageRequestAccepted) { linkPreviewStub.get().setVisibility(View.VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); //noinspection ConstantConditions @@ -1132,11 +1133,11 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe } else if (hasAudio(messageRecord)) { audioViewStub.get().setVisibility(View.VISIBLE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); audioViewStub.get().setAudio(Objects.requireNonNull(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide()), new AudioViewCallbacks(), showControls, true); @@ -1158,11 +1159,11 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe } else if (hasDocument(messageRecord)) { documentViewStub.get().setVisibility(View.VISIBLE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); //noinspection ConstantConditions @@ -1185,11 +1186,11 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe stickerStub.get().setVisibility(View.VISIBLE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); if (hasSticker(messageRecord)) { @@ -1214,12 +1215,12 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe bodyBubble.setBackgroundColor(Color.TRANSPARENT); } else if (hasThumbnail(messageRecord)) { mediaThumbnailStub.require().setVisibility(View.VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); List thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides(); @@ -1252,8 +1253,8 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe footer.setVisibility(VISIBLE); - if (thumbnailSlides.size() == 1 && - thumbnailSlides.get(0).isVideoGif() && + if (thumbnailSlides.size() == 1 && + thumbnailSlides.get(0).isVideoGif() && thumbnailSlides.get(0) instanceof VideoSlide) { Uri uri = thumbnailSlides.get(0).getUri(); @@ -1268,23 +1269,23 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe } else if (isGiftMessage(messageRecord)) { if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(GONE); paymentViewStub.setVisibility(View.GONE); footer.setVisibility(VISIBLE); } else if (messageRecord.isPaymentNotification()) { if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(GONE); MediaMmsMessageRecord mediaMmsMessageRecord = (MediaMmsMessageRecord) messageRecord; @@ -1294,12 +1295,12 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe footer.setVisibility(VISIBLE); } else { if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); + if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); + if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); + if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE); + if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); + if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); + if (revealableStub.resolved()) revealableStub.get().setVisibility(View.GONE); paymentViewStub.setVisibility(View.GONE); ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -1316,7 +1317,7 @@ private void setMediaAttributes(@NonNull MessageRecord messageRe } private void updateRevealableMargins(MessageRecord messageRecord, Optional previous, Optional next, boolean isGroupThread) { - int bigMargin = readDimen(R.dimen.message_bubble_revealable_padding); + int bigMargin = readDimen(R.dimen.message_bubble_revealable_padding); int smallMargin = readDimen(R.dimen.message_bubble_top_padding); //noinspection ConstantConditions @@ -1333,10 +1334,10 @@ private void updateRevealableMargins(MessageRecord messageRecord, Optional previous, @NonNull Optional next, - boolean isGroupThread) + boolean isGroupThread) { int defaultRadius = readDimen(R.dimen.message_corner_radius); int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius); @@ -1401,13 +1402,13 @@ private void setThumbnailCorners(@NonNull MessageRecord current, } private void setSharedContactCorners(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - if (messageRecord.isDisplayBodyEmpty(getContext())){ + if (messageRecord.isDisplayBodyEmpty(getContext())) { if (isSingularMessage(current, previous, next, isGroupThread) || isEndOfMessageCluster(current, next, isGroupThread)) { - sharedContactStub.get().setSingularStyle(); + sharedContactStub.get().setSingularStyle(); } else if (current.isOutgoing()) { - sharedContactStub.get().setClusteredOutgoingStyle(); + sharedContactStub.get().setClusteredOutgoingStyle(); } else { - sharedContactStub.get().setClusteredIncomingStyle(); + sharedContactStub.get().setClusteredIncomingStyle(); } } } @@ -1520,7 +1521,7 @@ private void setQuote(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse); + int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse); int spacingBottom = spacingTop; if (isStartOfMessageCluster(current, previous, isGroupThread)) { @@ -2055,9 +2057,9 @@ public boolean shouldProjectContent() { colorizerProjections.clear(); if ((messageRecord.isOutgoing() || !outgoingOnly) && - !hasNoBubble(messageRecord) && + !hasNoBubble(messageRecord) && !messageRecord.isRemoteDelete() && - bodyBubbleCorners != null && + bodyBubbleCorners != null && bodyBubble.getVisibility() == VISIBLE) { Projection bodyBubbleToRoot = Projection.relativeToParent(coordinateRoot, bodyBubble, bodyBubbleCorners).translateX(bodyBubble.getTranslationX()); @@ -2116,13 +2118,13 @@ public boolean shouldProjectContent() { } } - if ((messageRecord.isOutgoing() || !outgoingOnly) && + if ((messageRecord.isOutgoing() || !outgoingOnly) && hasNoBubble(messageRecord) && - hasWallpaper && + hasWallpaper && bodyBubble.getVisibility() == VISIBLE) { - ConversationItemFooter footer = getActiveFooter(messageRecord); - Projection footerProjection = footer.getProjection(coordinateRoot); + ConversationItemFooter footer = getActiveFooter(messageRecord); + Projection footerProjection = footer.getProjection(coordinateRoot); if (footerProjection != null) { colorizerProjections.add( footerProjection.translateX(bodyBubble.getTranslationX()) @@ -2261,7 +2263,7 @@ public void onClick(View v, final List slides) { for (Slide slide : slides) { ApplicationDependencies.getJobManager().add(new AttachmentDownloadJob(messageRecord.getId(), - ((DatabaseAttachment)slide.asAttachment()).getAttachmentId(), + ((DatabaseAttachment) slide.asAttachment()).getAttachmentId(), true)); } } @@ -2301,6 +2303,10 @@ public void onClick(final View v, final Slide slide) { } else if (!canPlayContent && mediaItem != null && eventListener != null) { eventListener.onPlayInlineContent(conversationMessage); } else if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { + if (eventListener == null) { + return; + } + MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs( messageRecord.getThreadId(), messageRecord.getTimestamp(), @@ -2313,8 +2319,17 @@ public void onClick(final View v, final Slide slide) { false, false, MediaTable.Sorting.Newest, - slide.isVideoGif()); - context.startActivity(MediaIntentFactory.create(context, args)); + slide.isVideoGif(), + new MediaIntentFactory.SharedElementArgs( + slide.asAttachment().getWidth(), + slide.asAttachment().getHeight(), + mediaThumbnailStub.require().getCorners().getTopLeft(), + mediaThumbnailStub.require().getCorners().getTopRight(), + mediaThumbnailStub.require().getCorners().getBottomRight(), + mediaThumbnailStub.require().getCorners().getBottomLeft() + )); + MediaPreviewCache.INSTANCE.setDrawable(((ThumbnailView) v).getImageDrawable()); + eventListener.goToMediaPreview(ConversationItem.this, v, args); } else if (slide.getUri() != null) { Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType()); Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri()); @@ -2428,7 +2443,7 @@ public void onClick(@NonNull View widget) { } @Override - public void updateDrawState(@NonNull TextPaint ds) { } + public void updateDrawState(@NonNull TextPaint ds) {} } private final class AudioPlaybackSpeedToggleListener implements PlaybackSpeedToggleTextView.PlaybackSpeedListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemDisplayMode.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemDisplayMode.kt index f72e1f46dd..641f3f70e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemDisplayMode.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemDisplayMode.kt @@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.conversation enum class ConversationItemDisplayMode { /** Normal rendering, used for normal bubbles in the conversation view */ STANDARD, + /** Smaller bubbles, often trimming text and shrinking images. Used for quote threads. */ CONDENSED, + /** Less length restrictions. Used to show more info in message details. */ - DETAILED, + DETAILED } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt index bf8fc1d7dc..1b930278f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt @@ -20,7 +20,7 @@ object ConversationItemSelection { conversationItem: ConversationItem, list: RecyclerView, messageRecord: MessageRecord, - videoBitmap: Bitmap?, + videoBitmap: Bitmap? ): Bitmap { val isOutgoing = messageRecord.isOutgoing val hasNoBubble = messageRecord.hasNoBubble(conversationItem.context) @@ -30,7 +30,7 @@ object ConversationItemSelection { list = list, videoBitmap = videoBitmap, drawConversationItem = !isOutgoing || hasNoBubble, - hasReaction = messageRecord.reactions.isNotEmpty(), + hasReaction = messageRecord.reactions.isNotEmpty() ) } @@ -39,7 +39,7 @@ object ConversationItemSelection { list: RecyclerView, videoBitmap: Bitmap?, drawConversationItem: Boolean, - hasReaction: Boolean, + hasReaction: Boolean ): Bitmap { val bodyBubble = conversationItem.bodyBubble val reactionsView = conversationItem.reactionsView diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java index e0026014a7..6464d9a948 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java @@ -98,7 +98,7 @@ public long getUniqueId(@NonNull MessageDigest digest) { } public @NonNull SpannableString getDisplayBody(Context context) { - return (body != null) ? body : messageRecord.getDisplayBody(context); + return (body != null) ? new SpannableString(body) : messageRecord.getDisplayBody(context); } public boolean hasStyleLinks() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 96063cca4a..ec7c95a749 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -276,7 +276,6 @@ import org.thoughtcrime.securesms.util.ConversationUtil; import org.thoughtcrime.securesms.util.Debouncer; import org.thoughtcrime.securesms.util.DrawableUtil; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FullscreenHelper; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.LifecycleDisposable; @@ -501,7 +500,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat return; } - voiceNoteMediaController = new VoiceNoteMediaController(requireActivity()); + voiceNoteMediaController = new VoiceNoteMediaController(requireActivity(), true); voiceRecorderWakeLock = new VoiceRecorderWakeLock(requireActivity()); // TODO [alex] LargeScreenSupport -- Should be removed once we move to multi-pane layout. @@ -608,6 +607,8 @@ public void onResume() { if (SignalStore.rateLimit().needsRecaptcha()) { RecaptchaProofBottomSheetFragment.show(getChildFragmentManager()); } + + updateToggleButtonState(); } @Override @@ -962,7 +963,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (recipient != null && recipient.get().isMuted()) inflater.inflate(R.menu.conversation_muted, menu); else inflater.inflate(R.menu.conversation_unmuted, menu); - if (isSingleConversation() && getRecipient().getContactUri() == null && !recipient.get().isReleaseNotes() && !recipient.get().isSelf()) { + if (isSingleConversation() && getRecipient().getContactUri() == null && !recipient.get().isReleaseNotes() && !recipient.get().isSelf() && recipient.get().hasE164()) { inflater.inflate(R.menu.conversation_add_to_contacts, menu); } @@ -2177,7 +2178,8 @@ protected void initializeActionBar() { callback.onInitializeToolbar(toolbar); } - protected boolean isInBubble() { + @Override + public boolean isInBubble() { return callback.isInBubble(); } @@ -2193,7 +2195,12 @@ private void initializeResources(@NonNull ConversationIntents.Args args) { Log.i(TAG, "[initializeResources] Recipient: " + recipient.getId() + ", Thread: " + threadId); - recipient.observe(getViewLifecycleOwner(), this::onRecipientChanged); + disposables.add( + recipient + .observable() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::onRecipientChanged) + ); } private void initializeLinkPreviewObserver() { @@ -3200,13 +3207,8 @@ public void onRecorderStarted() { requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); voiceNoteMediaController.pausePlayback(); - try { - recordingSession = new RecordingSession(audioRecorder.startRecording()); - disposables.add(recordingSession); - } catch (AssertionError err) { - Log.e(TAG, "Could not start audio recording.", err); - Toast.makeText(requireContext(), R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_SHORT).show(); - } + recordingSession = new RecordingSession(audioRecorder.startRecording()); + disposables.add(recordingSession); } @Override @@ -3865,6 +3867,14 @@ public void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord) { } } + @Override + public void onFirstRender() { + if (getActivity() != null) { + requireActivity().supportStartPostponedEnterTransition(); + } + voiceNoteMediaController.finishPostpone(); + } + @Override public void onVoiceNotePause(@NonNull Uri uri) { voiceNoteMediaController.pausePlayback(uri); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java index 1edf340753..7e83078135 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java @@ -126,7 +126,7 @@ private ConversationViewModel() { this.recipientId = BehaviorSubject.create(); this.threadId = BehaviorSubject.create(); this.groupAuthorNameColorHelper = new GroupAuthorNameColorHelper(); - this.conversationStateStore = new RxStore<>(ConversationState.create(), Schedulers.io()); + this.conversationStateStore = new RxStore<>(ConversationState.create(), Schedulers.computation()); this.disposables = new CompositeDisposable(); this.conversationStateTick = BehaviorSubject.createDefault(Unit.INSTANCE); this.markReadRequestPublisher = PublishProcessor.create(); @@ -206,7 +206,7 @@ private ConversationViewModel() { .observeOn(Schedulers.io()) .switchMap(scheduledMessagesRepository::getScheduledMessageCount); - Observable liveRecipient = recipientId.distinctUntilChanged().switchMap(id -> Recipient.live(id).asObservable()); + Observable liveRecipient = recipientId.distinctUntilChanged().switchMap(id -> Recipient.live(id).observable()); canShowAsBubble = threadId.observeOn(Schedulers.io()).map(conversationRepository::canShowAsBubble); wallpaper = liveRecipient.map(r -> Optional.ofNullable(r.getWallpaper())).distinctUntilChanged(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduleMessageTimePickerBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduleMessageTimePickerBottomSheet.kt index 3b68d9a124..f3127cede1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduleMessageTimePickerBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduleMessageTimePickerBottomSheet.kt @@ -83,7 +83,7 @@ class ScheduleMessageTimePickerBottomSheet : FixedRoundedCornerBottomSheetDialog text = getString( R.string.ScheduleMessageTimePickerBottomSheet__timezone_disclaimer, zoneOffsetFormatter.format(zonedDateTime), - zoneNameFormatter.format(zonedDateTime), + zoneNameFormatter.format(zonedDateTime) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt index 64a36e08cd..0a23fe3b6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt @@ -17,5 +17,5 @@ data class SelectedConversationModel( val bubbleWidth: Int, val audioUri: Uri? = null, val isOutgoing: Boolean, - val focusedView: View?, + val focusedView: View? ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColorsPalette.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColorsPalette.kt index be325207f6..5d8665a0b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColorsPalette.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ChatColorsPalette.kt @@ -50,12 +50,14 @@ object ChatColorsPalette { @JvmField val TAUPE = ChatColors.forColor( - ChatColors.Id.BuiltIn, 0xFF8F616A.toInt() + ChatColors.Id.BuiltIn, + 0xFF8F616A.toInt() ) @JvmField val STEEL = ChatColors.forColor( - ChatColors.Id.BuiltIn, 0xFF71717F.toInt() + ChatColors.Id.BuiltIn, + 0xFF71717F.toInt() ) // endregion @@ -128,7 +130,8 @@ object ChatColorsPalette { ChatColors.LinearGradient( 180f, intArrayOf( - 0xFF6281D5.toInt(), 0xFF974460.toInt() + 0xFF6281D5.toInt(), + 0xFF974460.toInt() ), floatArrayOf(0f, 1f) ) @@ -216,7 +219,7 @@ object ChatColorsPalette { NameColor(lightColor = 0xFF5E6E0C.toInt(), darkColor = 0xFF8FAA09.toInt()), NameColor(lightColor = 0xFF077288.toInt(), darkColor = 0xFF00AED1.toInt()), NameColor(lightColor = 0xFFC20AA3.toInt(), darkColor = 0xFFF75FDD.toInt()), - NameColor(lightColor = 0xFF2D761E.toInt(), darkColor = 0xFF43B42D.toInt()), + NameColor(lightColor = 0xFF2D761E.toInt(), darkColor = 0xFF43B42D.toInt()) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/RecyclerViewColorizer.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/RecyclerViewColorizer.kt index c505ad7498..e3b9011a4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/RecyclerViewColorizer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/RecyclerViewColorizer.kt @@ -137,11 +137,19 @@ class RecyclerViewColorizer(private val recyclerView: RecyclerView) { outOfBoundsPaint.color = color canvas.drawRect( - 0f, -parent.height.toFloat(), parent.width.toFloat(), 0f, outOfBoundsPaint + 0f, + -parent.height.toFloat(), + parent.width.toFloat(), + 0f, + outOfBoundsPaint ) canvas.drawRect( - 0f, parent.height.toFloat(), parent.width.toFloat(), parent.height * 2f, outOfBoundsPaint + 0f, + parent.height.toFloat(), + parent.width.toFloat(), + parent.height * 2f, + outOfBoundsPaint ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorCreatorPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorCreatorPageFragment.kt index 24a3c54078..fa68ea800b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorCreatorPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorCreatorPageFragment.kt @@ -188,7 +188,7 @@ class CustomChatColorCreatorPageFragment : state.degrees, intArrayOf(topEdgeColor.getColor(), bottomEdgeColor.getColor()), floatArrayOf(0f, 1f) - ), + ) ) preview.setChatColors(chatColor) gradientTool.setSelectedEdge(state.selectedEdge) @@ -252,14 +252,15 @@ class CustomChatColorCreatorPageFragment : return listOf(0f, 1f).map { ColorUtils.HSLToColor( floatArrayOf( - hue, it, level + hue, + it, + level ) ) }.toIntArray() } private fun calculateLightness(hue: Float, valueFor60To80: Float = 0.3f): Float { - val point1 = PointF() val point2 = PointF() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorGradientToolView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorGradientToolView.kt index bf7a8517b5..e1781a948a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorGradientToolView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/custom/CustomChatColorGradientToolView.kt @@ -149,7 +149,6 @@ class CustomChatColorGradientToolView @JvmOverloads constructor( } override fun onTouchEvent(event: MotionEvent): Boolean { - if (event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP) { listener?.onGestureFinished() } else if (event.action == MotionEvent.ACTION_DOWN) { @@ -293,7 +292,6 @@ class CustomChatColorGradientToolView @JvmOverloads constructor( distanceX: Float, distanceY: Float ): Boolean { - val a = PointF(e2.getX(activePointerId) - center.x, e2.getY(activePointerId) - center.y) val b = PointF(center.x, 0f) val dot = a.dotProduct(b) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftState.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftState.kt index abb0f9e905..911839b0a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftState.kt @@ -17,7 +17,7 @@ data class DraftState( val bodyRangesDraft: DraftTable.Draft? = null, val quoteDraft: DraftTable.Draft? = null, val locationDraft: DraftTable.Draft? = null, - val voiceNoteDraft: DraftTable.Draft? = null, + val voiceNoteDraft: DraftTable.Draft? = null ) { fun copyAndClearDrafts(threadId: Long = this.threadId): DraftState { @@ -41,7 +41,7 @@ data class DraftState( bodyRangesDraft = drafts.getDraftOfType(DraftTable.Draft.BODY_RANGES), quoteDraft = drafts.getDraftOfType(DraftTable.Draft.QUOTE), locationDraft = drafts.getDraftOfType(DraftTable.Draft.LOCATION), - voiceNoteDraft = drafts.getDraftOfType(DraftTable.Draft.VOICE_NOTE), + voiceNoteDraft = drafts.getDraftOfType(DraftTable.Draft.VOICE_NOTE) ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt index dada7a5119..9d3badbb22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt @@ -9,6 +9,8 @@ import androidx.appcompat.widget.Toolbar import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment +import org.signal.core.util.getParcelableArrayListExtraCompat +import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FragmentWrapperActivity @@ -22,7 +24,7 @@ open class MultiselectForwardActivity : FragmentWrapperActivity(), MultiselectFo private const val ARGS = "args" } - private val args: MultiselectForwardFragmentArgs get() = intent.getParcelableExtra(ARGS)!! + private val args: MultiselectForwardFragmentArgs get() = intent.getParcelableExtraCompat(ARGS, MultiselectForwardFragmentArgs::class.java)!! override val contentViewId: Int = R.layout.multiselect_forward_activity @@ -73,7 +75,7 @@ open class MultiselectForwardActivity : FragmentWrapperActivity(), MultiselectFo } else if (intent == null || !intent.hasExtra(RESULT_SELECTION)) { throw IllegalStateException("Selection contract requires a selection.") } else { - val selection: List = intent.getParcelableArrayListExtra(RESULT_SELECTION)!! + val selection: List = intent.getParcelableArrayListExtraCompat(RESULT_SELECTION, ContactSearchKey.RecipientSearchKey::class.java)!! selection } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt index 8de35671e4..7898abc15d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt @@ -27,6 +27,8 @@ import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.viewModels import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.signal.core.util.getParcelableArrayListCompat +import org.signal.core.util.getParcelableCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.color.ViewColorSet @@ -103,7 +105,7 @@ class MultiselectForwardFragment : } private val args: MultiselectForwardFragmentArgs by lazy { - requireArguments().getParcelable(ARGS)!! + requireArguments().getParcelableCompat(ARGS, MultiselectForwardFragmentArgs::class.java)!! } override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater { @@ -124,7 +126,7 @@ class MultiselectForwardFragment : FeatureFlags.shareSelectionLimit(), !args.selectSingleRecipient, ContactSearchAdapter.DisplaySmsTag.DEFAULT, - ContactSearchAdapter.DisplayPhoneNumber.NEVER, + ContactSearchAdapter.DisplaySecondaryInformation.NEVER, this::getConfiguration, object : ContactSearchMediator.SimpleCallbacks() { override fun onBeforeContactsSelected(view: View?, contactSearchKeys: Set): Set { @@ -251,13 +253,13 @@ class MultiselectForwardFragment : } setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle -> - val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!! + val recipientId: RecipientId = bundle.getParcelableCompat(CreateStoryWithViewersFragment.STORY_RECIPIENT, RecipientId::class.java)!! contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey(recipientId, true))) contactFilterView.clear() } setFragmentResultListener(ChooseGroupStoryBottomSheet.GROUP_STORY) { _, bundle -> - val groups: Set = bundle.getParcelableArrayList(ChooseGroupStoryBottomSheet.RESULT_SET)?.toSet() ?: emptySet() + val groups: Set = bundle.getParcelableArrayListCompat(ChooseGroupStoryBottomSheet.RESULT_SET, RecipientId::class.java)?.toSet() ?: emptySet() val keys: Set = groups.map { ContactSearchKey.RecipientSearchKey(it, true) }.toSet() contactSearchMediator.addToVisibleGroupStories(keys) contactSearchMediator.setKeysSelected(keys) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt index ee8cc36e38..68f2147fc1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardViewModel.kt @@ -100,7 +100,7 @@ class MultiselectForwardViewModel( private val storySendRequirements: Stories.MediaTransform.SendRequirements, private val records: List, private val isSelectionOnly: Boolean, - private val repository: MultiselectForwardRepository, + private val repository: MultiselectForwardRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return requireNotNull(modelClass.cast(MultiselectForwardViewModel(storySendRequirements, records, isSelectionOnly, repository))) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java index 72f2d66024..5aa0bd772a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.SignalDatabase; +import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.model.IdentityRecord; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -152,8 +153,24 @@ private TrustAndVerifyResult trustOrVerifyChangedRecipientsAndResendInternal(@No for (ChangedRecipient changedRecipient : changedRecipients) { SignalProtocolAddress mismatchAddress = changedRecipient.getRecipient().requireServiceId().toProtocolAddress(SignalServiceAddress.DEFAULT_DEVICE_ID); - Log.d(TAG, "Saving identity for: " + changedRecipient.getRecipient().getId() + " " + changedRecipient.getIdentityRecord().getIdentityKey().hashCode()); - SignalIdentityKeyStore.SaveResult result = ApplicationDependencies.getProtocolStore().aci().identities().saveIdentity(mismatchAddress, changedRecipient.getIdentityRecord().getIdentityKey(), true); + IdentityKey newIdentityKey = messageRecord.getIdentityKeyMismatches() + .stream() + .filter(mismatch -> mismatch.getRecipientId(context).equals(changedRecipient.getRecipient().getId())) + .map(IdentityKeyMismatch::getIdentityKey) + .findFirst() + .orElse(null); + + if (newIdentityKey == null) { + Log.w(TAG, "Could not find new identity key in the MessageRecords mismatched identities! Using the recipients current identity key"); + newIdentityKey = changedRecipient.getIdentityRecord().getIdentityKey(); + } + + if (newIdentityKey.hashCode() != changedRecipient.getIdentityRecord().getIdentityKey().hashCode()) { + Log.w(TAG, "Note: The new identity key does not match the identity key we currently have for the recipient. This is not unexpected, but calling it out for debugging reasons. New: " + newIdentityKey.hashCode() + ", Current: " + changedRecipient.getIdentityRecord().getIdentityKey().hashCode()); + } + + Log.d(TAG, "Saving identity for: " + changedRecipient.getRecipient().getId() + " " + newIdentityKey.hashCode()); + SignalIdentityKeyStore.SaveResult result = ApplicationDependencies.getProtocolStore().aci().identities().saveIdentity(mismatchAddress, newIdentityKey, true); Log.d(TAG, "Saving identity result: " + result); if (result == SignalIdentityKeyStore.SaveResult.NO_CHANGE) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 29c53ca9f6..d891875504 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -82,6 +82,7 @@ import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SimpleTask; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.MainFragment; import org.thoughtcrime.securesms.MainNavigator; import org.thoughtcrime.securesms.MuteDialog; @@ -104,6 +105,7 @@ import org.thoughtcrime.securesms.components.reminder.ReminderView; import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder; import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder; +import org.thoughtcrime.securesms.components.reminder.UsernameOutOfSyncReminder; import org.thoughtcrime.securesms.components.settings.app.notifications.manual.NotificationProfileSelectionFragment; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner; import org.thoughtcrime.securesms.components.voice.VoiceNotePlayerView; @@ -145,6 +147,7 @@ import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile; import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity; import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity; import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -156,6 +159,7 @@ import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel; import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.AppStartup; +import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.ConversationUtil; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.LifecycleDisposable; @@ -287,7 +291,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat SelectionLimits.NO_LIMITS, false, ContactSearchAdapter.DisplaySmsTag.DEFAULT, - ContactSearchAdapter.DisplayPhoneNumber.NEVER, + ContactSearchAdapter.DisplaySecondaryInformation.NEVER, this::mapSearchStateToConfiguration, new ContactSearchMediator.SimpleCallbacks(), false, @@ -295,7 +299,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat fixedContacts, displayCheckBox, displaySmsTag, - displayPhoneNumber, + displaySecondaryInformation, callbacks, longClickCallbacks, storyContextMenuCallbacks @@ -306,7 +310,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat fixedContacts, displayCheckBox, displaySmsTag, - displayPhoneNumber, + displaySecondaryInformation, new ContactSearchClickCallbacks(callbacks), longClickCallbacks, storyContextMenuCallbacks, @@ -371,6 +375,8 @@ public boolean canStartNestedScroll() { list.setLayoutManager(new LinearLayoutManager(requireActivity())); list.setItemAnimator(itemAnimator); list.addItemDecoration(archiveDecoration); + CachedInflater.from(list.getContext()).clear(); + CachedInflater.from(list.getContext()).cacheUntilLimit(R.layout.conversation_list_item_view, list, 10); snapToTopDataObserver = new SnapToTopDataObserver(list); @@ -513,26 +519,35 @@ public void onPrepareOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(@NonNull MenuItem item) { super.onOptionsItemSelected(item); - switch (item.getItemId()) { - case R.id.menu_new_group: - handleCreateGroup(); return true; - case R.id.menu_settings: - handleDisplaySettings(); return true; - case R.id.menu_clear_passphrase: - handleClearPassphrase(); return true; - case R.id.menu_mark_all_read: - handleMarkAllRead(); return true; - case R.id.menu_invite: - handleInvite(); return true; - case R.id.menu_notification_profile: - handleNotificationProfile(); return true; - case R.id.menu_filter_unread_chats: - handleFilterUnreadChats(); return true; - case R.id.menu_clear_unread_filter: - onClearFilterClick(); return true; - } + int itemId = item.getItemId(); - return false; + if (itemId == R.id.menu_new_group) { + handleCreateGroup(); + return true; + } else if (itemId == R.id.menu_settings) { + handleDisplaySettings(); + return true; + } else if (itemId == R.id.menu_clear_passphrase) { + handleClearPassphrase(); + return true; + } else if (itemId == R.id.menu_mark_all_read) { + handleMarkAllRead(); + return true; + } else if (itemId == R.id.menu_invite) { + handleInvite(); + return true; + } else if (itemId == R.id.menu_notification_profile) { + handleNotificationProfile(); + return true; + } else if (itemId == R.id.menu_filter_unread_chats) { + handleFilterUnreadChats(); + return true; + } else if (itemId == R.id.menu_clear_unread_filter) { + onClearFilterClick(); + return true; + } else { + return false; + } } @Override @@ -715,6 +730,8 @@ private void onReminderAction(@IdRes int reminderActionId) { CdsTemporaryErrorBottomSheet.show(getChildFragmentManager()); } else if (reminderActionId == R.id.reminder_action_cds_permanent_error_learn_more) { CdsPermanentErrorBottomSheet.show(getChildFragmentManager()); + } else if (reminderActionId == R.id.reminder_action_fix_username) { + startActivity(ManageProfileActivity.getIntentForUsernameEdit(requireContext())); } } @@ -765,7 +782,11 @@ public void onSearchClosed() { } private void updateSearchToolbarHint(@NonNull ConversationFilterRequest conversationFilterRequest) { - requireCallback().getSearchToolbar().get().setSearchInputHint( + Stub searchToolbar = requireCallback().getSearchToolbar(); + if (!searchToolbar.resolved()) { + return; + } + searchToolbar.get().setSearchInputHint( conversationFilterRequest.getFilter() == ConversationFilter.OFF ? R.string.SearchToolbar_search : R.string.SearchToolbar_search_unread_chats ); } @@ -802,13 +823,14 @@ public void onItemRangeInserted(int positionStart, int itemCount) { startupStopwatch.split("data-set"); SignalLocalMetrics.ColdStart.onConversationListDataLoaded(); defaultAdapter.unregisterAdapterDataObserver(this); - list.post(() -> { - AppStartup.getInstance().onCriticalRenderEventEnd(); - startupStopwatch.split("first-render"); - startupStopwatch.stop(TAG); - - if (getContext() != null) { - ConversationFragment.prepare(getContext()); + if (requireActivity() instanceof MainActivity) { + ((MainActivity) requireActivity()).onFirstRender(); + } + list.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + list.removeOnLayoutChangeListener(this); + list.post(ConversationListFragment.this::onFirstRender); } }); } @@ -883,6 +905,17 @@ public void onBackground() {} }); } + private void onFirstRender() { + AppStartup.getInstance().onCriticalRenderEventEnd(); + startupStopwatch.split("first-render"); + startupStopwatch.stop(TAG); + mediaControllerOwner.getVoiceNoteMediaController().finishPostpone(); + requireCallback().getSearchToolbar().get(); + if (getContext() != null) { + ConversationFragment.prepare(getContext()); + } + } + private void onConversationListChanged(@NonNull List conversations) { LinearLayoutManager layoutManager = (LinearLayoutManager) list.getLayoutManager(); int firstVisibleItem = layoutManager != null ? layoutManager.findFirstCompletelyVisibleItemPosition() : -1; @@ -974,6 +1007,8 @@ private void updateReminders() { return Optional.of(new CdsTemporyErrorReminder(context)); } else if (CdsPermanentErrorReminder.isEligible()) { return Optional.of(new CdsPermanentErrorReminder(context)); + } else if (UsernameOutOfSyncReminder.isEligible()) { + return Optional.of(new UsernameOutOfSyncReminder(context)); } else { return Optional.empty(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index 3368d6d9f6..f6bb1fa6b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -22,7 +22,6 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; -import android.text.TextUtils; import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; @@ -73,6 +72,7 @@ import org.thoughtcrime.securesms.glide.GlideLiveDataTarget; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -85,7 +85,6 @@ import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; -import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Set; @@ -637,6 +636,14 @@ private void onRecipientChanged(@NonNull Recipient recipient) { return emphasisAdded(context, "", defaultTint); } else if (MessageTypes.isBadDecryptType(thread.getType())) { return emphasisAdded(context, context.getString(R.string.ThreadRecord_delivery_issue), defaultTint); + } else if (MessageTypes.isThreadMergeType(thread.getType())) { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_history_has_been_merged), defaultTint); + } else if (MessageTypes.isSessionSwitchoverType(thread.getType())) { + if (thread.getRecipient().getE164().isPresent()) { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_s_belongs_to_s, PhoneNumberFormatter.prettyPrint(thread.getRecipient().requireE164()), thread.getRecipient().getDisplayName(context)), defaultTint); + } else { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_safety_number_changed), defaultTint); + } } else { ThreadTable.Extra extra = thread.getExtra(); if (extra != null && extra.isViewOnce()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt index fdef0a6cae..62d72c985d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt @@ -28,13 +28,13 @@ class ConversationListSearchAdapter( fixedContacts: Set, displayCheckBox: Boolean, displaySmsTag: DisplaySmsTag, - displayPhoneNumber: DisplayPhoneNumber, + displaySecondaryInformation: DisplaySecondaryInformation, onClickedCallbacks: ConversationListSearchClickCallbacks, longClickCallbacks: LongClickCallbacks, storyContextMenuCallbacks: StoryContextMenuCallbacks, lifecycleOwner: LifecycleOwner, glideRequests: GlideRequests -) : ContactSearchAdapter(context, fixedContacts, displayCheckBox, displaySmsTag, displayPhoneNumber, onClickedCallbacks, longClickCallbacks, storyContextMenuCallbacks) { +) : ContactSearchAdapter(context, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, onClickedCallbacks, longClickCallbacks, storyContextMenuCallbacks) { init { registerFactory( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/RelinkDevicesReminderBottomSheetFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/RelinkDevicesReminderBottomSheetFragment.kt new file mode 100644 index 0000000000..5a6b5dbaff --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/RelinkDevicesReminderBottomSheetFragment.kt @@ -0,0 +1,90 @@ +package org.thoughtcrime.securesms.conversationlist + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentManager +import org.signal.core.ui.Buttons +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity +import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment +import org.thoughtcrime.securesms.util.BottomSheetUtil + +/** + * Bottom Sheet Dialog to remind a user who has just re-registered to re-link their linked devices. + */ +class RelinkDevicesReminderBottomSheetFragment : ComposeBottomSheetDialogFragment() { + + @Preview + @Composable + override fun SheetContent() { + return Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .padding(16.dp) + .wrapContentSize() + ) { + Handle() + Column(horizontalAlignment = Alignment.Start) { + Text( + text = stringResource(id = R.string.RelinkDevicesReminderFragment__relink_your_devices), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier + .padding(8.dp) + ) + Text( + text = stringResource(R.string.RelinkDevicesReminderFragment__the_devices_you_added_were_unlinked), + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(8.dp) + ) + } + Buttons.LargeTonal( + onClick = ::launchLinkedDevicesSettingsPage, + modifier = Modifier + .padding(8.dp) + .fillMaxWidth() + .align(Alignment.Start) + ) { + Text( + text = stringResource(R.string.RelinkDevicesReminderFragment__open_settings), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + TextButton( + onClick = ::dismiss, + modifier = Modifier + .padding(start = 8.dp, end = 8.dp) + .wrapContentSize() + ) { + Text( + text = stringResource(R.string.RelinkDevicesReminderFragment__later), + color = MaterialTheme.colorScheme.primary + ) + } + } + } + + @SuppressLint("DiscouragedApi") + private fun launchLinkedDevicesSettingsPage() { + startActivity(AppSettingsActivity.linkedDevices(requireContext())) + } + + companion object { + @JvmStatic + fun show(fragmentManager: FragmentManager) { + RelinkDevicesReminderBottomSheetFragment().show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/chatfilter/ConversationListFilterPullView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/chatfilter/ConversationListFilterPullView.kt index 55c93bb04b..286aab95a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/chatfilter/ConversationListFilterPullView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/chatfilter/ConversationListFilterPullView.kt @@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.doOnNextLayout import com.google.android.material.animation.ArgbEvaluatorCompat +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.animation.AnimationCompleteListener import org.thoughtcrime.securesms.databinding.ConversationListFilterPullViewBinding @@ -89,7 +90,7 @@ class ConversationListFilterPullView @JvmOverloads constructor( override fun onRestoreInstanceState(state: Parcelable?) { val bundle = state as Bundle - val root: Parcelable? = bundle.getParcelable(INSTANCE_STATE_ROOT) + val root: Parcelable? = bundle.getParcelableCompat(INSTANCE_STATE_ROOT, Parcelable::class.java) super.onRestoreInstanceState(root) val restoredState: FilterPullState = FilterPullState.valueOf(bundle.getString(INSTANCE_STATE_STATE)!!) diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java index 268d7a5f0f..8e5bbee351 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/ModernDecryptingPartInputStream.java @@ -22,6 +22,8 @@ public class ModernDecryptingPartInputStream { + public static final String PREMATURE_END_ERROR_MESSAGE = "Prematurely reached end of stream!"; + public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file, long offset) throws IOException { @@ -75,7 +77,7 @@ private static void readFully(InputStream in, byte[] buffer) throws IOException for (;;) { int read = in.read(buffer, offset, buffer.length-offset); - if (read == -1) throw new IOException("Prematurely reached end of stream!"); + if (read == -1) throw new IOException(PREMATURE_END_ERROR_MESSAGE); else if (read + offset < buffer.length) offset += read; else return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java index e05fee47f5..9538c7b9eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java @@ -69,7 +69,7 @@ public static List> getAccessFor(@NonNull Conte @WorkerThread public static Map> getAccessMapFor(@NonNull Context context, @NonNull List recipients, boolean isForStory) { - List> accessList = getAccessFor(context, recipients, true, isForStory); + List> accessList = getAccessFor(context, recipients, isForStory, true); Iterator recipientIterator = recipients.iterator(); Iterator> accessIterator = accessList.iterator(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java index 5e95c613f0..66a59d83e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.java @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData; +import org.thoughtcrime.securesms.jobs.GenerateAudioWaveFormJob; import org.thoughtcrime.securesms.mms.MediaStream; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -639,6 +640,10 @@ public void insertAttachmentsForPlaceholder(long mmsId, @NonNull AttachmentId at //noinspection ResultOfMethodCallIgnored transferFile.delete(); } + + if (placeholder != null && MediaUtil.isAudio(placeholder)) { + GenerateAudioWaveFormJob.enqueue(placeholder.getAttachmentId()); + } } private static @Nullable String getVisualHashStringOrNull(@Nullable Attachment attachment) { @@ -795,10 +800,14 @@ public void updateMessageId(@NonNull Collection attachmentIds, lon Log.i(TAG, "Inserted attachment at ID: " + attachmentId); } - for (Attachment attachment : quoteAttachment) { - AttachmentId attachmentId = insertAttachment(mmsId, attachment, true); - insertedAttachments.put(attachment, attachmentId); - Log.i(TAG, "Inserted quoted attachment at ID: " + attachmentId); + try { + for (Attachment attachment : quoteAttachment) { + AttachmentId attachmentId = insertAttachment(mmsId, attachment, true); + insertedAttachments.put(attachment, attachmentId); + Log.i(TAG, "Inserted quoted attachment at ID: " + attachmentId); + } + } catch (MmsException e) { + Log.w(TAG, "Failed to insert quote attachment! messageId: " + mmsId); } return insertedAttachments; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt index ed2b87bd5f..d53e9511d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DistributionListTables.kt @@ -50,7 +50,8 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign fun insertInitialDistributionListAtCreationTime(db: net.zetetic.database.sqlcipher.SQLiteDatabase) { val recipientId = db.insert( - RecipientTable.TABLE_NAME, null, + RecipientTable.TABLE_NAME, + null, contentValuesOf( RecipientTable.GROUP_TYPE to RecipientTable.GroupType.DISTRIBUTION_LIST.id, RecipientTable.DISTRIBUTION_LIST_ID to DistributionListId.MY_STORY_ID, @@ -59,7 +60,8 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign ) ) db.insert( - ListTable.TABLE_NAME, null, + ListTable.TABLE_NAME, + null, contentValuesOf( ListTable.ID to DistributionListId.MY_STORY_ID, ListTable.NAME to DistributionId.MY_STORY.toString(), @@ -71,7 +73,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign } } - private object ListTable { + object ListTable { const val TABLE_NAME = "distribution_list" const val ID = "_id" @@ -549,7 +551,9 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign arrayOf(ListTable.RECIPIENT_ID), "${ListTable.DISTRIBUTION_ID} = ?", SqlUtil.buildArgs(distributionId.toString()), - null, null, null + null, + null, + null )?.use { cursor -> if (cursor.moveToFirst()) { RecipientId.from(CursorUtil.requireLong(cursor, ListTable.RECIPIENT_ID)) @@ -565,7 +569,9 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign arrayOf(ListTable.RECIPIENT_ID), "${ListTable.ID} = ?", SqlUtil.buildArgs(distributionListId), - null, null, null + null, + null, + null )?.use { cursor -> if (cursor.moveToFirst()) { RecipientId.from(CursorUtil.requireLong(cursor, ListTable.RECIPIENT_ID)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupCallRingTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupCallRingTable.kt index 58747a7fe6..ada2c67b7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupCallRingTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupCallRingTable.kt @@ -79,6 +79,10 @@ class GroupCallRingTable(context: Context, databaseHelper: SignalDatabase) : Dat db.delete(TABLE_NAME, "$DATE_RECEIVED < ?", SqlUtil.buildArgs(System.currentTimeMillis() - VALID_RING_DURATION)) } + + fun deleteAll() { + databaseHelper.signalWritableDatabase.delete(TABLE_NAME, null, null) + } } private fun CallManager.RingUpdate.toCode(): Int { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt index bacf4a8282..f1f77430fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt @@ -34,7 +34,6 @@ import org.signal.core.util.withinTransaction import org.signal.libsignal.zkgroup.groups.GroupMasterKey import org.signal.storageservice.protos.groups.Member import org.signal.storageservice.protos.groups.local.DecryptedGroup -import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterator import org.thoughtcrime.securesms.crypto.SenderKeyUtil @@ -214,6 +213,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .query(select, query.whereArgs) .use { cursor -> return if (cursor.moveToFirst()) { + var refreshCursor = false val groupRecord = getGroup(cursor) if (groupRecord.isPresent && RemappedRecords.getInstance().areAnyRemapped(groupRecord.get().members)) { val groupId = groupRecord.get().id @@ -237,9 +237,20 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT } if (updateCount > 0) { - getGroup(groupId) + Log.i(TAG, "Successfully updated $updateCount rows. GroupId: $groupId, Remaps: $remaps", true) + refreshCursor = true } else { - throw IllegalStateException("Failed to update group with remapped recipients!") + Log.w(TAG, "Failed to update any rows. GroupId: $groupId, Remaps: $remaps", true) + } + } + + if (refreshCursor) { + readableDatabase.query(select, query.whereArgs).use { refreshedCursor -> + if (refreshedCursor.moveToFirst()) { + getGroup(refreshedCursor) + } else { + Optional.empty() + } } } else { getGroup(cursor) @@ -530,7 +541,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT ) as $MEMBER_GROUP_CONCAT FROM ${MembershipTable.TABLE_NAME} INNER JOIN $TABLE_NAME ON ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID - INNER JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} + LEFT JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} """.toSingleLine() var query = "${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID} = ?" @@ -635,7 +646,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT * There was a point in time where we weren't properly responding to group creates on linked devices. This would result in us having a Recipient entry for the * group, but we'd either be missing the group entry, or that entry would be missing a master key. This method fixes this scenario. */ - fun fixMissingMasterKey(authServiceId: ServiceId?, groupMasterKey: GroupMasterKey) { + fun fixMissingMasterKey(groupMasterKey: GroupMasterKey) { val groupId = GroupId.v2(groupMasterKey) if (getGroupV1ByExpectedV2(groupId).isPresent) { Log.w(TAG, "There already exists a V1 group that should be migrated into this group. But if the recipient already exists, there's not much we can do here.") @@ -1059,7 +1070,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT .update(MembershipTable.TABLE_NAME) .values(RECIPIENT_ID to toId.serialize()) .where("${MembershipTable.RECIPIENT_ID} = ?", fromId) - .run() + .run(conflictStrategy = SQLiteDatabase.CONFLICT_IGNORE) for (group in getGroupsContainingMember(fromId, false, true)) { if (group.isV2Group) { @@ -1171,7 +1182,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT if (memberLevel.isAbsent()) { memberLevel = DecryptedGroupUtil.findRequestingByUuid(decryptedGroup.requestingMembersList, serviceId.get().uuid()) - .map { m: DecryptedRequestingMember? -> MemberLevel.REQUESTING_MEMBER } + .map { _ -> MemberLevel.REQUESTING_MEMBER } } return if (memberLevel.isPresent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java index 4a83284f99..8930c066c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -95,7 +95,6 @@ private static final class Dependencies { if (instance == null) { SqlCipherLibraryLoader.load(); instance = new JobDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context)); - instance.setWriteAheadLoggingEnabled(true); } } } @@ -103,7 +102,7 @@ private static final class Dependencies { } public JobDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { - super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0, new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook()); + super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0, new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook(), true); this.application = application; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java index d4184b0b5f..238f540cc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java @@ -55,7 +55,6 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase if (instance == null) { SqlCipherLibraryLoader.load(); instance = new KeyValueDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context)); - instance.setWriteAheadLoggingEnabled(true); } } } @@ -68,7 +67,7 @@ public static boolean exists(Context context) { private KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { - super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0,new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook()); + super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0,new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook(), true); this.application = application; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt index 1c5de1e846..fbbb620d49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LocalMetricsDatabase.kt @@ -35,7 +35,8 @@ class LocalMetricsDatabase private constructor( DATABASE_VERSION, 0, SqlCipherDeletingErrorHandler(DATABASE_NAME), - SqlCipherDatabaseHook() + SqlCipherDatabaseHook(), + true ), SignalDatabaseOpenHelper { @@ -83,7 +84,6 @@ class LocalMetricsDatabase private constructor( if (instance == null) { SqlCipherLibraryLoader.load() instance = LocalMetricsDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context)) - instance!!.setWriteAheadLoggingEnabled(true) } } } @@ -129,7 +129,8 @@ class LocalMetricsDatabase private constructor( try { event.splits.forEach { split -> db.insert( - TABLE_NAME, null, + TABLE_NAME, + null, ContentValues().apply { put(CREATED_AT, event.createdAt) put(EVENT_ID, event.eventId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LogDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LogDatabase.kt index 397ff32a7f..a6668d72f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LogDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LogDatabase.kt @@ -40,7 +40,8 @@ class LogDatabase private constructor( DATABASE_VERSION, 0, SqlCipherDeletingErrorHandler(DATABASE_NAME), - SqlCipherDatabaseHook() + SqlCipherDatabaseHook(), + true ), SignalDatabaseOpenHelper { @@ -87,7 +88,6 @@ class LogDatabase private constructor( if (instance == null) { SqlCipherLibraryLoader.load() instance = LogDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context)) - instance!!.setWriteAheadLoggingEnabled(true) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.java deleted file mode 100644 index c6a74220ba..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.java +++ /dev/null @@ -1,301 +0,0 @@ -package org.thoughtcrime.securesms.database; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.MediaUtil; - -import java.util.List; - -@SuppressLint({"RecipientIdDatabaseReferenceUsage", "ThreadIdDatabaseReferenceUsage"}) // Not a real table, just a view -public class MediaTable extends DatabaseTable { - - public static final int ALL_THREADS = -1; - private static final String THREAD_RECIPIENT_ID = "THREAD_RECIPIENT_ID"; - - private static final String BASE_MEDIA_QUERY = "SELECT " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.ROW_ID + " AS " + AttachmentTable.ROW_ID + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.CONTENT_TYPE + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.UNIQUE_ID + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.MMS_ID + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.TRANSFER_STATE + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.SIZE + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.FILE_NAME + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.DATA + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.CDN_NUMBER + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.CONTENT_LOCATION + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.CONTENT_DISPOSITION + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.DIGEST + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.FAST_PREFLIGHT_ID + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.VOICE_NOTE + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.BORDERLESS + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.VIDEO_GIF + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.WIDTH + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.HEIGHT + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.QUOTE + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.STICKER_PACK_ID + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.STICKER_PACK_KEY + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.STICKER_ID + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.STICKER_EMOJI + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.VISUAL_HASH + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.TRANSFORM_PROPERTIES + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.DISPLAY_ORDER + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.CAPTION + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.NAME + ", " - + AttachmentTable.TABLE_NAME + "." + AttachmentTable.UPLOAD_TIMESTAMP + ", " - + MessageTable.TABLE_NAME + "." + MessageTable.TYPE + ", " - + MessageTable.TABLE_NAME + "." + MessageTable.DATE_SENT + ", " - + MessageTable.TABLE_NAME + "." + MessageTable.DATE_RECEIVED + ", " - + MessageTable.TABLE_NAME + "." + MessageTable.DATE_SERVER + ", " - + MessageTable.TABLE_NAME + "." + MessageTable.THREAD_ID + ", " - + MessageTable.TABLE_NAME + "." + MessageTable.RECIPIENT_ID + ", " - + ThreadTable.TABLE_NAME + "." + ThreadTable.RECIPIENT_ID + " as " + THREAD_RECIPIENT_ID + " " - + "FROM " + AttachmentTable.TABLE_NAME + " LEFT JOIN " + MessageTable.TABLE_NAME - + " ON " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.MMS_ID + " = " + MessageTable.TABLE_NAME + "." + MessageTable.ID + " " - + "LEFT JOIN " + ThreadTable.TABLE_NAME - + " ON " + ThreadTable.TABLE_NAME + "." + ThreadTable.ID + " = " + MessageTable.TABLE_NAME + "." + MessageTable.THREAD_ID + " " - + "WHERE " + AttachmentTable.MMS_ID + " IN (SELECT " + MessageTable.ID - + " FROM " + MessageTable.TABLE_NAME - + " WHERE " + MessageTable.THREAD_ID + " __EQUALITY__ ?) AND (%s) AND " - + MessageTable.VIEW_ONCE + " = 0 AND " - + MessageTable.STORY_TYPE + " = 0 AND " - + AttachmentTable.DATA + " IS NOT NULL AND " - + "(" + AttachmentTable.QUOTE + " = 0 OR (" + AttachmentTable.QUOTE + " = 1 AND " + AttachmentTable.DATA_HASH + " IS NULL)) AND " - + AttachmentTable.STICKER_PACK_ID + " IS NULL AND " - + MessageTable.TABLE_NAME + "." + MessageTable.RECIPIENT_ID + " > 0 AND " - + THREAD_RECIPIENT_ID + " > 0"; - - private static final String UNIQUE_MEDIA_QUERY = "SELECT " - + "MAX(" + AttachmentTable.SIZE + ") as " + AttachmentTable.SIZE + ", " - + AttachmentTable.CONTENT_TYPE + " " - + "FROM " + AttachmentTable.TABLE_NAME + " " - + "WHERE " + AttachmentTable.STICKER_PACK_ID + " IS NULL AND " + AttachmentTable.TRANSFER_STATE + " = " + AttachmentTable.TRANSFER_PROGRESS_DONE + " " - + "GROUP BY " + AttachmentTable.DATA; - - private static final String GALLERY_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentTable.CONTENT_TYPE + " NOT LIKE 'image/svg%' AND (" + - AttachmentTable.CONTENT_TYPE + " LIKE 'image/%' OR " + - AttachmentTable.CONTENT_TYPE + " LIKE 'video/%')"); - private static final String AUDIO_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentTable.CONTENT_TYPE + " LIKE 'audio/%'"); - private static final String ALL_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentTable.CONTENT_TYPE + " NOT LIKE 'text/x-signal-plain'"); - private static final String DOCUMENT_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentTable.CONTENT_TYPE + " LIKE 'image/svg%' OR (" + - AttachmentTable.CONTENT_TYPE + " NOT LIKE 'image/%' AND " + - AttachmentTable.CONTENT_TYPE + " NOT LIKE 'video/%' AND " + - AttachmentTable.CONTENT_TYPE + " NOT LIKE 'audio/%' AND " + - AttachmentTable.CONTENT_TYPE + " NOT LIKE 'text/x-signal-plain')"); - - MediaTable(Context context, SignalDatabase databaseHelper) { - super(context, databaseHelper); - } - - public @NonNull Cursor getGalleryMediaForThread(long threadId, @NonNull Sorting sorting) { - SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); - String query = sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY)); - String[] args = {threadId + ""}; - - return database.rawQuery(query, args); - } - - public @NonNull Cursor getDocumentMediaForThread(long threadId, @NonNull Sorting sorting) { - SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); - String query = sorting.applyToQuery(applyEqualityOperator(threadId, DOCUMENT_MEDIA_QUERY)); - String[] args = {threadId + ""}; - - return database.rawQuery(query, args); - } - - public @NonNull Cursor getAudioMediaForThread(long threadId, @NonNull Sorting sorting) { - SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); - String query = sorting.applyToQuery(applyEqualityOperator(threadId, AUDIO_MEDIA_QUERY)); - String[] args = {threadId + ""}; - - return database.rawQuery(query, args); - } - - public @NonNull Cursor getAllMediaForThread(long threadId, @NonNull Sorting sorting) { - SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); - String query = sorting.applyToQuery(applyEqualityOperator(threadId, ALL_MEDIA_QUERY)); - String[] args = {threadId + ""}; - - return database.rawQuery(query, args); - } - - private static String applyEqualityOperator(long threadId, String query) { - return query.replace("__EQUALITY__", threadId == ALL_THREADS ? "!=" : "="); - } - - public StorageBreakdown getStorageBreakdown() { - StorageBreakdown storageBreakdown = new StorageBreakdown(); - SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); - - try (Cursor cursor = database.rawQuery(UNIQUE_MEDIA_QUERY, new String[0])) { - int sizeColumn = cursor.getColumnIndexOrThrow(AttachmentTable.SIZE); - int contentTypeColumn = cursor.getColumnIndexOrThrow(AttachmentTable.CONTENT_TYPE); - - while (cursor.moveToNext()) { - int size = cursor.getInt(sizeColumn); - String type = cursor.getString(contentTypeColumn); - - switch (MediaUtil.getSlideTypeFromContentType(type)) { - case GIF: - case IMAGE: - case MMS: - storageBreakdown.photoSize += size; - break; - case VIDEO: - storageBreakdown.videoSize += size; - break; - case AUDIO: - storageBreakdown.audioSize += size; - break; - case LONG_TEXT: - case DOCUMENT: - storageBreakdown.documentSize += size; - break; - default: - break; - } - } - } - - return storageBreakdown; - } - - public static class MediaRecord { - - private final DatabaseAttachment attachment; - private final RecipientId recipientId; - private final RecipientId threadRecipientId; - private final long threadId; - private final long date; - private final boolean outgoing; - - private MediaRecord(@Nullable DatabaseAttachment attachment, - @NonNull RecipientId recipientId, - @NonNull RecipientId threadRecipientId, - long threadId, - long date, - boolean outgoing) - { - this.attachment = attachment; - this.recipientId = recipientId; - this.threadRecipientId = threadRecipientId; - this.threadId = threadId; - this.date = date; - this.outgoing = outgoing; - } - - public static MediaRecord from(@NonNull Cursor cursor) { - AttachmentTable attachmentDatabase = SignalDatabase.attachments(); - List attachments = attachmentDatabase.getAttachments(cursor); - RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.RECIPIENT_ID))); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.THREAD_ID)); - boolean outgoing = MessageTypes.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.TYPE))); - - long date; - - if (MessageTypes.isPushType(cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.TYPE)))) { - date = cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.DATE_SENT)); - } else { - date = cursor.getLong(cursor.getColumnIndexOrThrow(MessageTable.DATE_RECEIVED)); - } - - RecipientId threadRecipient = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_RECIPIENT_ID))); - - return new MediaRecord(attachments != null && attachments.size() > 0 ? attachments.get(0) : null, - recipientId, - threadRecipient, - threadId, - date, - outgoing); - } - - public @Nullable DatabaseAttachment getAttachment() { - return attachment; - } - - public String getContentType() { - return attachment.getContentType(); - } - - public @NonNull RecipientId getRecipientId() { - return recipientId; - } - - public @NonNull RecipientId getThreadRecipientId() { - return threadRecipientId; - } - - public long getThreadId() { - return threadId; - } - - public long getDate() { - return date; - } - - public boolean isOutgoing() { - return outgoing; - } - } - - public enum Sorting { - Newest (AttachmentTable.TABLE_NAME + "." + AttachmentTable.MMS_ID + " DESC, " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.DISPLAY_ORDER + " DESC, " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.ROW_ID + " DESC"), - Oldest (AttachmentTable.TABLE_NAME + "." + AttachmentTable.MMS_ID + " ASC, " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.DISPLAY_ORDER + " DESC, " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.ROW_ID + " ASC"), - Largest(AttachmentTable.TABLE_NAME + "." + AttachmentTable.SIZE + " DESC, " + AttachmentTable.TABLE_NAME + "." + AttachmentTable.DISPLAY_ORDER + " DESC"); - - private final String postFix; - - Sorting(@NonNull String order) { - postFix = " ORDER BY " + order; - } - - private String applyToQuery(@NonNull String query) { - return query + postFix; - } - - public boolean isRelatedToFileSize() { - return this == Largest; - } - - public static @NonNull Sorting deserialize(int code) { - switch (code) { - case 0: - return Newest; - case 1: - return Oldest; - case 2: - return Largest; - default: - throw new IllegalArgumentException("Unknown code: " + code); - } - } - } - - public final static class StorageBreakdown { - private long photoSize; - private long videoSize; - private long audioSize; - private long documentSize; - - public long getPhotoSize() { - return photoSize; - } - - public long getVideoSize() { - return videoSize; - } - - public long getAudioSize() { - return audioSize; - } - - public long getDocumentSize() { - return documentSize; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt new file mode 100644 index 0000000000..79d5f27b81 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaTable.kt @@ -0,0 +1,274 @@ +package org.thoughtcrime.securesms.database + +import android.annotation.SuppressLint +import android.content.Context +import android.database.Cursor +import org.signal.core.util.requireInt +import org.signal.core.util.requireLong +import org.signal.core.util.requireNonNullString +import org.signal.core.util.toSingleLine +import org.thoughtcrime.securesms.attachments.DatabaseAttachment +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.MediaUtil +import org.thoughtcrime.securesms.util.MediaUtil.SlideType + +@SuppressLint("RecipientIdDatabaseReferenceUsage", "ThreadIdDatabaseReferenceUsage") // Not a real table, just a view +class MediaTable internal constructor(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper) { + + companion object { + const val ALL_THREADS = -1 + private const val THREAD_RECIPIENT_ID = "THREAD_RECIPIENT_ID" + private val BASE_MEDIA_QUERY = """ + SELECT + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ROW_ID} AS ${AttachmentTable.ROW_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CONTENT_TYPE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.UNIQUE_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.TRANSFER_STATE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.SIZE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.FILE_NAME}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DATA}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CDN_NUMBER}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CONTENT_LOCATION}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CONTENT_DISPOSITION}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DIGEST}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.FAST_PREFLIGHT_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.VOICE_NOTE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.BORDERLESS}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.VIDEO_GIF}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.WIDTH}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.HEIGHT}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.QUOTE}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_PACK_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_PACK_KEY}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_ID}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.STICKER_EMOJI}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.VISUAL_HASH}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.TRANSFORM_PROPERTIES}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DISPLAY_ORDER}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.CAPTION}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.NAME}, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.UPLOAD_TIMESTAMP}, + ${MessageTable.TABLE_NAME}.${MessageTable.TYPE}, + ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SENT}, + ${MessageTable.TABLE_NAME}.${MessageTable.DATE_RECEIVED}, + ${MessageTable.TABLE_NAME}.${MessageTable.DATE_SERVER}, + ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID}, + ${MessageTable.TABLE_NAME}.${MessageTable.RECIPIENT_ID}, + ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} as $THREAD_RECIPIENT_ID + FROM + ${AttachmentTable.TABLE_NAME} + LEFT JOIN ${MessageTable.TABLE_NAME} ON ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID} = ${MessageTable.TABLE_NAME}.${MessageTable.ID} + LEFT JOIN ${ThreadTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.ID} = ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} + WHERE + ${AttachmentTable.MMS_ID} IN ( + SELECT ${MessageTable.ID} + FROM ${MessageTable.TABLE_NAME} + WHERE ${MessageTable.THREAD_ID} __EQUALITY__ ? + ) AND + (%s) AND + ${MessageTable.VIEW_ONCE} = 0 AND + ${MessageTable.STORY_TYPE} = 0 AND + ${AttachmentTable.DATA} IS NOT NULL AND + ( + ${AttachmentTable.QUOTE} = 0 OR + ( + ${AttachmentTable.QUOTE} = 1 AND + ${AttachmentTable.DATA_HASH} IS NULL + ) + ) AND + ${AttachmentTable.STICKER_PACK_ID} IS NULL AND + ${MessageTable.TABLE_NAME}.${MessageTable.RECIPIENT_ID} > 0 AND + $THREAD_RECIPIENT_ID > 0 + """.toSingleLine() + + private val UNIQUE_MEDIA_QUERY = """ + SELECT + MAX(${AttachmentTable.SIZE}) as ${AttachmentTable.SIZE}, + ${AttachmentTable.CONTENT_TYPE} + FROM + ${AttachmentTable.TABLE_NAME} + WHERE + ${AttachmentTable.STICKER_PACK_ID} IS NULL AND + ${AttachmentTable.TRANSFER_STATE} = ${AttachmentTable.TRANSFER_PROGRESS_DONE} + GROUP BY ${AttachmentTable.DATA} + """.toSingleLine() + + private val GALLERY_MEDIA_QUERY = String.format( + BASE_MEDIA_QUERY, + """ + ${AttachmentTable.CONTENT_TYPE} NOT LIKE 'image/svg%' AND + (${AttachmentTable.CONTENT_TYPE} LIKE 'image/%' OR ${AttachmentTable.CONTENT_TYPE} LIKE 'video/%') + """.toSingleLine() + ) + + private val AUDIO_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, "${AttachmentTable.CONTENT_TYPE} LIKE 'audio/%'") + private val ALL_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, "${AttachmentTable.CONTENT_TYPE} NOT LIKE 'text/x-signal-plain'") + private val DOCUMENT_MEDIA_QUERY = String.format( + BASE_MEDIA_QUERY, + """ + ${AttachmentTable.CONTENT_TYPE} LIKE 'image/svg%' OR + ( + ${AttachmentTable.CONTENT_TYPE} NOT LIKE 'image/%' AND + ${AttachmentTable.CONTENT_TYPE} NOT LIKE 'video/%' AND + ${AttachmentTable.CONTENT_TYPE} NOT LIKE 'audio/%' AND + ${AttachmentTable.CONTENT_TYPE} NOT LIKE 'text/x-signal-plain' + )""".toSingleLine() + ) + + private fun applyEqualityOperator(threadId: Long, query: String): String { + return query.replace("__EQUALITY__", if (threadId == ALL_THREADS.toLong()) "!=" else "=") + } + } + + fun getGalleryMediaForThread(threadId: Long, sorting: Sorting): Cursor { + val query = sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY)) + val args = arrayOf(threadId.toString() + "") + return readableDatabase.rawQuery(query, args) + } + + fun getDocumentMediaForThread(threadId: Long, sorting: Sorting): Cursor { + val query = sorting.applyToQuery(applyEqualityOperator(threadId, DOCUMENT_MEDIA_QUERY)) + val args = arrayOf(threadId.toString() + "") + return readableDatabase.rawQuery(query, args) + } + + fun getAudioMediaForThread(threadId: Long, sorting: Sorting): Cursor { + val query = sorting.applyToQuery(applyEqualityOperator(threadId, AUDIO_MEDIA_QUERY)) + val args = arrayOf(threadId.toString() + "") + return readableDatabase.rawQuery(query, args) + } + + fun getAllMediaForThread(threadId: Long, sorting: Sorting): Cursor { + val query = sorting.applyToQuery(applyEqualityOperator(threadId, ALL_MEDIA_QUERY)) + val args = arrayOf(threadId.toString() + "") + return readableDatabase.rawQuery(query, args) + } + + fun getStorageBreakdown(): StorageBreakdown { + var photoSize: Long = 0 + var videoSize: Long = 0 + var audioSize: Long = 0 + var documentSize: Long = 0 + + readableDatabase.rawQuery(UNIQUE_MEDIA_QUERY, null).use { cursor -> + while (cursor.moveToNext()) { + val size: Int = cursor.requireInt(AttachmentTable.SIZE) + val type: String = cursor.requireNonNullString(AttachmentTable.CONTENT_TYPE) + + when (MediaUtil.getSlideTypeFromContentType(type)) { + SlideType.GIF, + SlideType.IMAGE, + SlideType.MMS -> { + photoSize += size.toLong() + } + SlideType.VIDEO -> { + videoSize += size.toLong() + } + SlideType.AUDIO -> { + audioSize += size.toLong() + } + SlideType.LONG_TEXT, + SlideType.DOCUMENT -> { + documentSize += size.toLong() + } + else -> {} + } + } + } + + return StorageBreakdown( + photoSize = photoSize, + videoSize = videoSize, + audioSize = audioSize, + documentSize = documentSize + ) + } + + data class MediaRecord constructor( + val attachment: DatabaseAttachment?, + val recipientId: RecipientId, + val threadRecipientId: RecipientId, + val threadId: Long, + val date: Long, + val isOutgoing: Boolean + ) { + + val contentType: String + get() = attachment!!.contentType + + companion object { + @JvmStatic + fun from(cursor: Cursor): MediaRecord { + val attachments = SignalDatabase.attachments.getAttachments(cursor) + + return MediaRecord( + attachment = if (attachments.isNotEmpty()) attachments[0] else null, + recipientId = RecipientId.from(cursor.requireLong(MessageTable.RECIPIENT_ID)), + threadId = cursor.requireLong(MessageTable.THREAD_ID), + threadRecipientId = RecipientId.from(cursor.requireLong(THREAD_RECIPIENT_ID)), + date = if (MessageTypes.isPushType(cursor.requireLong(MessageTable.TYPE))) { + cursor.requireLong(MessageTable.DATE_SENT) + } else { + cursor.requireLong(MessageTable.DATE_RECEIVED) + }, + isOutgoing = MessageTypes.isOutgoingMessageType(cursor.requireLong(MessageTable.TYPE)) + ) + } + } + } + + enum class Sorting(order: String) { + Newest( + """ + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID} DESC, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DISPLAY_ORDER} DESC, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ROW_ID} DESC + """.toSingleLine() + ), + Oldest( + """ + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.MMS_ID} ASC, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DISPLAY_ORDER} DESC, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ROW_ID} ASC + """.toSingleLine() + ), + Largest( + """ + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.SIZE} DESC, + ${AttachmentTable.TABLE_NAME}.${AttachmentTable.DISPLAY_ORDER} DESC + """.toSingleLine() + ); + + private val postFix: String + + init { + postFix = " ORDER BY $order" + } + + fun applyToQuery(query: String): String { + return query + postFix + } + + val isRelatedToFileSize: Boolean + get() = this == Largest + + companion object { + fun deserialize(code: Int): Sorting { + return when (code) { + 0 -> Newest + 1 -> Oldest + 2 -> Largest + else -> throw IllegalArgumentException("Unknown code: $code") + } + } + } + } + + data class StorageBreakdown( + val photoSize: Long, + val videoSize: Long, + val audioSize: Long, + val documentSize: Long + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java index 7dbe29faaa..8965df5442 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java @@ -58,7 +58,6 @@ public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabas if (instance == null) { SqlCipherLibraryLoader.load(); instance = new MegaphoneDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context)); - instance.setWriteAheadLoggingEnabled(true); } } } @@ -66,7 +65,7 @@ public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabas } public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { - super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0, new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook()); + super(application, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, 0, new SqlCipherErrorHandler(DATABASE_NAME), new SqlCipherDatabaseHook(), true); this.application = application; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageSendLogTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageSendLogTables.kt index 65199f1d99..0644e988db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageSendLogTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageSendLogTables.kt @@ -83,7 +83,7 @@ class MessageSendLogTables constructor(context: Context?, databaseHelper: Signal /** Created for [deleteEntriesForRecipient] */ val CREATE_INDEXES = arrayOf( - "CREATE INDEX msl_payload_date_sent_index ON $TABLE_NAME ($DATE_SENT)", + "CREATE INDEX msl_payload_date_sent_index ON $TABLE_NAME ($DATE_SENT)" ) val CREATE_TRIGGERS = arrayOf( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java index 41978e3e38..e9001a1bf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java @@ -639,14 +639,13 @@ public InsertResult updateBundleMessageBody(long messageId, String body) { values.put(THREAD_ID, threadId); values.put(EXPIRES_IN, expiresIn); - long messageId = getWritableDatabase().insert(TABLE_NAME, null, values); - boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted(); + long messageId = getWritableDatabase().insert(TABLE_NAME, null, values); if (unread) { SignalDatabase.threads().incrementUnread(threadId, 1, 0); } - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); notifyConversationListeners(threadId); TrimThreadJob.enqueueAsync(threadId); @@ -660,15 +659,14 @@ public void updateCallLog(long messageId, long type, boolean unread) { values.put(READ, unread ? 0 : 1); getWritableDatabase().update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(messageId)); - long threadId = getThreadIdForMessage(messageId); - Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId); - boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient != null && recipient.isMuted(); + long threadId = getThreadIdForMessage(messageId); + Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId); if (unread) { SignalDatabase.threads().incrementUnread(threadId, 1, 0); } - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); notifyConversationListeners(threadId); ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(new MessageId(messageId)); @@ -719,8 +717,8 @@ public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, SignalDatabase.threads().incrementUnread(threadId, 1, 0); } - boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient.isMuted(); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + + SignalDatabase.threads().update(threadId, true); db.setTransactionSuccessful(); } finally { @@ -796,8 +794,7 @@ public void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipientId, SignalDatabase.threads().incrementUnread(threadId, 1, 0); } - final boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient.isMuted(); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); db.setTransactionSuccessful(); } finally { @@ -913,12 +910,12 @@ public Optional insertMessageInbox(IncomingTextMessage message, lo message.isJustAGroupLeave() || (type & MessageTypes.GROUP_UPDATE_BIT) > 0; - boolean unread = !silent && (Util.isDefaultSmsProvider(context) || - message.isSecureMessage() || - message.isGroup() || - message.isPreKeyBundle()); + boolean unread = !silent && (message.isSecureMessage() || + message.isGroup() || + message.isPreKeyBundle() || + Util.isDefaultSmsProvider(context)); - long threadId; + long threadId; if (groupRecipient == null) threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient); else threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient); @@ -957,8 +954,7 @@ public Optional insertMessageInbox(IncomingTextMessage message, lo } if (!silent) { - final boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && (recipient.isMuted() || (groupRecipient != null && groupRecipient.isMuted())); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); } if (message.getSubscriptionId() != -1) { @@ -1542,12 +1538,12 @@ private static long getReleaseChannelThreadId(boolean hasSeenReleaseChannelStori return releaseChannelThreadId; } + @VisibleForTesting public void deleteGroupStoryReplies(long parentStoryId) { SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); String[] args = SqlUtil.buildArgs(parentStoryId); db.delete(TABLE_NAME, PARENT_STORY_ID + " = ?", args); - OptimizeMessageSearchIndexJob.enqueue(); } public int deleteStoriesOlderThan(long timestamp, boolean hasSeenReleaseChannelStories) { @@ -1949,6 +1945,8 @@ public void markAsRemoteDelete(long messageId) { db.endTransaction(); } + OptimizeMessageSearchIndexJob.enqueue(); + ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(new MessageId(messageId)); ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners(); @@ -2511,8 +2509,6 @@ private Optional insertMessageInbox(IncomingMediaMessage retrieved if (threadRecipientId == null) { threadRecipientId = retrieved.getFrom(); } - boolean keepThreadArchived = threadRecipientId != null && SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(threadRecipientId).isMuted(); - long messageId = insertMediaMessage(threadId, retrieved.getBody(), retrieved.getAttachments(), @@ -2524,13 +2520,14 @@ private Optional insertMessageInbox(IncomingMediaMessage retrieved contentValues, null, updateThread, - !keepThreadArchived); + true); boolean isNotStoryGroupReply = retrieved.getParentStoryId() == null || !retrieved.getParentStoryId().isGroupReply(); + if (!MessageTypes.isPaymentsActivated(mailbox) && !MessageTypes.isPaymentsRequestToActivate(mailbox) && !MessageTypes.isExpirationTimerUpdate(mailbox) && !retrieved.getStoryType().isStory() && isNotStoryGroupReply) { boolean incrementUnreadMentions = !retrieved.getMentions().isEmpty() && retrieved.getMentions().stream().anyMatch(m -> m.getRecipientId().equals(Recipient.self().getId())); SignalDatabase.threads().incrementUnread(threadId, 1, incrementUnreadMentions ? 1 : 0); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); } notifyConversationListeners(threadId); @@ -2679,8 +2676,7 @@ public Pair insertMessageInbox(@NonNull NotificationInd notification long messageId = db.insert(TABLE_NAME, null, values); SignalDatabase.threads().incrementUnread(threadId, 1, 0); - boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted(); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); notifyConversationListeners(threadId); @@ -2703,8 +2699,7 @@ public void insertBadDecryptMessage(@NonNull RecipientId recipientId, int sender databaseHelper.getSignalWritableDatabase().insert(TABLE_NAME, null, values); SignalDatabase.threads().incrementUnread(threadId, 1, 0); - boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && Recipient.resolved(recipientId).isMuted(); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); notifyConversationListeners(threadId); @@ -3813,9 +3808,10 @@ public void setMismatchedIdentities(long messageId, @NonNull Set data = new ArrayList<>(); try (Cursor cursor = db.query(TABLE_NAME, new String[] { RECIPIENT_ID, SERVER_GUID, DATE_RECEIVED }, query, args, null, null, DATE_RECEIVED + " DESC", "3")) { while (cursor.moveToNext()) { - RecipientId id = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)); - String serverGuid = CursorUtil.requireString(cursor, SERVER_GUID); + RecipientId id = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)); + String serverGuid = CursorUtil.requireString(cursor, SERVER_GUID); long dateReceived = CursorUtil.requireLong(cursor, DATE_RECEIVED); + if (!Util.isEmpty(serverGuid)) { data.add(new ReportSpamData(id, serverGuid, dateReceived)); } @@ -4179,9 +4175,7 @@ public boolean checkMessageExists(@NonNull MessageRecord messageRecord) { } public @NonNull List getReportSpamMessageServerData(long threadId, long timestamp, int limit) { - return SignalDatabase - .messages() - .getReportSpamMessageServerGuids(threadId, timestamp) + return getReportSpamMessageServerGuids(threadId, timestamp) .stream() .sorted((l, r) -> -Long.compare(l.getDateReceived(), r.getDateReceived())) .limit(limit) @@ -4665,16 +4659,20 @@ public List getScheduledMessagesBefore(long time) { } } - public @Nullable Long getOldestScheduledSendTimestamp() { + public @Nullable MessageRecord getOldestScheduledSendTimestamp() { String[] columns = new String[] { SCHEDULED_DATE }; String selection = STORY_TYPE + " = ? AND " + PARENT_STORY_ID + " <= ? AND " + SCHEDULED_DATE + " != ?"; String[] args = SqlUtil.buildArgs(0, 0, -1); String order = SCHEDULED_DATE + " ASC, " + ID + " ASC"; String limit = "1"; - try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, columns, selection, args, null, null, order, limit)) { - return cursor != null && cursor.moveToNext() ? cursor.getLong(0) : null; + try (MmsReader reader = mmsReaderFor(getReadableDatabase().query(TABLE_NAME, MMS_PROJECTION, selection, args, null, null, order, limit))) { + if (reader.getNext() != null) { + return reader.getCurrent(); + } } + + return null; } public Cursor getMessagesForNotificationState(Collection stickyThreads) { @@ -5311,6 +5309,10 @@ public MessageRecord getCurrent() { } } + public MessageId getCurrentId() { + return new MessageId(CursorUtil.requireLong(cursor, ID)); + } + @Override public @NonNull MessageExportState getMessageExportStateForCurrentRecord() { byte[] messageExportState = CursorUtil.requireBlob(cursor, MessageTable.EXPORT_STATE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt b/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt index 1e00ccad1f..9c66635b99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/NotificationProfileTables.kt @@ -1,9 +1,11 @@ +@file:Suppress("ktlint:filename") + package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import android.database.Cursor -import net.zetetic.database.sqlcipher.SQLiteConstraintException +import android.database.sqlite.SQLiteConstraintException import org.signal.core.util.SqlUtil import org.signal.core.util.requireBoolean import org.signal.core.util.requireInt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt index dfe689c307..04d65abfaf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PnpOperations.kt @@ -33,6 +33,7 @@ data class PnpDataSet( this.removeIf { it.id == toUpdate.id } this += update(toUpdate) } + /** * Applies the set of operations and returns the resulting dataset. * Important: This only occurs _in memory_. You must still apply the operations to disk to persist them. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index d2a1b33f34..cc28e86129 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -23,6 +23,8 @@ import org.signal.core.util.optionalLong import org.signal.core.util.optionalString import org.signal.core.util.or import org.signal.core.util.readToSet +import org.signal.core.util.readToSingleBoolean +import org.signal.core.util.readToSingleLong import org.signal.core.util.requireBlob import org.signal.core.util.requireBoolean import org.signal.core.util.requireInt @@ -148,6 +150,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da const val SYSTEM_JOINED_NAME = "system_display_name" const val SYSTEM_FAMILY_NAME = "system_family_name" const val SYSTEM_GIVEN_NAME = "system_given_name" + const val SYSTEM_NICKNAME = "system_nickname" private const val SYSTEM_PHOTO_URI = "system_photo_uri" const val SYSTEM_PHONE_TYPE = "system_phone_type" const val SYSTEM_PHONE_LABEL = "system_phone_label" @@ -247,7 +250,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da $NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0, $UNREGISTERED_TIMESTAMP INTEGER DEFAULT 0, $HIDDEN INTEGER DEFAULT 0, - $REPORTING_TOKEN BLOB DEFAULT NULL + $REPORTING_TOKEN BLOB DEFAULT NULL, + $SYSTEM_NICKNAME TEXT DEFAULT NULL ) """.trimIndent() @@ -522,73 +526,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return serviceIdToProfileKey } - private fun fetchRecipient(serviceId: ServiceId?, e164: String?, changeSelf: Boolean): RecipientFetch { - val byE164 = e164?.let { getByE164(it) } ?: Optional.empty() - val byAci = serviceId?.let { getByServiceId(it) } ?: Optional.empty() - - var logs = LogBundle( - bySid = byAci.map { id -> RecipientLogDetails(id = id) }.orElse(null), - byE164 = byE164.map { id -> RecipientLogDetails(id = id) }.orElse(null), - label = "L0" - ) - - if (byAci.isPresent && byE164.isPresent && byAci.get() == byE164.get()) { - return RecipientFetch.Match(byAci.get(), logs.label("L0")) - } - - if (byAci.isPresent && byE164.isAbsent()) { - val aciRecord: RecipientRecord = getRecord(byAci.get()) - logs = logs.copy(bySid = aciRecord.toLogDetails()) - - if (e164 != null && (changeSelf || serviceId != SignalStore.account().aci)) { - val changedNumber: RecipientId? = if (aciRecord.e164 != null && aciRecord.e164 != e164) aciRecord.id else null - return RecipientFetch.MatchAndUpdateE164(byAci.get(), e164, changedNumber, logs.label("L1")) - } else if (e164 == null) { - return RecipientFetch.Match(byAci.get(), logs.label("L2")) - } else { - return RecipientFetch.Match(byAci.get(), logs.label("L3")) - } - } - - if (byAci.isAbsent() && byE164.isPresent) { - val e164Record: RecipientRecord = getRecord(byE164.get()) - logs = logs.copy(byE164 = e164Record.toLogDetails()) - - if (serviceId != null && e164Record.serviceId == null) { - return RecipientFetch.MatchAndUpdateAci(byE164.get(), serviceId, logs.label("L4")) - } else if (serviceId != null && e164Record.serviceId != SignalStore.account().aci) { - return RecipientFetch.InsertAndReassignE164(serviceId, e164, byE164.get(), logs.label("L5")) - } else if (serviceId != null) { - return RecipientFetch.Insert(serviceId, null, logs.label("L6")) - } else { - return RecipientFetch.Match(byE164.get(), logs.label("L7")) - } - } - - if (byAci.isAbsent() && byE164.isAbsent()) { - return RecipientFetch.Insert(serviceId, e164, logs.label("L8")) - } - - require(byAci.isPresent && byE164.isPresent && byAci.get() != byE164.get()) { "Assumed conditions at this point." } - - val aciRecord: RecipientRecord = getRecord(byAci.get()) - val e164Record: RecipientRecord = getRecord(byE164.get()) - - logs = logs.copy(bySid = aciRecord.toLogDetails(), byE164 = e164Record.toLogDetails()) - - if (e164Record.serviceId == null) { - val changedNumber: RecipientId? = if (aciRecord.e164 != null) aciRecord.id else null - return RecipientFetch.MatchAndMerge(sidId = byAci.get(), e164Id = byE164.get(), changedNumber = changedNumber, logs.label("L9")) - } else { - if (e164Record.serviceId != SignalStore.account().aci) { - val changedNumber: RecipientId? = if (aciRecord.e164 != null) aciRecord.id else null - return RecipientFetch.MatchAndReassignE164(id = byAci.get(), e164Id = byE164.get(), e164 = e164!!, changedNumber = changedNumber, logs.label("L10")) - } else { - return RecipientFetch.Match(byAci.get(), logs.label("L11")) - } - } - } - fun getOrInsertFromServiceId(serviceId: ServiceId): RecipientId { return getAndPossiblyMerge(serviceId = serviceId, e164 = null) } @@ -783,7 +720,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return if (result.isNotEmpty()) { result[0] - } else null + } else { + null + } } fun markNeedsSyncWithoutRefresh(recipientIds: Collection) { @@ -1031,6 +970,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da Log.w(TAG, "Avoided attempt to apply null profile key in account record update!") } + put(USERNAME, update.new.username) put(STORAGE_SERVICE_ID, Base64.encodeBytes(update.new.id.raw)) if (update.new.hasUnknownFields()) { @@ -1133,6 +1073,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da """.trimIndent() val out: MutableList = ArrayList() val columns: Array = TYPED_RECIPIENT_PROJECTION + arrayOf( + SYSTEM_NICKNAME, "$TABLE_NAME.$STORAGE_PROTO", "$TABLE_NAME.$UNREGISTERED_TIMESTAMP", "${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}", @@ -1162,36 +1103,47 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * @return All storage IDs for synced records, excluding the ones that need to be deleted. */ fun getContactStorageSyncIdsMap(): Map { - val inPart = "(?, ?)" - val args = SqlUtil.buildArgs(GroupType.NONE.id, Recipient.self().id, GroupType.SIGNAL_V1.id, GroupType.DISTRIBUTION_LIST.id) - - val query = """ - $STORAGE_SERVICE_ID NOT NULL AND ( - ($GROUP_TYPE = ? AND $SERVICE_ID NOT NULL AND $ID != ?) - OR - $GROUP_TYPE IN $inPart - ) - """.trimIndent() val out: MutableMap = HashMap() - readableDatabase.query(TABLE_NAME, arrayOf(ID, STORAGE_SERVICE_ID, GROUP_TYPE), query, args, null, null, null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - val id = RecipientId.from(cursor.requireLong(ID)) - val encodedKey = cursor.requireNonNullString(STORAGE_SERVICE_ID) - val groupType = GroupType.fromId(cursor.requireInt(GROUP_TYPE)) - val key = Base64.decodeOrThrow(encodedKey) - - when (groupType) { - GroupType.NONE -> out[id] = StorageId.forContact(key) - GroupType.SIGNAL_V1 -> out[id] = StorageId.forGroupV1(key) - GroupType.DISTRIBUTION_LIST -> out[id] = StorageId.forStoryDistributionList(key) - else -> throw AssertionError() + readableDatabase + .select(ID, STORAGE_SERVICE_ID, GROUP_TYPE) + .from(TABLE_NAME) + .where( + """ + $STORAGE_SERVICE_ID NOT NULL AND ( + ($GROUP_TYPE = ? AND $SERVICE_ID NOT NULL AND $ID != ?) + OR + $GROUP_TYPE = ? + OR + $DISTRIBUTION_LIST_ID NOT NULL AND $DISTRIBUTION_LIST_ID IN ( + SELECT ${DistributionListTables.ListTable.ID} + FROM ${DistributionListTables.ListTable.TABLE_NAME} + ) + ) + """.toSingleLine(), + GroupType.NONE.id, + Recipient.self().id, + GroupType.SIGNAL_V1.id + ) + .run() + .use { cursor -> + while (cursor.moveToNext()) { + val id = RecipientId.from(cursor.requireLong(ID)) + val encodedKey = cursor.requireNonNullString(STORAGE_SERVICE_ID) + val groupType = GroupType.fromId(cursor.requireInt(GROUP_TYPE)) + val key = Base64.decodeOrThrow(encodedKey) + + when (groupType) { + GroupType.NONE -> out[id] = StorageId.forContact(key) + GroupType.SIGNAL_V1 -> out[id] = StorageId.forGroupV1(key) + GroupType.DISTRIBUTION_LIST -> out[id] = StorageId.forStoryDistributionList(key) + else -> throw AssertionError() + } } } - } for (id in groups.getAllGroupV2Ids()) { - val recipient = Recipient.externalGroupExact(id!!) + val recipient = Recipient.externalGroupExact(id) val recipientId = recipient.id val existing: RecipientRecord = getRecordForSync(recipientId) ?: throw AssertionError() val key = existing.storageId ?: throw AssertionError() @@ -2079,6 +2031,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } } + fun getUsername(id: RecipientId): String? { + return writableDatabase.query(TABLE_NAME, arrayOf(USERNAME), "$ID = ?", SqlUtil.buildArgs(id), null, null, null).use { + if (it.moveToFirst()) { + it.requireString(USERNAME) + } else { + null + } + } + } + fun setUsername(id: RecipientId, username: String?) { writableDatabase.withinTransaction { if (username != null) { @@ -2239,17 +2201,81 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } fun markUnregistered(id: RecipientId) { + if (FeatureFlags.phoneNumberPrivacy()) { + val record = getRecord(id) + + if (record.pni != null && record.serviceId != record.pni) { + markUnregisteredAndSplit(id, record) + } else { + markUnregisteredWithoutSplit(id) + } + } else { + markUnregisteredWithoutSplit(id) + } + } + + /** + * Marks the user unregistered and also splits it into an ACI-only and PNI-only contact. + * This is to allow a new user to register the number with a new ACI. + */ + private fun markUnregisteredAndSplit(id: RecipientId, record: RecipientRecord) { + check(record.pni != null && record.pni != record.serviceId) + + val contentValues = contentValuesOf( + REGISTERED to RegisteredState.NOT_REGISTERED.id, + UNREGISTERED_TIMESTAMP to System.currentTimeMillis(), + PHONE to null, + PNI_COLUMN to null + ) + + if (update(id, contentValues)) { + Log.i(TAG, "[WithSplit] Newly marked $id as unregistered.") + markNeedsSync(id) + ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) + } + + val splitId = getAndPossiblyMerge(record.pni, record.pni, record.e164) + Log.i(TAG, "Split off new recipient as $splitId (ACI-only recipient is $id)") + } + + /** + * Marks the user unregistered without splitting the contact into an ACI-only and PNI-only contact. + */ + private fun markUnregisteredWithoutSplit(id: RecipientId) { val contentValues = contentValuesOf( REGISTERED to RegisteredState.NOT_REGISTERED.id, UNREGISTERED_TIMESTAMP to System.currentTimeMillis() ) if (update(id, contentValues)) { - Log.i(TAG, "Newly marked $id as unregistered.") + Log.i(TAG, "[WithoutSplit] Newly marked $id as unregistered.") + markNeedsSync(id) ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) } } + /** + * Removes the target recipient's E164+PNI, then creates a new recipient with that E164+PNI. + * Done so we can match a split contact during storage sync. + */ + fun splitForStorageSync(storageId: ByteArray) { + check(FeatureFlags.phoneNumberPrivacy()) + + val record = getByStorageId(storageId)!! + check(record.serviceId != null && record.pni != null && record.serviceId != record.pni) + + writableDatabase + .update(TABLE_NAME) + .values( + PNI_COLUMN to null, + PHONE to null + ) + .where("$ID = ?", record.id) + .run() + + getAndPossiblyMerge(record.pni, record.pni, record.e164) + } + fun bulkUpdatedRegisteredStatus(registered: Map, unregistered: Collection) { writableDatabase.withinTransaction { val registeredWithServiceId: Set = getRegisteredWithServiceIds() @@ -2278,13 +2304,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } for (id in unregistered) { - val values = contentValuesOf( - REGISTERED to RegisteredState.NOT_REGISTERED.id, - UNREGISTERED_TIMESTAMP to System.currentTimeMillis() - ) - if (update(id, values)) { - ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id) - } + markUnregistered(id) } } } @@ -2438,6 +2458,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da @VisibleForTesting fun writePnpChangeSetToDisk(changeSet: PnpChangeSet, inputPni: PNI?): RecipientId { + var hadThreadMerge = false for (operation in changeSet.operations) { @Exhaustive when (operation) { @@ -2497,16 +2518,21 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da .run() } is PnpOperation.Merge -> { - merge(operation.primaryId, operation.secondaryId, inputPni) + val mergeResult: MergeResult = merge(operation.primaryId, operation.secondaryId, inputPni) + hadThreadMerge = hadThreadMerge || mergeResult.neededThreadMerge } is PnpOperation.SessionSwitchoverInsert -> { - val threadId: Long? = threads.getThreadIdFor(operation.recipientId) - if (threadId != null) { - val event = SessionSwitchoverEvent - .newBuilder() - .setE164(operation.e164 ?: "") - .build() - SignalDatabase.messages.insertSessionSwitchoverEvent(operation.recipientId, threadId, event) + if (hadThreadMerge) { + Log.d(TAG, "Skipping SSE insert because we already had a thread merge event.") + } else { + val threadId: Long? = threads.getThreadIdFor(operation.recipientId) + if (threadId != null) { + val event = SessionSwitchoverEvent + .newBuilder() + .setE164(operation.e164 ?: "") + .build() + SignalDatabase.messages.insertSessionSwitchoverEvent(operation.recipientId, threadId, event) + } } } is PnpOperation.ChangeNumberInsert -> { @@ -2608,7 +2634,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val fullData = partialData.copy( e164Record = partialData.byE164?.let { getRecord(it) }, pniSidRecord = partialData.byPniSid?.let { getRecord(it) }, - aciSidRecord = partialData.byAciSid?.let { getRecord(it) }, + aciSidRecord = partialData.byAciSid?.let { getRecord(it) } ) check(fullData.commonId == null) @@ -2654,6 +2680,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } + if (!pniVerified && fullData.pniSidRecord != null && finalData.aciSidRecord != null && sessions.hasAnySessionFor(fullData.pniSidRecord.serviceId.toString())) { + breadCrumbs += "FinalUpdateSSE" + operations += PnpOperation.SessionSwitchoverInsert( + recipientId = primaryId, + e164 = finalData.e164 + ) + } + return PnpChangeSet( id = PnpIdResolver.PnpNoopId(primaryId), operations = operations, @@ -3010,6 +3044,16 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return results } + /** True if the recipient exists and is muted, otherwise false. */ + fun isMuted(id: RecipientId): Boolean { + return readableDatabase + .select(MUTE_UNTIL) + .from(TABLE_NAME) + .where("$ID = ?", id) + .run() + .readToSingleBoolean() + } + fun getRegisteredE164s(): Set { return readableDatabase .select(PHONE) @@ -3560,6 +3604,15 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } } + fun getExpiresInSeconds(id: RecipientId): Long { + return readableDatabase + .select(MESSAGE_EXPIRATION_TIME) + .from(TABLE_NAME) + .where(ID_WHERE, id) + .run() + .readToSingleLong(0L) + } + /** * Will update the database with the content values you specified. It will make an intelligent * query such that this will only return true if a row was *actually* updated. @@ -3620,7 +3673,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * Merges one ACI recipient with an E164 recipient. It is assumed that the E164 recipient does * *not* have an ACI. */ - private fun merge(primaryId: RecipientId, secondaryId: RecipientId, newPni: PNI? = null): RecipientId { + private fun merge(primaryId: RecipientId, secondaryId: RecipientId, newPni: PNI? = null): MergeResult { ensureInTransaction() val db = writableDatabase val primaryRecord = getRecord(primaryId) @@ -3632,7 +3685,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } // Threads - val threadMerge = threads.merge(primaryId, secondaryId) + val threadMerge: ThreadTable.MergeResult = threads.merge(primaryId, secondaryId) threads.setLastScrolled(threadMerge.threadId, 0) threads.update(threadMerge.threadId, false, false) @@ -3697,7 +3750,11 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } db.update(TABLE_NAME, uuidValues, ID_WHERE, SqlUtil.buildArgs(primaryId)) - return primaryId + + return MergeResult( + finalId = primaryId, + neededThreadMerge = threadMerge.neededMerge + ) } private fun ensureInTransaction() { @@ -3744,6 +3801,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da put(SYSTEM_GIVEN_NAME, systemName.givenName) put(SYSTEM_FAMILY_NAME, systemName.familyName) put(SYSTEM_JOINED_NAME, systemName.toString()) + put(SYSTEM_NICKNAME, contact.systemNickname.orElse(null)) put(PROFILE_KEY, contact.profileKey.map { source -> Base64.encodeBytes(source) }.orElse(null)) put(USERNAME, if (TextUtils.isEmpty(username)) null else username) put(PROFILE_SHARING, if (contact.isProfileSharingEnabled) "1" else "0") @@ -3821,6 +3879,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * get them back through CDS). */ fun debugClearServiceIds(recipientId: RecipientId? = null) { + check(FeatureFlags.internalUser()) + writableDatabase .update(TABLE_NAME) .values( @@ -3844,6 +3904,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * Should only be used for debugging! A very destructive action that clears all known profile keys and credentials. */ fun debugClearProfileData(recipientId: RecipientId? = null) { + check(FeatureFlags.internalUser()) + writableDatabase .update(TABLE_NAME) .values( @@ -3872,6 +3934,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da * Should only be used for debugging! Clears the E164 and PNI from a recipient. */ fun debugClearE164AndPni(recipientId: RecipientId) { + check(FeatureFlags.internalUser()) + writableDatabase .update(TABLE_NAME) .values( @@ -3881,7 +3945,28 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da .where(ID_WHERE, recipientId) .run() - Recipient.live(recipientId).refresh() + ApplicationDependencies.getRecipientCache().clear() + RecipientId.clearCache() + } + + /** + * Should only be used for debugging! Clears the ACI from a contact. + * Only works if the recipient has a PNI. + */ + fun debugRemoveAci(recipientId: RecipientId) { + check(FeatureFlags.internalUser()) + + writableDatabase.execSQL( + """ + UPDATE $TABLE_NAME + SET $SERVICE_ID = $PNI_COLUMN + WHERE $ID = ? AND $PNI_COLUMN NOT NULL + """.toSingleLine(), + SqlUtil.buildArgs(recipientId) + ) + + ApplicationDependencies.getRecipientCache().clear() + RecipientId.clearCache() } fun getRecord(context: Context, cursor: Cursor): RecipientRecord { @@ -4010,7 +4095,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da storiesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.STORIES, Capabilities.BIT_LENGTH).toInt()), giftBadgesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.GIFT_BADGES, Capabilities.BIT_LENGTH).toInt()), pnpCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PNP, Capabilities.BIT_LENGTH).toInt()), - paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt()), + paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt()) ) } @@ -4047,6 +4132,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val identityKey = cursor.optionalString(IDENTITY_KEY).map { Base64.decodeOrThrow(it) }.orElse(null) val identityStatus = cursor.optionalInt(IDENTITY_STATUS).map { VerifiedStatus.forState(it) }.orElse(VerifiedStatus.DEFAULT) val unregisteredTimestamp = cursor.optionalLong(UNREGISTERED_TIMESTAMP).orElse(0) + val systemNickname = cursor.optionalString(SYSTEM_NICKNAME).orElse(null) return RecipientRecord.SyncExtras( storageProto = storageProto, @@ -4055,7 +4141,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da identityStatus = identityStatus, isArchived = archived, isForcedUnread = forcedUnread, - unregisteredTimestamp = unregisteredTimestamp + unregisteredTimestamp = unregisteredTimestamp, + systemNickname = systemNickname ) } @@ -4099,13 +4186,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return !this.isPresent } - private fun RecipientRecord.toLogDetails(): RecipientLogDetails { - return RecipientLogDetails( - id = this.id, - serviceId = this.serviceId, - e164 = this.e164 - ) - } + private data class MergeResult( + val finalId: RecipientId, + val neededThreadMerge: Boolean + ) inner class BulkOperationsHandle internal constructor(private val database: SQLiteDatabase) { private val pendingRecipients: MutableSet = mutableSetOf() @@ -4160,6 +4244,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da rotateStorageId(id) } } + + pendingRecipients.forEach { id -> rotateStorageId(id) } } private fun clearSystemDataForPendingInfo() { @@ -4443,84 +4529,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da } } - private sealed class RecipientFetch(val logBundle: LogBundle?) { - /** - * We have a matching recipient, and no writes need to occur. - */ - data class Match(val id: RecipientId, val bundle: LogBundle?) : RecipientFetch(bundle) - - /** - * We found a matching recipient and can update them with a new E164. - */ - data class MatchAndUpdateE164(val id: RecipientId, val e164: String, val changedNumber: RecipientId?, val bundle: LogBundle) : RecipientFetch(bundle) - - /** - * We found a matching recipient and can give them an E164 that used to belong to someone else. - */ - data class MatchAndReassignE164(val id: RecipientId, val e164Id: RecipientId, val e164: String, val changedNumber: RecipientId?, val bundle: LogBundle) : RecipientFetch(bundle) - - /** - * We found a matching recipient and can update them with a new ACI. - */ - data class MatchAndUpdateAci(val id: RecipientId, val serviceId: ServiceId, val bundle: LogBundle) : RecipientFetch(bundle) - - /** - * We found a matching recipient and can insert an ACI as a *new user*. - */ - data class MatchAndInsertAci(val id: RecipientId, val serviceId: ServiceId, val bundle: LogBundle) : RecipientFetch(bundle) - - /** - * The ACI maps to ACI-only recipient, and the E164 maps to a different E164-only recipient. We need to merge the two together. - */ - data class MatchAndMerge(val sidId: RecipientId, val e164Id: RecipientId, val changedNumber: RecipientId?, val bundle: LogBundle) : RecipientFetch(bundle) - - /** - * We don't have a matching recipient, so we need to insert one. - */ - data class Insert(val serviceId: ServiceId?, val e164: String?, val bundle: LogBundle) : RecipientFetch(bundle) - - /** - * We need to create a new recipient and give it the E164 of an existing recipient. - */ - data class InsertAndReassignE164(val serviceId: ServiceId?, val e164: String?, val e164Id: RecipientId, val bundle: LogBundle) : RecipientFetch(bundle) - } - - /** - * Simple class for [fetchRecipient] to pass back info that can be logged. - */ - private data class LogBundle( - val label: String, - val serviceId: ServiceId? = null, - val e164: String? = null, - val bySid: RecipientLogDetails? = null, - val byE164: RecipientLogDetails? = null - ) { - fun label(label: String): LogBundle { - return this.copy(label = label) - } - } - - /** - * Minimal info about a recipient that we'd want to log. Used in [fetchRecipient]. - */ - private data class RecipientLogDetails( - val id: RecipientId, - val serviceId: ServiceId? = null, - val e164: String? = null - ) - data class CdsV2Result( val pni: PNI, val aci: ACI? - ) { - fun bestServiceId(): ServiceId { - return if (aci != null) { - aci - } else { - pni - } - } - } + ) data class ProcessPnpTupleResult( val finalId: RecipientId, @@ -4529,6 +4541,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val oldIds: Set, val changedNumberId: RecipientId?, val operations: List, - val breadCrumbs: List, + val breadCrumbs: List ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt index feafc88271..44a9e35492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchTable.kt @@ -31,7 +31,7 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa @Language("sql") val CREATE_TABLE = arrayOf( - "CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts5($BODY, $THREAD_ID UNINDEXED, content=${MessageTable.TABLE_NAME}, content_rowid=${MessageTable.ID})", + "CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts5($BODY, $THREAD_ID UNINDEXED, content=${MessageTable.TABLE_NAME}, content_rowid=${MessageTable.ID})" ) @Language("sql") diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt index 27841d6425..2fd3c1e266 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeySharedTable.kt @@ -50,7 +50,7 @@ class SenderKeySharedTable internal constructor(context: Context?, databaseHelpe ADDRESS to address.name, DEVICE to address.deviceId, DISTRIBUTION_ID to distributionId.toString(), - TIMESTAMP to System.currentTimeMillis(), + TIMESTAMP to System.currentTimeMillis() ) db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt index 0666e2a166..aadf6026cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SenderKeyTable.kt @@ -60,7 +60,7 @@ class SenderKeyTable internal constructor(context: Context?, databaseHelper: Sig DEVICE to address.deviceId, DISTRIBUTION_ID to distributionId.toString(), RECORD to record.serialize(), - CREATED_AT to System.currentTimeMillis(), + CREATED_AT to System.currentTimeMillis() ) db.insertWithOnConflict(TABLE_NAME, null, insertValues, SQLiteDatabase.CONFLICT_REPLACE) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt index 9c3b51035f..f73c0e2603 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt @@ -25,7 +25,8 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data SignalDatabaseMigrations.DATABASE_VERSION, 0, SqlCipherErrorHandler(DATABASE_NAME), - SqlCipherDatabaseHook() + SqlCipherDatabaseHook(), + true ), SignalDatabaseOpenHelper { @@ -209,7 +210,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data synchronized(SignalDatabase::class.java) { if (instance == null) { instance = SignalDatabase(application, databaseSecret, attachmentSecret) - instance!!.setWriteAheadLoggingEnabled(true) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt index aab3e0f109..10fbd62b79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.kt @@ -59,7 +59,10 @@ class SqlCipherErrorHandler(private val databaseName: String) : DatabaseErrorHan try { SQLiteDatabase.openOrCreateDatabase( - databaseFile.absolutePath, DatabaseSecretProvider.getOrCreateDatabaseSecret(context).asString(), null, null, + databaseFile.absolutePath, + DatabaseSecretProvider.getOrCreateDatabaseSecret(context).asString(), + null, + null, object : SQLiteDatabaseHook { override fun preKey(connection: SQLiteConnection) {} override fun postKey(connection: SQLiteConnection) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/StorySendTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/StorySendTable.kt index 5138513344..cd71b212e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/StorySendTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/StorySendTable.kt @@ -46,7 +46,7 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas val CREATE_INDEXS = arrayOf( "CREATE INDEX story_sends_recipient_id_sent_timestamp_allows_replies_index ON $TABLE_NAME ($RECIPIENT_ID, $SENT_TIMESTAMP, $ALLOWS_REPLIES)", - "CREATE INDEX story_sends_message_id_distribution_id_index ON $TABLE_NAME ($MESSAGE_ID, $DISTRIBUTION_ID)", + "CREATE INDEX story_sends_message_id_distribution_id_index ON $TABLE_NAME ($MESSAGE_ID, $DISTRIBUTION_ID)" ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt index 814d5ba922..9fb17ebdf1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -8,6 +8,7 @@ import android.database.MergeCursor import android.net.Uri import androidx.core.content.contentValuesOf import com.fasterxml.jackson.annotation.JsonProperty +import org.json.JSONObject import org.jsoup.helper.StringUtil import org.signal.core.util.CursorUtil import org.signal.core.util.SqlUtil @@ -31,6 +32,7 @@ import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo import org.thoughtcrime.securesms.database.SignalDatabase.Companion.attachments import org.thoughtcrime.securesms.database.SignalDatabase.Companion.drafts +import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groupCallRings import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groupReceipts import org.thoughtcrime.securesms.database.SignalDatabase.Companion.mentions import org.thoughtcrime.securesms.database.SignalDatabase.Companion.messageLog @@ -58,6 +60,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.ConversationUtil import org.thoughtcrime.securesms.util.JsonUtils +import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.isScheduled import org.whispersystems.signalservice.api.push.ServiceId @@ -243,7 +246,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa .where("$ID = ?", threadId) .run() - if (unarchive) { + if (unarchive && allowedToUnarchive(threadId)) { val archiveValues = contentValuesOf(ARCHIVED to 0) val query = SqlUtil.buildTrueUpdateQuery(ID_WHERE, SqlUtil.buildArgs(threadId), archiveValues) if (writableDatabase.update(TABLE_NAME, archiveValues, query.where, query.whereArgs) > 0) { @@ -252,6 +255,16 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } } + private fun allowedToUnarchive(threadId: Long): Boolean { + if (!SignalStore.settings().shouldKeepMutedChatsArchived()) { + return true + } + + val threadRecipientId: RecipientId? = getRecipientIdForThreadId(threadId) + + return threadRecipientId == null || !recipients.isMuted(threadRecipientId) + } + fun updateSnippetUriSilently(threadId: Long, attachment: Uri?) { writableDatabase .update(TABLE_NAME) @@ -272,7 +285,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa SNIPPET_URI to attachment?.toString() ) - if (unarchive) { + if (unarchive && allowedToUnarchive(threadId)) { contentValues.put(ARCHIVED, 0) } @@ -1067,6 +1080,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa messageLog.deleteAll() messages.deleteAllThreads() drafts.clearAllDrafts() + groupCallRings.deleteAll() db.delete(TABLE_NAME, null, null) } @@ -1731,9 +1745,21 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val extraString = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_EXTRAS)) val extra: Extra? = if (extraString != null) { try { - JsonUtils.fromJson(extraString, Extra::class.java) - } catch (e: IOException) { - Log.w(TAG, "Failed to decode extras!") + val jsonObject = SaneJSONObject(JSONObject(extraString)) + Extra( + isViewOnce = jsonObject.getBoolean("isRevealable"), + isSticker = jsonObject.getBoolean("isSticker"), + stickerEmoji = jsonObject.getString("stickerEmoji"), + isAlbum = jsonObject.getBoolean("isAlbum"), + isRemoteDelete = jsonObject.getBoolean("isRemoteDelete"), + isMessageRequestAccepted = jsonObject.getBoolean("isMessageRequestAccepted"), + isGv2Invite = jsonObject.getBoolean("isGv2Invite"), + groupAddedBy = jsonObject.getString("groupAddedBy"), + individualRecipientId = jsonObject.getString("individualRecipientId")!!, + bodyRanges = jsonObject.getString("bodyRanges"), + isScheduled = jsonObject.getBoolean("isScheduled") + ) + } catch (exception: Exception) { null } } else { @@ -1766,11 +1792,13 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa private fun getSnippetUri(cursor: Cursor?): Uri? { return if (cursor!!.isNull(cursor.getColumnIndexOrThrow(SNIPPET_URI))) { null - } else try { - Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_URI))) - } catch (e: IllegalArgumentException) { - Log.w(TAG, e) - null + } else { + try { + Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_URI))) + } catch (e: IllegalArgumentException) { + Log.w(TAG, e) + null + } } } @@ -1780,17 +1808,39 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } data class Extra( - @field:JsonProperty @param:JsonProperty("isRevealable") val isViewOnce: Boolean = false, - @field:JsonProperty @param:JsonProperty("isSticker") val isSticker: Boolean = false, - @field:JsonProperty @param:JsonProperty("stickerEmoji") val stickerEmoji: String? = null, - @field:JsonProperty @param:JsonProperty("isAlbum") val isAlbum: Boolean = false, - @field:JsonProperty @param:JsonProperty("isRemoteDelete") val isRemoteDelete: Boolean = false, - @field:JsonProperty @param:JsonProperty("isMessageRequestAccepted") val isMessageRequestAccepted: Boolean = true, - @field:JsonProperty @param:JsonProperty("isGv2Invite") val isGv2Invite: Boolean = false, - @field:JsonProperty @param:JsonProperty("groupAddedBy") val groupAddedBy: String? = null, - @field:JsonProperty @param:JsonProperty("individualRecipientId") private val individualRecipientId: String, - @field:JsonProperty @param:JsonProperty("bodyRanges") val bodyRanges: String? = null, - @field:JsonProperty @param:JsonProperty("isScheduled") val isScheduled: Boolean = false + @field:JsonProperty + @param:JsonProperty("isRevealable") + val isViewOnce: Boolean = false, + @field:JsonProperty + @param:JsonProperty("isSticker") + val isSticker: Boolean = false, + @field:JsonProperty + @param:JsonProperty("stickerEmoji") + val stickerEmoji: String? = null, + @field:JsonProperty + @param:JsonProperty("isAlbum") + val isAlbum: Boolean = false, + @field:JsonProperty + @param:JsonProperty("isRemoteDelete") + val isRemoteDelete: Boolean = false, + @field:JsonProperty + @param:JsonProperty("isMessageRequestAccepted") + val isMessageRequestAccepted: Boolean = true, + @field:JsonProperty + @param:JsonProperty("isGv2Invite") + val isGv2Invite: Boolean = false, + @field:JsonProperty + @param:JsonProperty("groupAddedBy") + val groupAddedBy: String? = null, + @field:JsonProperty + @param:JsonProperty("individualRecipientId") + private val individualRecipientId: String, + @field:JsonProperty + @param:JsonProperty("bodyRanges") + val bodyRanges: String? = null, + @field:JsonProperty + @param:JsonProperty("isScheduled") + val isScheduled: Boolean = false ) { fun getIndividualRecipientId(): String { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 30b7ac62b1..d3bf6e88df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V176_AddScheduledDa import org.thoughtcrime.securesms.database.helpers.migration.V177_MessageSendLogTableCleanupMigration import org.thoughtcrime.securesms.database.helpers.migration.V178_ReportingTokenColumnMigration import org.thoughtcrime.securesms.database.helpers.migration.V179_CleanupDanglingMessageSendLogMigration +import org.thoughtcrime.securesms.database.helpers.migration.V180_RecipientNicknameMigration /** * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. @@ -43,7 +44,7 @@ object SignalDatabaseMigrations { val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass) - const val DATABASE_VERSION = 179 + const val DATABASE_VERSION = 180 @JvmStatic fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -170,6 +171,10 @@ object SignalDatabaseMigrations { if (oldVersion < 179) { V179_CleanupDanglingMessageSendLogMigration.migrate(context, db, oldVersion, newVersion) } + + if (oldVersion < 180) { + V180_RecipientNicknameMigration.migrate(context, db, oldVersion, newVersion) + } } @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt index 48622c2f56..267d973058 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt @@ -1837,7 +1837,8 @@ object V149_LegacyMigrations : SignalDatabaseMigration { ) val recipientId = db.insert( - "recipient", null, + "recipient", + null, contentValuesOf( "distribution_list_id" to 1L, "storage_service_key" to Base64.encodeBytes(StorageSyncHelper.generateKey()), @@ -1847,7 +1848,8 @@ object V149_LegacyMigrations : SignalDatabaseMigration { val listUUID = UUID.randomUUID().toString() db.insert( - "distribution_list", null, + "distribution_list", + null, contentValuesOf( "_id" to 1L, "name" to listUUID, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V180_RecipientNicknameMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V180_RecipientNicknameMigration.kt new file mode 100644 index 0000000000..1c8a3fc9b6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V180_RecipientNicknameMigration.kt @@ -0,0 +1,13 @@ +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import net.zetetic.database.sqlcipher.SQLiteDatabase + +/** + * Adds support for storing the systemNickname from storage service. + */ +object V180_RecipientNicknameMigration : SignalDatabaseMigration { + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("ALTER TABLE recipient ADD COLUMN system_nickname TEXT DEFAULT NULL") + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index e9090dfbb0..b257ba8352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context; import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails; +import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent; import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.JumboEmoji; @@ -235,6 +236,18 @@ public SpannableString getDisplayBody(@NonNull Context context, @Nullable Consum } catch (InvalidProtocolBufferException e) { throw new AssertionError(e); } + } else if (isSessionSwitchoverEventType()) { + try { + SessionSwitchoverEvent event = SessionSwitchoverEvent.parseFrom(Base64.decodeOrThrow(getBody())); + + if (event.getE164().isEmpty()) { + return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, r.getDisplayName(context)), R.drawable.ic_update_safety_number_16); + } else { + return fromRecipient(getIndividualRecipient(), r -> context.getString(R.string.MessageRecord_s_belongs_to_s, PhoneNumberFormatter.prettyPrint(r.requireE164()), r.getDisplayName(context)), R.drawable.ic_update_info_16); + } + } catch (InvalidProtocolBufferException e) { + throw new AssertionError(e); + } } else if (isSmsExportType()) { int messageResource = R.string.MessageRecord__you_can_no_longer_send_sms_messages_in_signal; return fromRecipient(getIndividualRecipient(), r -> context.getString(messageResource, r.getDisplayName(context)), R.drawable.ic_update_info_16); @@ -550,6 +563,10 @@ public boolean isThreadMergeEventType() { return MessageTypes.isThreadMergeType(type); } + public boolean isSessionSwitchoverEventType() { + return MessageTypes.isSessionSwitchoverType(type); + } + public boolean isSmsExportType() { return MessageTypes.isSmsExport(type); } @@ -574,7 +591,7 @@ public boolean isUpdate() { return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isProfileChange() || isGroupV1MigrationEvent() || isChatSessionRefresh() || isBadDecryptType() || - isChangeNumber() || isBoostRequest() || isThreadMergeEventType() || isSmsExportType() || + isChangeNumber() || isBoostRequest() || isThreadMergeEventType() || isSmsExportType() || isSessionSwitchoverEventType() || isPaymentsRequestToActivate() || isPaymentsActivated(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt index 898e16e26f..0f2b58b1f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/RecipientRecord.kt @@ -113,7 +113,8 @@ data class RecipientRecord( val identityStatus: VerifiedStatus, val isArchived: Boolean, val isForcedUnread: Boolean, - val unregisteredTimestamp: Long + val unregisteredTimestamp: Long, + val systemNickname: String? ) data class Capabilities( diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 630b79ccbe..923bb40ba2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; import org.thoughtcrime.securesms.messages.IncomingMessageObserver; -import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; import org.thoughtcrime.securesms.net.Network; import org.thoughtcrime.securesms.net.NetworkManager; import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor; @@ -88,6 +87,7 @@ public class ApplicationDependencies { private static final Object LOCK = new Object(); private static final Object FRAME_RATE_TRACKER_LOCK = new Object(); private static final Object JOB_MANAGER_LOCK = new Object(); + private static final Object SIGNAL_HTTP_CLIENT_LOCK = new Object(); private static Application application; // MOLLY: Rename provider to dependencyProvider @@ -99,7 +99,6 @@ public class ApplicationDependencies { private static volatile SignalServiceMessageReceiver messageReceiver; private static volatile NetworkManager networkManager; private static volatile IncomingMessageObserver incomingMessageObserver; - private static volatile IncomingMessageProcessor incomingMessageProcessor; private static volatile BackgroundMessageRetriever backgroundMessageRetriever; private static volatile LiveRecipientCache recipientCache; private static volatile JobManager jobManager; @@ -299,18 +298,6 @@ public static void restartAllNetworkConnections() { return networkManager; } - public static @NonNull IncomingMessageProcessor getIncomingMessageProcessor() { - if (incomingMessageProcessor == null) { - synchronized (LOCK) { - if (incomingMessageProcessor == null) { - incomingMessageProcessor = getProvider().provideIncomingMessageProcessor(); - } - } - } - - return incomingMessageProcessor; - } - public static @NonNull BackgroundMessageRetriever getBackgroundMessageRetriever() { if (backgroundMessageRetriever == null) { synchronized (LOCK) { @@ -560,7 +547,7 @@ public static TypingStatusSender getTypingStatusSender() { public static @NonNull OkHttpClient getSignalOkHttpClient() { if (signalOkHttpClient == null) { - synchronized (LOCK) { + synchronized (SIGNAL_HTTP_CLIENT_LOCK) { if (signalOkHttpClient == null) { try { OkHttpClient baseClient = ApplicationDependencies.getOkHttpClient(); @@ -709,7 +696,6 @@ public interface Provider { @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver(@NonNull SignalServiceConfiguration signalServiceConfiguration); @NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess(); @NonNull NetworkManager provideNetworkManager(); - @NonNull IncomingMessageProcessor provideIncomingMessageProcessor(); @NonNull BackgroundMessageRetriever provideBackgroundMessageRetriever(); @NonNull LiveRecipientCache provideRecipientCache(); @NonNull JobManager provideJobManager(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index c32f7278f1..6b946b2211 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -47,7 +47,6 @@ import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; import org.thoughtcrime.securesms.messages.IncomingMessageObserver; -import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; import org.thoughtcrime.securesms.net.NetworkManager; import org.thoughtcrime.securesms.net.SignalWebSocketHealthMonitor; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -162,11 +161,6 @@ public ApplicationDependencyProvider(@NonNull Application context) { return NetworkManager.create(context); } - @Override - public @NonNull IncomingMessageProcessor provideIncomingMessageProcessor() { - return new IncomingMessageProcessor(context); - } - @Override public @NonNull BackgroundMessageRetriever provideBackgroundMessageRetriever() { return new BackgroundMessageRetriever(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferLockedDialog.java b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferLockedDialog.java index 6c7e11e52b..ad9dee7e94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferLockedDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/olddevice/OldDeviceTransferLockedDialog.java @@ -41,7 +41,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Signal_MaterialAlertDialog); + MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(requireContext()); dialogBuilder.setView(R.layout.old_device_transfer_locked_dialog_fragment) .setPositiveButton(R.string.OldDeviceTransferLockedDialog__done, (d, w) -> OldDeviceExitActivity.exit(requireActivity())) .setNegativeButton(R.string.OldDeviceTransferLockedDialog__cancel_and_activate_this_device, (d, w) -> onUnlockRequest()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiFiles.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiFiles.kt index 508afc1bca..9009f83598 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiFiles.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiFiles.kt @@ -5,6 +5,7 @@ import android.net.Uri import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import okio.HashingSink import okio.blackholeSink @@ -141,7 +142,6 @@ object EmojiFiles { private fun getDirectory(context: Context): File = File(context.getEmojiDirectory(), this.uuid.toString()).apply { mkdir() } companion object { - private val objectMapper = ObjectMapper() @JvmStatic @@ -149,7 +149,12 @@ object EmojiFiles { fun readVersion(context: Context, skipValidation: Boolean = false): Version? { val version = try { getInputStream(context, context.getVersionFile()).use { - objectMapper.readValue(it, Version::class.java) + val tree: JsonNode = objectMapper.readTree(it) + Version( + version = tree["version"].asInt(), + uuid = objectMapper.convertValue(tree["uuid"], UUID::class.java), + density = tree["density"].asText() + ) } } catch (e: Exception) { Log.w(TAG, "Could not read current emoji version from disk.", e) @@ -221,8 +226,15 @@ object EmojiFiles { @JvmStatic fun read(context: Context, version: Version): NameCollection { try { - getInputStream(context, context.getNameFile(version.uuid)).use { - return objectMapper.readValue(it, NameCollection::class.java) + getInputStream(context, context.getNameFile(version.uuid)).use { inputStream -> + val tree: JsonNode = objectMapper.readTree(inputStream) + val elements = tree["names"].elements().asSequence().map { + Name( + name = it["name"].asText(), + uuid = objectMapper.convertValue(it["uuid"], UUID::class.java) + ) + }.toList() + return NameCollection(objectMapper.convertValue(tree["versionUuid"], UUID::class.java), elements) } } catch (e: Exception) { return NameCollection(version.uuid, listOf()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt index 9e25d30912..23f40451c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/emoji/EmojiRemote.kt @@ -56,7 +56,7 @@ class EmojiImageRequest( class EmojiFileRequest( version: Int, density: String, - name: String, + name: String ) : EmojiRequest { override val uri: String = "$BASE_STATIC_BUCKET_URI/$version/$density/$name" } diff --git a/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt index d30679041c..899fa4ee68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt @@ -92,6 +92,7 @@ class WebRtcViewModel(state: WebRtcServiceState) { val identityChangedParticipants: Set = state.callInfoState.identityChangedRecipients val remoteDevicesCount: OptionalLong = state.callInfoState.remoteDevicesCount val participantLimit: Long? = state.callInfoState.participantLimit + @get:JvmName("shouldRingGroup") val ringGroup: Boolean = state.getCallSetupState(state.callInfoState.activePeer?.callId).ringGroup val ringerRecipient: Recipient = state.getCallSetupState(state.callInfoState.activePeer?.callId).ringerRecipient diff --git a/app/src/main/java/org/thoughtcrime/securesms/fonts/FontManifest.kt b/app/src/main/java/org/thoughtcrime/securesms/fonts/FontManifest.kt index c4c7cf2103..eef19019ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/fonts/FontManifest.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/fonts/FontManifest.kt @@ -35,7 +35,7 @@ data class FontManifest @JsonCreator constructor( @JsonProperty("chinese-traditional") val chineseTraditional: FontScript?, @JsonProperty("chinese-simplified") val chineseSimplified: FontScript?, @JsonProperty("arabic") val arabic: FontScript?, - @JsonProperty("japanese") val japanese: FontScript?, + @JsonProperty("japanese") val japanese: FontScript? ) /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/fonts/Fonts.kt b/app/src/main/java/org/thoughtcrime/securesms/fonts/Fonts.kt index eff04145e9..c9d14a7261 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/fonts/Fonts.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/fonts/Fonts.kt @@ -91,7 +91,9 @@ object Fonts { } val fontDownloadKey = FontDownloadKey( - version, supportedScript, font + version, + supportedScript, + font ) val taskInProgress = taskCache[fontDownloadKey] diff --git a/app/src/main/java/org/thoughtcrime/securesms/fonts/TypefaceHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/fonts/TypefaceHelper.kt index aeabaf5eb3..6979ab0b2f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/fonts/TypefaceHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/fonts/TypefaceHelper.kt @@ -33,6 +33,6 @@ object TypefaceHelper { SEMI_BOLD(600), BOLD(700), EXTRA_BOLD(800), - BLACK(900), + BLACK(900) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt index 7a8fc9d4ce..7139d70033 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt @@ -8,7 +8,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob -import org.thoughtcrime.securesms.messages.RestStrategy +import org.thoughtcrime.securesms.messages.WebSocketStrategy import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor /** @@ -97,7 +97,7 @@ object FcmFetchManager { @JvmStatic fun retrieveMessages(context: Context) { - val success = ApplicationDependencies.getBackgroundMessageRetriever().retrieveMessages(context, RestStrategy(), RestStrategy()) + val success = ApplicationDependencies.getBackgroundMessageRetriever().retrieveMessages(context, WebSocketStrategy()) if (success) { Log.i(TAG, "Successfully retrieved messages.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmJobService.java b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmJobService.java index 2434be184d..fead61abdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmJobService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmJobService.java @@ -13,7 +13,7 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; -import org.thoughtcrime.securesms.messages.RestStrategy; +import org.thoughtcrime.securesms.messages.WebSocketStrategy; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -49,7 +49,7 @@ public boolean onStartJob(JobParameters params) { SignalExecutors.UNBOUNDED.execute(() -> { Context context = getApplicationContext(); BackgroundMessageRetriever retriever = ApplicationDependencies.getBackgroundMessageRetriever(); - boolean success = retriever.retrieveMessages(context, new RestStrategy(), new RestStrategy()); + boolean success = retriever.retrieveMessages(context, new WebSocketStrategy()); if (success) { Log.i(TAG, "Successfully retrieved messages."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java index e405baec42..d1af361d17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmReceiveService.java @@ -86,13 +86,18 @@ private static void handleReceivedNotification(Context context, @Nullable Remote boolean enqueueSuccessful = false; try { - long timeSinceLastRefresh = System.currentTimeMillis() - SignalStore.misc().getLastFcmForegroundServiceTime(); + boolean highPriority = remoteMessage != null && remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH; + long timeSinceLastRefresh = System.currentTimeMillis() - SignalStore.misc().getLastFcmForegroundServiceTime(); + Log.d(TAG, String.format(Locale.US, "[handleReceivedNotification] API: %s, FeatureFlag: %s, RemoteMessagePriority: %s, TimeSinceLastRefresh: %s ms", Build.VERSION.SDK_INT, FeatureFlags.useFcmForegroundService(), remoteMessage != null ? remoteMessage.getPriority() : "n/a", timeSinceLastRefresh)); - if (FeatureFlags.useFcmForegroundService() && Build.VERSION.SDK_INT >= 31 && remoteMessage != null && remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH && timeSinceLastRefresh > FCM_FOREGROUND_INTERVAL) { + if (highPriority && FeatureFlags.useFcmForegroundService()) { + enqueueSuccessful = FcmFetchManager.enqueue(context, true); + SignalStore.misc().setLastFcmForegroundServiceTime(System.currentTimeMillis()); + } else if (highPriority && Build.VERSION.SDK_INT >= 31 && timeSinceLastRefresh > FCM_FOREGROUND_INTERVAL) { enqueueSuccessful = FcmFetchManager.enqueue(context, true); SignalStore.misc().setLastFcmForegroundServiceTime(System.currentTimeMillis()); - } else if (Build.VERSION.SDK_INT < 26 || remoteMessage == null || remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH) { + } else if (highPriority || Build.VERSION.SDK_INT < 26 || remoteMessage == null) { enqueueSuccessful = FcmFetchManager.enqueue(context, false); } } catch (Exception e) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java index d1cf6736bd..09b1fe03f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ProjectionRecycler.java @@ -114,7 +114,10 @@ private void startPlayback(@NonNull RecyclerView parent, @NonNull GiphyMp4Projec }); holder.playContent(giphyMp4Playable.getMediaItem(), giphyMp4Playable.getPlaybackPolicyEnforcer()); } else { + giphyMp4Playable.showProjectionArea(); + holder.setOnPlaybackReady(() -> { + holder.show(); giphyMp4Playable.hideProjectionArea(); parent.invalidate(); }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java index 0c18dcc164..c082cebf4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.giph.ui; +import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.widget.Toast; @@ -55,6 +56,7 @@ public void onPreCreate() { dynamicTheme.onCreate(this); } + @SuppressLint("MissingInflatedId") @Override public void onCreate(Bundle bundle, boolean ready) { setContentView(R.layout.giphy_activity); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 748eea0f0a..cbc2191227 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -937,6 +937,14 @@ public GroupManager.GroupActionResult joinGroup(@NonNull DecryptedGroupJoinInfo if (group.isPresent()) { Log.i(TAG, "Group already present locally"); + if (decryptedChange != null) { + try { + groupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey) + .updateLocalGroupToRevision(decryptedChange.getRevision(), System.currentTimeMillis(), decryptedChange); + } catch (GroupNotAMemberException e) { + Log.w(TAG, "Unable to apply join change to existing group", e); + } + } } else { groupDatabase.create(groupMasterKey, decryptedGroup); Log.i(TAG, "Created local group with placeholder"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupChangeFailureReason.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupChangeFailureReason.java index 732b830306..d0b32e3215 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupChangeFailureReason.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupChangeFailureReason.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.groups.ui; +import android.annotation.SuppressLint; + import androidx.annotation.NonNull; import org.thoughtcrime.securesms.groups.GroupChangeBusyException; @@ -18,6 +20,7 @@ public enum GroupChangeFailureReason { NETWORK, OTHER; + @SuppressLint("SuspiciousIndentation") public static @NonNull GroupChangeFailureReason fromException(@NonNull Throwable e) { if (e instanceof MembershipNotSuitableForV2Exception) return GroupChangeFailureReason.NOT_GV2_CAPABLE; if (e instanceof IOException) return GroupChangeFailureReason.NETWORK; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index 267ce64ca8..20ea7ffe1b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -19,6 +19,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval; +import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.model.GroupRecord; import org.thoughtcrime.securesms.database.MessageTable; @@ -333,7 +334,14 @@ private boolean notInGroupAndNotBeingAdded(@NonNull Optional localR .filter(Objects::nonNull) .anyMatch(serviceIds::matches); - return !currentlyInGroup && !addedAsMember && !addedAsPendingMember; + boolean addedAsRequestingMember = signedGroupChange.getNewRequestingMembersList() + .stream() + .map(DecryptedRequestingMember::getUuid) + .map(UuidUtil::fromByteStringOrNull) + .filter(Objects::nonNull) + .anyMatch(serviceIds::matches); + + return !currentlyInGroup && !addedAsMember && !addedAsPendingMember && !addedAsRequestingMember; } private boolean notHavingInviteRevoked(@NonNull DecryptedGroupChange signedGroupChange) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java index dede570bf7..934dd00ace 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -80,66 +80,80 @@ synchronized void wakeUp() { } @WorkerThread - synchronized void submitNewJobChain(@NonNull List> chain) { - chain = Stream.of(chain).filterNot(List::isEmpty).toList(); + void submitNewJobChain(@NonNull List> chain) { + synchronized (this) { + chain = Stream.of(chain).filterNot(List::isEmpty).toList(); - if (chain.isEmpty()) { - Log.w(TAG, "Tried to submit an empty job chain. Skipping."); - return; - } + if (chain.isEmpty()) { + Log.w(TAG, "Tried to submit an empty job chain. Skipping."); + return; + } - if (chainExceedsMaximumInstances(chain)) { - Job solo = chain.get(0).get(0); - jobTracker.onStateChange(solo, JobTracker.JobState.IGNORED); - Log.w(TAG, JobLogger.format(solo, "Already at the max instance count. Factory limit: " + solo.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + solo.getParameters().getMaxInstancesForQueue() + ". Skipping.")); - return; + if (chainExceedsMaximumInstances(chain)) { + Job solo = chain.get(0).get(0); + jobTracker.onStateChange(solo, JobTracker.JobState.IGNORED); + Log.w(TAG, JobLogger.format(solo, "Already at the max instance count. Factory limit: " + solo.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + solo.getParameters().getMaxInstancesForQueue() + ". Skipping.")); + return; + } + + insertJobChain(chain); + scheduleJobs(chain.get(0)); } - insertJobChain(chain); + // We have no control over what happens in jobs' onSubmit method, so we drop our lock to reduce the possibility of a deadlock triggerOnSubmit(chain); - notifyAll(); - scheduleJobs(chain.get(0)); + + synchronized (this) { + notifyAll(); + } } @WorkerThread - synchronized void submitJobWithExistingDependencies(@NonNull Job job, @NonNull Collection dependsOn, @Nullable String dependsOnQueue) { + void submitJobWithExistingDependencies(@NonNull Job job, @NonNull Collection dependsOn, @Nullable String dependsOnQueue) { List> chain = Collections.singletonList(Collections.singletonList(job)); - if (chainExceedsMaximumInstances(chain)) { - jobTracker.onStateChange(job, JobTracker.JobState.IGNORED); - Log.w(TAG, JobLogger.format(job, "Already at the max instance count. Factory limit: " + job.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + job.getParameters().getMaxInstancesForQueue() + ". Skipping.")); - return; - } + synchronized (this) { + if (chainExceedsMaximumInstances(chain)) { + jobTracker.onStateChange(job, JobTracker.JobState.IGNORED); + Log.w(TAG, JobLogger.format(job, "Already at the max instance count. Factory limit: " + job.getParameters().getMaxInstancesForFactory() + ", Queue limit: " + job.getParameters().getMaxInstancesForQueue() + ". Skipping.")); + return; + } - Set allDependsOn = new HashSet<>(dependsOn); - Set aliveDependsOn = Stream.of(dependsOn) - .filter(id -> jobStorage.getJobSpec(id) != null) - .collect(Collectors.toSet()); + Set allDependsOn = new HashSet<>(dependsOn); + Set aliveDependsOn = Stream.of(dependsOn) + .filter(id -> jobStorage.getJobSpec(id) != null) + .collect(Collectors.toSet()); - if (dependsOnQueue != null) { - List inQueue = Stream.of(jobStorage.getJobsInQueue(dependsOnQueue)) - .map(JobSpec::getId) - .toList(); + if (dependsOnQueue != null) { + List inQueue = Stream.of(jobStorage.getJobsInQueue(dependsOnQueue)) + .map(JobSpec::getId) + .toList(); - allDependsOn.addAll(inQueue); - aliveDependsOn.addAll(inQueue); - } + allDependsOn.addAll(inQueue); + aliveDependsOn.addAll(inQueue); + } - if (jobTracker.haveAnyFailed(allDependsOn)) { - Log.w(TAG, "This job depends on a job that failed! Failing this job immediately."); - List dependents = onFailure(job); - job.setContext(application); - job.onFailure(); - Stream.of(dependents).forEach(Job::onFailure); - return; - } + if (jobTracker.haveAnyFailed(allDependsOn)) { + Log.w(TAG, "This job depends on a job that failed! Failing this job immediately."); + List dependents = onFailure(job); + job.setContext(application); + job.onFailure(); + Stream.of(dependents).forEach(Job::onFailure); + return; + } + + FullSpec fullSpec = buildFullSpec(job, aliveDependsOn); + jobStorage.insertJobs(Collections.singletonList(fullSpec)); - FullSpec fullSpec = buildFullSpec(job, aliveDependsOn); - jobStorage.insertJobs(Collections.singletonList(fullSpec)); + scheduleJobs(Collections.singletonList(job)); + } - scheduleJobs(Collections.singletonList(job)); + // We have no control over what happens in jobs' onSubmit method, so we drop our lock to reduce the possibility of a deadlock triggerOnSubmit(chain); - notifyAll(); + + synchronized (this) { + notifyAll(); + } } @WorkerThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java index 5b2df9c1b6..f6b4aab07f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java @@ -531,6 +531,34 @@ public void enqueue(@NonNull JobTracker.JobListener listener) { enqueue(); } + public Optional enqueueAndBlockUntilCompletion(long timeout) { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference resultState = new AtomicReference<>(); + JobTracker.JobListener listener = new JobTracker.JobListener() { + @Override + public void onStateChanged(@NonNull Job job, @NonNull JobTracker.JobState jobState) { + if (jobState.isComplete()) { + jobManager.removeListener(this); + resultState.set(jobState); + latch.countDown(); + } + } + }; + + enqueue(listener); + + try { + if (!latch.await(timeout, TimeUnit.MILLISECONDS)) { + return Optional.empty(); + } + } catch (InterruptedException e) { + Log.w(TAG, "Interrupted during enqueueSynchronously()", e); + return Optional.empty(); + } + + return Optional.ofNullable(resultState.get()); + } + @VisibleForTesting public List> getJobListChain() { return jobs; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DecryptionsDrainedConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DecryptionsDrainedConstraint.java index c6a95e5513..54231054eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DecryptionsDrainedConstraint.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DecryptionsDrainedConstraint.java @@ -21,7 +21,7 @@ private DecryptionsDrainedConstraint() { @Override public boolean isMet() { - return ApplicationDependencies.getIncomingMessageObserver().isDecryptionDrained(); + return ApplicationDependencies.getIncomingMessageObserver().getDecryptionDrained(); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GenerateAudioWaveFormJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/GenerateAudioWaveFormJob.kt new file mode 100644 index 0000000000..76b160c3f2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GenerateAudioWaveFormJob.kt @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.jobs + +import android.os.Build +import androidx.annotation.RequiresApi +import org.signal.core.util.concurrent.safeBlockingGet +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.attachments.AttachmentId +import org.thoughtcrime.securesms.attachments.DatabaseAttachment +import org.thoughtcrime.securesms.audio.AudioWaveForms +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.util.MediaUtil +import kotlin.time.Duration.Companion.days + +/** + * Generate and save wave form for an audio attachment. + */ +class GenerateAudioWaveFormJob private constructor(private val attachmentId: AttachmentId, parameters: Parameters) : BaseJob(parameters) { + + companion object { + private val TAG = Log.tag(GenerateAudioWaveFormJob::class.java) + + private const val KEY_PART_ROW_ID = "part_row_id" + private const val KEY_PAR_UNIQUE_ID = "part_unique_id" + + const val KEY = "GenerateAudioWaveFormJob" + + @JvmStatic + fun enqueue(attachmentId: AttachmentId) { + if (Build.VERSION.SDK_INT < 23) { + Log.i(TAG, "Unable to generate waveform on this version of Android") + } else { + ApplicationDependencies.getJobManager().add(GenerateAudioWaveFormJob(attachmentId)) + } + } + } + + private constructor(attachmentId: AttachmentId) : this( + attachmentId, + Parameters.Builder() + .setQueue("GenerateAudioWaveFormJob") + .setLifespan(1.days.inWholeMilliseconds) + .setMaxAttempts(1) + .build() + ) + + override fun serialize(): Data { + return Data.Builder() + .putLong(KEY_PART_ROW_ID, attachmentId.rowId) + .putLong(KEY_PAR_UNIQUE_ID, attachmentId.uniqueId) + .build() + } + + override fun getFactoryKey(): String = KEY + + @RequiresApi(23) + override fun onRun() { + val attachment: DatabaseAttachment? = SignalDatabase.attachments.getAttachment(attachmentId) + + if (attachment == null) { + Log.i(TAG, "Unable to find attachment in database.") + return + } + + if (!MediaUtil.isAudio(attachment)) { + Log.w(TAG, "Attempting to generate wave form for a non-audio attachment type: ${attachment.contentType}") + return + } + + try { + AudioWaveForms + .getWaveForm(context, attachment) + .safeBlockingGet() + + Log.i(TAG, "Generation successful") + } catch (e: Exception) { + Log.i(TAG, "Generation failed", e) + } + } + + override fun onShouldRetry(e: Exception): Boolean { + return false + } + + override fun onFailure() = Unit + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): GenerateAudioWaveFormJob { + return GenerateAudioWaveFormJob(AttachmentId(data.getLong(KEY_PART_ROW_ID), data.getLong(KEY_PAR_UNIQUE_ID)), parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index e862f239bc..ee979265a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.migrations.CachedAttachmentsMigrationJob; import org.thoughtcrime.securesms.migrations.ClearGlideCacheMigrationJob; import org.thoughtcrime.securesms.migrations.DatabaseMigrationJob; +import org.thoughtcrime.securesms.migrations.DecryptionsDrainedMigrationJob; import org.thoughtcrime.securesms.migrations.DeleteDeprecatedLogsMigrationJob; import org.thoughtcrime.securesms.migrations.DirectoryRefreshMigrationJob; import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob; @@ -103,8 +104,8 @@ public static Map getJobFactories(@NonNull Application appl put(FontDownloaderJob.KEY, new FontDownloaderJob.Factory()); put(ForceUpdateGroupV2Job.KEY, new ForceUpdateGroupV2Job.Factory()); put(ForceUpdateGroupV2WorkerJob.KEY, new ForceUpdateGroupV2WorkerJob.Factory()); + put(GenerateAudioWaveFormJob.KEY, new GenerateAudioWaveFormJob.Factory()); put(GiftSendJob.KEY, new FailingJob.Factory()); - put(SendPaymentsActivatedJob.KEY, new SendPaymentsActivatedJob.Factory()); put(GroupV1MigrationJob.KEY, new GroupV1MigrationJob.Factory()); put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory()); put(GroupCallPeekJob.KEY, new GroupCallPeekJob.Factory()); @@ -139,6 +140,7 @@ public static Map getJobFactories(@NonNull Application appl put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory()); put(MultiDeviceViewOnceOpenJob.KEY, new MultiDeviceViewOnceOpenJob.Factory()); put(MultiDeviceViewedUpdateJob.KEY, new MultiDeviceViewedUpdateJob.Factory()); + put(NewRegistrationUsernameSyncJob.KEY, new NewRegistrationUsernameSyncJob.Factory()); put(NullMessageSendJob.KEY, new NullMessageSendJob.Factory()); put(OptimizeMessageSearchIndexJob.KEY, new OptimizeMessageSearchIndexJob.Factory()); put(PaymentLedgerUpdateJob.KEY, new PaymentLedgerUpdateJob.Factory()); @@ -160,6 +162,7 @@ public static Map getJobFactories(@NonNull Application appl put(PushProcessMessageJob.KEY, new PushProcessMessageJob.Factory()); put(ReactionSendJob.KEY, new ReactionSendJob.Factory()); put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory()); + put(RefreshKbsCredentialsJob.KEY, new RefreshKbsCredentialsJob.Factory()); put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory()); put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory()); put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory()); @@ -175,6 +178,7 @@ public static Map getJobFactories(@NonNull Application appl put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory()); put(SenderKeyDistributionSendJob.KEY, new SenderKeyDistributionSendJob.Factory()); put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory()); + put(SendPaymentsActivatedJob.KEY, new SendPaymentsActivatedJob.Factory()); put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application)); put(SendRetryReceiptJob.KEY, new SendRetryReceiptJob.Factory()); put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application)); @@ -210,6 +214,7 @@ public static Map getJobFactories(@NonNull Application appl put(CachedAttachmentsMigrationJob.KEY, new FailingJob.Factory()); put(ClearGlideCacheMigrationJob.KEY, new ClearGlideCacheMigrationJob.Factory()); put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory()); + put(DecryptionsDrainedMigrationJob.KEY, new DecryptionsDrainedMigrationJob.Factory()); put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory()); put(DirectoryRefreshMigrationJob.KEY, new DirectoryRefreshMigrationJob.Factory()); put(EmojiDownloadMigrationJob.KEY, new EmojiDownloadMigrationJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java index df6af9865a..9ba21d67a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java @@ -57,9 +57,9 @@ public static void enqueue(boolean force) { .setQueue(QUEUE) .setMaxInstancesForFactory(1) .setMaxAttempts(3); - if (force || Build.VERSION.SDK_INT >= 31) { + if (force) { jobManager.cancelAllInQueue(QUEUE); - } else { + } else if (Build.VERSION.SDK_INT < 31) { parameters.addConstraint(ChargingConstraint.KEY); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java index 7c75a3f62b..ac04e0a24d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJobApi29.java @@ -207,6 +207,8 @@ private boolean verifyBackup(String backupPassword, DocumentFile temporaryFile, } catch (IOException e) { attempts++; Log.w(TAG, "Unable to find backup file, attempt: " + attempts + "/" + MAX_STORAGE_ATTEMPTS); + } catch (SecurityException e) { + Log.w(TAG, "Getting security exception when attempting to read file, aborting", e); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/NewRegistrationUsernameSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/NewRegistrationUsernameSyncJob.kt new file mode 100644 index 0000000000..e21928cd48 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/NewRegistrationUsernameSyncJob.kt @@ -0,0 +1,57 @@ +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.recipients.Recipient +import java.io.IOException + +/** + * If a user registers and the storage sync service doesn't contain a username, + * then we should delete our username from the server. + */ +class NewRegistrationUsernameSyncJob private constructor(parameters: Parameters) : BaseJob(parameters) { + + companion object { + private val TAG = Log.tag(NewRegistrationUsernameSyncJob::class.java) + + const val KEY = "NewRegistrationUsernameSyncJob" + } + + constructor() : this( + Parameters.Builder() + .setQueue(StorageSyncJob.QUEUE_KEY) + .setMaxInstancesForFactory(1) + .addConstraint(NetworkConstraint.KEY) + .build() + ) + + override fun serialize(): Data = Data.EMPTY + + override fun getFactoryKey(): String = KEY + + override fun onFailure() = Unit + + override fun onRun() { + if (SignalDatabase.recipients.getUsername(Recipient.self().id).isNullOrEmpty()) { + Log.i(TAG, "Clearing username from server.") + ApplicationDependencies.getSignalServiceAccountManager().deleteUsername() + } else { + Log.i(TAG, "Local user has a username, attempting username synchronization.") + RefreshOwnProfileJob.checkUsernameIsInSync() + } + } + + override fun onShouldRetry(e: Exception): Boolean { + return e is IOException + } + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): NewRegistrationUsernameSyncJob { + return NewRegistrationUsernameSyncJob(parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/OptimizeMessageSearchIndexJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/OptimizeMessageSearchIndexJob.kt index 80da8c115e..e3050dfba2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/OptimizeMessageSearchIndexJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/OptimizeMessageSearchIndexJob.kt @@ -1,10 +1,14 @@ package org.thoughtcrime.securesms.jobs +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobmanager.Data import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.transport.RetryLaterException import java.lang.Exception -import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds /** * Optimizes the message search index incrementally. @@ -14,10 +18,11 @@ class OptimizeMessageSearchIndexJob private constructor(parameters: Parameters) companion object { const val KEY = "OptimizeMessageSearchIndexJob" + private val TAG = Log.tag(OptimizeMessageSearchIndexJob::class.java) + @JvmStatic fun enqueue() { - // TODO [greyson] Temporarily disabled until we can figure out what to do. -// ApplicationDependencies.getJobManager().add(OptimizeMessageSearchIndexJob()) + ApplicationDependencies.getJobManager().add(OptimizeMessageSearchIndexJob()) } } @@ -33,15 +38,19 @@ class OptimizeMessageSearchIndexJob private constructor(parameters: Parameters) override fun getFactoryKey() = KEY override fun onFailure() = Unit override fun onShouldRetry(e: Exception) = e is RetryLaterException - override fun getNextRunAttemptBackoff(pastAttemptCount: Int, exception: Exception): Long = 1.minutes.inWholeMilliseconds + override fun getNextRunAttemptBackoff(pastAttemptCount: Int, exception: Exception): Long = 30.seconds.inWholeMilliseconds override fun onRun() { - // TODO [greyson] Temporarily disabled until we can figure out what to do. -// val success = SignalDatabase.messageSearch.optimizeIndex(10.seconds.inWholeMilliseconds) -// -// if (!success) { -// throw RetryLaterException() -// } + if (!SignalStore.registrationValues().isRegistrationComplete || SignalStore.account().aci == null) { + Log.w(TAG, "Registration not finished yet! Skipping.") + return + } + + val success = SignalDatabase.messageSearch.optimizeIndex(5.seconds.inWholeMilliseconds) + + if (!success) { + throw RetryLaterException() + } } class Factory : Job.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt index bdfae33392..b986343117 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt @@ -24,7 +24,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base companion object { const val KEY = "PnpInitializeDevicesJob" private val TAG = Log.tag(PnpInitializeDevicesJob::class.java) - private const val PLACEHOLDER_CODE = "123456" + private const val PLACEHOLDER_SESSION_ID = "123456789" @JvmStatic fun enqueueIfNecessary() { @@ -88,7 +88,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base try { Log.i(TAG, "Calling change number with our current number to distribute PNI messages") changeNumberRepository - .changeNumber(code = PLACEHOLDER_CODE, newE164 = e164, pniUpdateMode = true) + .changeNumber(sessionId = PLACEHOLDER_SESSION_ID, newE164 = e164, pniUpdateMode = true) .map(::VerifyResponseWithoutKbs) .safeBlockingGet() .resultOrThrow diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptDrainedJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptDrainedJob.java index 7aad9e6381..61d69b4106 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptDrainedJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptDrainedJob.java @@ -9,9 +9,8 @@ /** * A job that has the same queue as {@link PushDecryptMessageJob} that we enqueue so we can notify - * the {@link org.thoughtcrime.securesms.messages.IncomingMessageObserver} when decryptions have - * finished. This lets us know not just when the websocket is drained, but when all the decryptions - * for the messages we pulled down from the websocket have been finished. + * the {@link org.thoughtcrime.securesms.messages.IncomingMessageObserver} when the decryption job + * queue is empty. */ public class PushDecryptDrainedJob extends BaseJob { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java deleted file mode 100644 index eceaafea97..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.IdentityKey; -import org.signal.libsignal.protocol.SignalProtocolAddress; -import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; -import org.thoughtcrime.securesms.crypto.storage.SignalIdentityKeyStore; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState; -import org.thoughtcrime.securesms.messages.MessageDecryptionUtil; -import org.thoughtcrime.securesms.messages.MessageDecryptionUtil.DecryptionResult; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.transport.RetryLaterException; -import org.thoughtcrime.securesms.util.FeatureFlags; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.messages.SignalServicePniSignatureMessage; -import org.whispersystems.signalservice.api.push.PNI; -import org.whispersystems.signalservice.api.push.ServiceId; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.LinkedList; -import java.util.List; - -/** - * Decrypts an envelope. Enqueues a separate job, {@link PushProcessMessageJob}, to actually insert - * the result into our database. - */ -public final class PushDecryptMessageJob extends BaseJob { - - public static final String KEY = "PushDecryptJob"; - public static final String QUEUE = "__PUSH_DECRYPT_JOB__"; - - public static final String TAG = Log.tag(PushDecryptMessageJob.class); - - private static final String KEY_SMS_MESSAGE_ID = "sms_message_id"; - private static final String KEY_ENVELOPE = "envelope"; - - private final long smsMessageId; - private final SignalServiceEnvelope envelope; - - public PushDecryptMessageJob(Context context, @NonNull SignalServiceEnvelope envelope) { - this(context, envelope, -1); - } - - public PushDecryptMessageJob(Context context, @NonNull SignalServiceEnvelope envelope, long smsMessageId) { - this(new Parameters.Builder() - .setQueue(QUEUE) - .setMaxAttempts(Parameters.UNLIMITED) - .build(), - envelope, - smsMessageId); - setContext(context); - } - - private PushDecryptMessageJob(@NonNull Parameters parameters, @NonNull SignalServiceEnvelope envelope, long smsMessageId) { - super(parameters); - - this.envelope = envelope; - this.smsMessageId = smsMessageId; - } - - @Override - protected boolean shouldTrace() { - return true; - } - - @Override - public @NonNull Data serialize() { - return new Data.Builder().putBlobAsString(KEY_ENVELOPE, envelope.serialize()) - .putLong(KEY_SMS_MESSAGE_ID, smsMessageId) - .build(); - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws RetryLaterException { - List jobs = new LinkedList<>(); - DecryptionResult result = MessageDecryptionUtil.decrypt(context, envelope); - - if (result.getState() == MessageState.DECRYPTED_OK && envelope.isStory() && !isStoryMessage(result)) { - Log.w(TAG, "Envelope was flagged as a story, but it did not have any story-related content! Dropping."); - return; - } - - if (result.getContent() != null) { - if (result.getContent().getSenderKeyDistributionMessage().isPresent()) { - handleSenderKeyDistributionMessage(result.getContent().getSender(), result.getContent().getSenderDevice(), result.getContent().getSenderKeyDistributionMessage().get()); - } - result.getContent(); - if (envelope.hasReportingToken()) { - SignalDatabase.recipients().setReportingToken(RecipientId.from(result.getContent().getSender()), envelope.getReportingToken()); - } - - if (FeatureFlags.phoneNumberPrivacy() && result.getContent().getPniSignatureMessage().isPresent()) { - handlePniSignatureMessage(result.getContent().getSender(), result.getContent().getSenderDevice(), result.getContent().getPniSignatureMessage().get()); - } else if (result.getContent().getPniSignatureMessage().isPresent()) { - Log.w(TAG, "Ignoring PNI signature because the feature flag is disabled!"); - } - - jobs.add(new PushProcessMessageJob(result.getContent(), smsMessageId, envelope.getTimestamp())); - } else if (result.getException() != null && result.getState() != MessageState.NOOP) { - jobs.add(new PushProcessMessageJob(result.getState(), result.getException(), smsMessageId, envelope.getTimestamp())); - } - - jobs.addAll(result.getJobs()); - - for (Job job: jobs) { - ApplicationDependencies.getJobManager().add(job); - } - } - - @Override - public boolean onShouldRetry(@NonNull Exception exception) { - return exception instanceof RetryLaterException; - } - - @Override - public void onFailure() { - } - - private void handleSenderKeyDistributionMessage(@NonNull SignalServiceAddress address, int deviceId, @NonNull SenderKeyDistributionMessage message) { - Log.i(TAG, "Processing SenderKeyDistributionMessage from " + address.getServiceId() + "." + deviceId); - SignalServiceMessageSender sender = ApplicationDependencies.getSignalServiceMessageSender(); - sender.processSenderKeyDistributionMessage(new SignalProtocolAddress(address.getIdentifier(), deviceId), message); - } - - private void handlePniSignatureMessage(@NonNull SignalServiceAddress address, int deviceId, @NonNull SignalServicePniSignatureMessage pniSignatureMessage) { - Log.i(TAG, "Processing PniSignatureMessage from " + address.getServiceId() + "." + deviceId); - - PNI pni = pniSignatureMessage.getPni(); - - if (SignalDatabase.recipients().isAssociated(address.getServiceId(), pni)) { - Log.i(TAG, "[handlePniSignatureMessage] ACI (" + address.getServiceId() + ") and PNI (" + pni + ") are already associated."); - return; - } - - SignalIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities(); - SignalProtocolAddress aciAddress = new SignalProtocolAddress(address.getIdentifier(), deviceId); - SignalProtocolAddress pniAddress = new SignalProtocolAddress(pni.toString(), deviceId); - IdentityKey aciIdentity = identityStore.getIdentity(aciAddress); - IdentityKey pniIdentity = identityStore.getIdentity(pniAddress); - - if (aciIdentity == null) { - Log.w(TAG, "[validatePniSignature] No identity found for ACI address " + aciAddress); - return; - } - - if (pniIdentity == null) { - Log.w(TAG, "[validatePniSignature] No identity found for PNI address " + pniAddress); - return; - } - - if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.getSignature())) { - Log.i(TAG, "[validatePniSignature] PNI signature is valid. Associating ACI (" + address.getServiceId() + ") with PNI (" + pni + ")"); - SignalDatabase.recipients().getAndPossiblyMergePnpVerified(address.getServiceId(), pni, address.getNumber().orElse(null)); - } else { - Log.w(TAG, "[validatePniSignature] Invalid PNI signature! Cannot associate ACI (" + address.getServiceId() + ") with PNI (" + pni + ")"); - } - } - - private boolean isStoryMessage(@NonNull DecryptionResult result) { - if (result.getContent() == null) { - return false; - } - - if (result.getContent().getSenderKeyDistributionMessage().isPresent()) { - return true; - } - - if (result.getContent().getStoryMessage().isPresent()) { - return true; - } - - if (result.getContent().getDataMessage().isPresent() && - result.getContent().getDataMessage().get().getStoryContext().isPresent() && - result.getContent().getDataMessage().get().getGroupContext().isPresent()) - { - return true; - } - - if (result.getContent().getDataMessage().isPresent() && - result.getContent().getDataMessage().get().getRemoteDelete().isPresent()) - { - return true; - } - - return false; - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull PushDecryptMessageJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new PushDecryptMessageJob(parameters, - SignalServiceEnvelope.deserialize(data.getStringAsBlob(KEY_ENVELOPE)), - data.getLong(KEY_SMS_MESSAGE_ID)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.kt new file mode 100644 index 0000000000..90ba81d5eb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.kt @@ -0,0 +1,161 @@ +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata +import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState +import org.thoughtcrime.securesms.messages.MessageDecryptor +import org.thoughtcrime.securesms.transport.RetryLaterException +import org.whispersystems.signalservice.api.messages.SignalServiceContent +import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope +import org.whispersystems.signalservice.api.messages.SignalServiceMetadata +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer +import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer +import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto +import java.util.Optional + +/** + * Decrypts an envelope. Enqueues a separate job, [PushProcessMessageJob], to actually insert + * the result into our database. + */ +class PushDecryptMessageJob private constructor( + parameters: Parameters, + private val envelope: SignalServiceEnvelope, + private val smsMessageId: Long +) : BaseJob(parameters) { + + companion object { + val TAG = Log.tag(PushDecryptMessageJob::class.java) + + const val KEY = "PushDecryptJob" + const val QUEUE = "__PUSH_DECRYPT_JOB__" + + private const val KEY_SMS_MESSAGE_ID = "sms_message_id" + private const val KEY_ENVELOPE = "envelope" + } + + @Deprecated("No more jobs of this type should be enqueued. Decryptions now happen as things come off of the websocket.") + @JvmOverloads + constructor(envelope: SignalServiceEnvelope, smsMessageId: Long = -1) : this( + Parameters.Builder() + .setQueue(QUEUE) + .setMaxAttempts(Parameters.UNLIMITED) + .build(), + envelope, + smsMessageId + ) + + override fun shouldTrace() = true + + override fun serialize(): Data { + return Data.Builder() + .putBlobAsString(KEY_ENVELOPE, envelope.serialize()) + .putLong(KEY_SMS_MESSAGE_ID, smsMessageId) + .build() + } + + override fun getFactoryKey() = KEY + + @Throws(RetryLaterException::class) + public override fun onRun() { + val result = MessageDecryptor.decrypt(context, envelope.proto, envelope.serverDeliveredTimestamp) + + when (result) { + is MessageDecryptor.Result.Success -> { + ApplicationDependencies.getJobManager().add( + PushProcessMessageJob( + result.toMessageState(), + result.toSignalServiceContent(), + null, + smsMessageId, + result.envelope.timestamp + ) + ) + } + + is MessageDecryptor.Result.Error -> { + ApplicationDependencies.getJobManager().add( + PushProcessMessageJob( + result.toMessageState(), + null, + result.errorMetadata.toExceptionMetadata(), + smsMessageId, + result.envelope.timestamp + ) + ) + } + + is MessageDecryptor.Result.Ignore -> { + // No action needed + } + + else -> { + throw AssertionError("Unexpected result! ${result.javaClass.simpleName}") + } + } + + result.followUpOperations.forEach { it.run() } + } + + public override fun onShouldRetry(exception: Exception): Boolean { + return exception is RetryLaterException + } + + override fun onFailure() = Unit + + private fun MessageDecryptor.Result.toMessageState(): MessageState { + return when (this) { + is MessageDecryptor.Result.DecryptionError -> MessageState.DECRYPTION_ERROR + is MessageDecryptor.Result.Ignore -> MessageState.NOOP + is MessageDecryptor.Result.InvalidVersion -> MessageState.INVALID_VERSION + is MessageDecryptor.Result.LegacyMessage -> MessageState.LEGACY_MESSAGE + is MessageDecryptor.Result.Success -> MessageState.DECRYPTED_OK + is MessageDecryptor.Result.UnsupportedDataMessage -> MessageState.UNSUPPORTED_DATA_MESSAGE + } + } + + private fun MessageDecryptor.Result.Success.toSignalServiceContent(): SignalServiceContent { + val localAddress = SignalServiceAddress(this.metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164)) + val metadata = SignalServiceMetadata( + SignalServiceAddress(this.metadata.sourceServiceId, Optional.ofNullable(this.metadata.sourceE164)), + this.metadata.sourceDeviceId, + this.envelope.timestamp, + this.envelope.serverTimestamp, + this.serverDeliveredTimestamp, + this.metadata.sealedSender, + this.envelope.serverGuid, + Optional.ofNullable(this.metadata.groupId), + this.metadata.destinationServiceId.toString() + ) + + val contentProto = SignalServiceContentProto.newBuilder() + .setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress)) + .setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(metadata)) + .setContent(content) + .build() + + return SignalServiceContent.createFromProto(contentProto)!! + } + + private fun MessageDecryptor.ErrorMetadata.toExceptionMetadata(): ExceptionMetadata { + return ExceptionMetadata( + this.sender, + this.senderDevice, + this.groupId + ) + } + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): PushDecryptMessageJob { + return PushDecryptMessageJob( + parameters, + SignalServiceEnvelope.deserialize(data.getStringAsBlob(KEY_ENVELOPE)), + data.getLong(KEY_SMS_MESSAGE_ID) + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java index 20a4a2a0ac..cd7b9a3315 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java @@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; -import org.thoughtcrime.securesms.messages.RestStrategy; +import org.thoughtcrime.securesms.messages.WebSocketStrategy; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; @@ -60,7 +60,7 @@ public static Job withDelayedForegroundService(long foregroundServiceAfterMs) { @Override public void onRun() throws IOException { BackgroundMessageRetriever retriever = ApplicationDependencies.getBackgroundMessageRetriever(); - boolean result = retriever.retrieveMessages(context, foregroundServiceDelayMs, new RestStrategy()); + boolean result = retriever.retrieveMessages(context, foregroundServiceDelayMs, new WebSocketStrategy()); if (result) { Log.i(TAG, "Successfully pulled messages."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessEarlyMessagesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessEarlyMessagesJob.kt index 26622aa95e..8decf6a735 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessEarlyMessagesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessEarlyMessagesJob.kt @@ -48,7 +48,7 @@ class PushProcessEarlyMessagesJob private constructor(parameters: Parameters) : if (contents.isPresent) { for (content: SignalServiceContent in contents.get()) { Log.i(TAG, "[${id.sentTimestamp}] Processing early content for $id") - MessageContentProcessor.forEarlyContent(context).process(MessageContentProcessor.MessageState.DECRYPTED_OK, content, null, id.sentTimestamp, -1) + MessageContentProcessor.create(context).processEarlyContent(MessageContentProcessor.MessageState.DECRYPTED_OK, content, null, id.sentTimestamp, -1) } } else { Log.w(TAG, "[${id.sentTimestamp}] Saw $id in the cache, but when we went to retrieve it, it was already gone.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index fab5ed9541..73dbe71d86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -9,7 +9,6 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.groups.BadGroupIdException; import org.thoughtcrime.securesms.groups.GroupChangeBusyException; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; @@ -52,31 +51,6 @@ public final class PushProcessMessageJob extends BaseJob { private final long smsMessageId; private final long timestamp; - @WorkerThread - PushProcessMessageJob(@NonNull SignalServiceContent content, - long smsMessageId, - long timestamp) - { - this(MessageState.DECRYPTED_OK, - content, - null, - smsMessageId, - timestamp); - } - - @WorkerThread - PushProcessMessageJob(@NonNull MessageState messageState, - @NonNull ExceptionMetadata exceptionMetadata, - long smsMessageId, - long timestamp) - { - this(messageState, - null, - exceptionMetadata, - smsMessageId, - timestamp); - } - @WorkerThread public PushProcessMessageJob(@NonNull MessageState messageState, @Nullable SignalServiceContent content, @@ -185,7 +159,7 @@ protected boolean shouldTrace() { @Override public void onRun() throws Exception { - MessageContentProcessor processor = MessageContentProcessor.forNormalContent(context); + MessageContentProcessor processor = MessageContentProcessor.create(context); processor.process(messageState, content, exceptionMetadata, timestamp, smsMessageId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index faa1d02f58..99cb09584d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -20,6 +20,7 @@ import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; +import org.whispersystems.util.Base64; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -91,6 +92,7 @@ public void onRun() throws IOException { String registrationLockV2 = null; KbsValues kbsValues = SignalStore.kbsValues(); int pniRegistrationId = new RegistrationRepository(ApplicationDependencies.getApplication()).getPniRegistrationId(); + String recoveryPassword = kbsValues.getRecoveryPassword(); if (kbsValues.isV2RegistrationLockEnabled()) { registrationLockV2 = kbsValues.getRegistrationLockToken(); @@ -106,22 +108,27 @@ public void onRun() throws IOException { AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut()); Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() + + "\n Recovery password? " + !TextUtils.isEmpty(recoveryPassword) + "\n Phone number discoverable : " + phoneNumberDiscoverable + "\n Device Name : " + (encryptedDeviceName != null) + "\n Capabilities: " + capabilities); - SignalServiceAccountManager signalAccountManager = ApplicationDependencies.getSignalServiceAccountManager(); - signalAccountManager.setAccountAttributes(null, - registrationId, - fetchesMessages, - registrationLockV1, - registrationLockV2, - unidentifiedAccessKey, - universalUnidentifiedAccess, - capabilities, - phoneNumberDiscoverable, - encryptedDeviceName, - pniRegistrationId); + AccountAttributes accountAttributes = new AccountAttributes( + null, + registrationId, + fetchesMessages, + registrationLockV1, + registrationLockV2, + unidentifiedAccessKey, + universalUnidentifiedAccess, + capabilities, + phoneNumberDiscoverable, + (encryptedDeviceName == null) ? null : Base64.encodeBytes(encryptedDeviceName), + pniRegistrationId, + recoveryPassword + ); + + ApplicationDependencies.getSignalServiceAccountManager().setAccountAttributes(accountAttributes); hasRefreshedThisAppCycle = true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshKbsCredentialsJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshKbsCredentialsJob.kt new file mode 100644 index 0000000000..04265b3e55 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshKbsCredentialsJob.kt @@ -0,0 +1,67 @@ +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.pin.KbsRepository +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException +import java.io.IOException +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days + +/** + * Refresh KBS authentication credentials for talking to KBS during re-registration. + */ +class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : BaseJob(parameters) { + + companion object { + const val KEY = "RefreshKbsCredentialsJob" + + private val TAG = Log.tag(RefreshKbsCredentialsJob::class.java) + private val FREQUENCY: Duration = 15.days + + @JvmStatic + fun enqueueIfNecessary() { + if (SignalStore.kbsValues().hasPin()) { + val lastTimestamp = SignalStore.kbsValues().lastRefreshAuthTimestamp + if (lastTimestamp + FREQUENCY.inWholeMilliseconds < System.currentTimeMillis() || lastTimestamp > System.currentTimeMillis()) { + ApplicationDependencies.getJobManager().add(RefreshKbsCredentialsJob()) + } else { + Log.d(TAG, "Do not need to refresh credentials. Last refresh: $lastTimestamp") + } + } + } + } + + private constructor() : this( + parameters = Parameters.Builder() + .setQueue("RefreshKbsCredentials") + .addConstraint(NetworkConstraint.KEY) + .setMaxInstancesForQueue(2) + .setLifespan(1.days.inWholeMilliseconds) + .build() + ) + + override fun serialize(): Data = Data.Builder().build() + + override fun getFactoryKey(): String = KEY + + override fun onRun() { + KbsRepository().refreshAuthorization() + } + + override fun onShouldRetry(e: Exception): Boolean { + return e is IOException && e !is NonSuccessfulResponseCodeException + } + + override fun onFailure() = Unit + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): RefreshKbsCredentialsJob { + return RefreshKbsCredentialsJob(parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java index 78be2daa7d..ea867e7ce6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshOwnProfileJob.java @@ -4,8 +4,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import org.signal.core.util.logging.Log; +import org.signal.libsignal.usernames.BaseUsernameException; +import org.signal.libsignal.usernames.Username; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; @@ -19,6 +22,7 @@ import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.ProfileUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -28,8 +32,14 @@ import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil; +import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse; +import org.whispersystems.signalservice.internal.push.WhoAmIResponse; +import org.whispersystems.util.Base64UrlSafe; import java.io.IOException; +import java.util.Collections; +import java.util.Objects; + /** * Refreshes the profile of the local user. Different from {@link RetrieveProfileJob} in that we @@ -116,10 +126,11 @@ protected void onRun() throws Exception { profileAndCredential.getExpiringProfileKeyCredential() .ifPresent(expiringProfileKeyCredential -> setExpiringProfileKeyCredential(self, ProfileKeyUtil.getSelfProfileKey(), expiringProfileKeyCredential)); - String username = ApplicationDependencies.getSignalServiceAccountManager().getWhoAmI().getUsername(); - SignalDatabase.recipients().setUsername(Recipient.self().getId(), username); - StoryOnboardingDownloadJob.Companion.enqueueIfNeeded(); + + if (FeatureFlags.usernames()) { + checkUsernameIsInSync(); + } } private void setExpiringProfileKeyCredential(@NonNull Recipient recipient, @@ -219,6 +230,41 @@ private void ensureUnidentifiedAccessCorrect(@Nullable String unidentifiedAccess } } + static void checkUsernameIsInSync() { + try { + String localUsername = SignalDatabase.recipients().getUsername(Recipient.self().getId()); + boolean hasLocalUsername = !TextUtils.isEmpty(localUsername); + + if (!hasLocalUsername) { + return; + } + + WhoAmIResponse whoAmIResponse = ApplicationDependencies.getSignalServiceAccountManager().getWhoAmI(); + boolean hasServerUsername = !TextUtils.isEmpty(whoAmIResponse.getUsernameHash()); + String serverUsernameHash = whoAmIResponse.getUsernameHash(); + String localUsernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(localUsername)); + + if (!hasServerUsername || !Objects.equals(localUsernameHash, serverUsernameHash)) { + tryToReserveAndConfirmLocalUsername(localUsername, localUsernameHash); + } + } catch (IOException | BaseUsernameException e) { + Log.w(TAG, "Failed perform synchronization check", e); + } + } + + private static void tryToReserveAndConfirmLocalUsername(@NonNull String localUsername, @NonNull String localUsernameHash) { + try { + ReserveUsernameResponse response = ApplicationDependencies.getSignalServiceAccountManager() + .reserveUsername(Collections.singletonList(localUsernameHash)); + + ApplicationDependencies.getSignalServiceAccountManager() + .confirmUsername(localUsername, response); + } catch (IOException e) { + Log.d(TAG, "Failed to synchronize username.", e); + SignalStore.phoneNumberPrivacy().markUsernameOutOfSync(); + } + } + public static final class Factory implements Job.Factory { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java index 07ff29471d..df83d0f7a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * Report 1 to {@link #MAX_MESSAGE_COUNT} message guids received prior to {@link #timestamp} in {@link #threadId} to the server as spam. @@ -41,7 +42,7 @@ public class ReportSpamJob extends BaseJob { public ReportSpamJob(long threadId, long timestamp) { this(new Parameters.Builder().addConstraint(NetworkConstraint.KEY) - .setMaxAttempts(5) + .setLifespan(TimeUnit.DAYS.toMillis(1)) .setQueue("ReportSpamJob") .build(), threadId, @@ -75,13 +76,14 @@ public void onRun() throws IOException { int count = 0; List reportSpamData = SignalDatabase.messages().getReportSpamMessageServerData(threadId, timestamp, MAX_MESSAGE_COUNT); SignalServiceAccountManager signalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager(); - for (ReportSpamData data : reportSpamData) { - final RecipientId recipientId = data.getRecipientId(); - Optional serviceId = Recipient.resolved(recipientId).getServiceId(); + for (ReportSpamData data : reportSpamData) { + RecipientId recipientId = data.getRecipientId(); + Recipient recipient = Recipient.resolved(recipientId); + Optional serviceId = recipient.getServiceId(); if (serviceId.isPresent() && !serviceId.get().isUnknown()) { - String reportingTokenEncoded = ""; + String reportingTokenEncoded = null; byte[] reportingTokenBytes = SignalDatabase.recipients().getReportingToken(recipientId); if (reportingTokenBytes != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt index b76282f9af..5eb1415e2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt @@ -406,7 +406,7 @@ class RetrieveRemoteAnnouncementsJob private constructor(private val force: Bool @JsonProperty val linkText: String?, @JsonProperty val title: String, @JsonProperty val body: String, - @JsonProperty val callToActionText: String?, + @JsonProperty val callToActionText: String? ) class TranslatedRemoteMegaphone @JsonCreator constructor( @@ -415,7 +415,7 @@ class RetrieveRemoteAnnouncementsJob private constructor(private val force: Bool @JsonProperty val title: String, @JsonProperty val body: String, @JsonProperty val primaryCtaText: String?, - @JsonProperty val secondaryCtaText: String?, + @JsonProperty val secondaryCtaText: String? ) class Factory : Job.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java index 96db300eb6..177cef625c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java @@ -88,8 +88,8 @@ protected void onRun() throws IOException, RetryLaterException { StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey(); SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - RecipientTable recipientTable = SignalDatabase.recipients(); - UnknownStorageIdTable storageIdDatabase = SignalDatabase.unknownStorageIds(); + RecipientTable recipientTable = SignalDatabase.recipients(); + UnknownStorageIdTable storageIdTable = SignalDatabase.unknownStorageIds(); long currentVersion = accountManager.getStorageManifestVersion(); Map oldContactStorageIds = recipientTable.getContactStorageSyncIdsMap(); @@ -134,7 +134,7 @@ protected void onRun() throws IOException, RetryLaterException { SignalStore.storageService().setManifest(manifest); recipientTable.applyStorageIdUpdates(newContactStorageIds); recipientTable.applyStorageIdUpdates(Collections.singletonMap(Recipient.self().getId(), accountRecord.getId())); - storageIdDatabase.deleteAll(); + storageIdTable.deleteAll(); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java index 27899880d2..b019c9dce2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java @@ -331,7 +331,7 @@ private boolean performSync() throws IOException, RetryLaterException, InvalidKe Log.i(TAG, "Removed " + removedUnregistered + " recipients from storage service that have been unregistered for longer than 30 days."); } - List localStorageIds = getAllLocalStorageIds(self); + List localStorageIds = getAllLocalStorageIds(self).stream().filter(it -> !it.isUnknown()).collect(Collectors.toList()); IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIds); List remoteInserts = buildLocalStorageRecords(context, self, idDifference.getLocalOnlyIds()); List remoteDeletes = Stream.of(idDifference.getRemoteOnlyIds()).map(StorageId::getRaw).toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/KeyboardStickerListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/KeyboardStickerListAdapter.kt index 308f2d80df..2032fdfb8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/KeyboardStickerListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/sticker/KeyboardStickerListAdapter.kt @@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder class KeyboardStickerListAdapter( private val glideRequests: GlideRequests, private val eventListener: EventListener?, - private val allowApngAnimation: Boolean, + private val allowApngAnimation: Boolean ) : MappingAdapter() { init { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt index a507977304..ce4e465fa9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/AccountValues.kt @@ -76,7 +76,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal KEY_ACI_IDENTITY_PUBLIC_KEY, KEY_ACI_IDENTITY_PRIVATE_KEY, KEY_PNI_IDENTITY_PUBLIC_KEY, - KEY_PNI_IDENTITY_PRIVATE_KEY, + KEY_PNI_IDENTITY_PRIVATE_KEY ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java index dc54a90ce5..712e4e0dc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -19,15 +20,16 @@ public final class KbsValues extends SignalStoreValues { - public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled"; - private static final String MASTER_KEY = "kbs.registration_lock_master_key"; - private static final String TOKEN_RESPONSE = "kbs.token_response"; - private static final String PIN = "kbs.pin"; - private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash"; - private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp"; - public static final String OPTED_OUT = "kbs.opted_out"; - private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped"; - private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens"; + public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled"; + private static final String MASTER_KEY = "kbs.registration_lock_master_key"; + private static final String TOKEN_RESPONSE = "kbs.token_response"; + private static final String PIN = "kbs.pin"; + private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash"; + private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp"; + public static final String OPTED_OUT = "kbs.opted_out"; + private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped"; + private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens"; + private static final String KBS_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp"; KbsValues(KeyValueStore store) { super(store); @@ -55,6 +57,8 @@ public void clearRegistrationLockAndPin() { .remove(PIN) .remove(LAST_CREATE_FAILED_TIMESTAMP) .remove(OPTED_OUT) + .remove(KBS_AUTH_TOKENS) + .remove(KBS_LAST_AUTH_REFRESH_TIMESTAMP) .commit(); } @@ -149,6 +153,15 @@ public synchronized boolean lastPinCreateFailed() { } } + public synchronized @Nullable String getRecoveryPassword() { + MasterKey masterKey = getMasterKey(); + if (masterKey != null && hasPin()) { + return masterKey.deriveRegistrationRecoveryPassword(); + } else { + return null; + } + } + public synchronized @Nullable String getPin() { return getString(PIN, null); } @@ -171,6 +184,7 @@ public synchronized void setPinForgottenOrSkipped(boolean value) { public synchronized void putAuthTokenList(List tokens) { putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE); + setLastRefreshAuthTimestamp(System.currentTimeMillis()); } public synchronized List getKbsAuthTokenList() { @@ -193,6 +207,16 @@ public synchronized boolean appendAuthTokenToList(String token) { } } + public boolean removeAuthTokens(@NonNull List invalid) { + List tokens = new ArrayList<>(getKbsAuthTokenList()); + if (tokens.removeAll(invalid)) { + putAuthTokenList(tokens); + return true; + } + + return false; + } + /** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */ public synchronized void optOut() { getStore().beginWrite() @@ -220,4 +244,12 @@ public synchronized boolean hasOptedOut() { throw new AssertionError(e); } } + + private void setLastRefreshAuthTimestamp(long timestamp) { + putLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, timestamp); + } + + public long getLastRefreshAuthTimestamp() { + return getLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, 0L); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java index 7611fc885c..90c5903bfe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java @@ -5,10 +5,8 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; public final class MiscellaneousValues extends SignalStoreValues { @@ -30,6 +28,7 @@ public final class MiscellaneousValues extends SignalStoreValues { private static final String LAST_FOREGROUND_TIME = "misc.last_foreground_time"; private static final String PNI_INITIALIZED_DEVICES = "misc.pni_initialized_devices"; private static final String SMS_PHASE_1_START_MS = "misc.sms_export.phase_1_start.3"; + private static final String LINKED_DEVICES_REMINDER = "misc.linked_devices_reminder"; MiscellaneousValues(@NonNull KeyValueStore store) { super(store); @@ -224,4 +223,12 @@ public boolean hasPniInitializedDevices() { public void setPniInitializedDevices(boolean value) { putBoolean(PNI_INITIALIZED_DEVICES, value); } + + public void setShouldShowLinkedDevicesReminder(boolean value) { + putBoolean(LINKED_DEVICES_REMINDER, value); + } + + public boolean getShouldShowLinkedDevicesReminder() { + return getBoolean(LINKED_DEVICES_REMINDER, false); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/OnboardingValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/OnboardingValues.java index a5f5c3f361..32e49fa66f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/OnboardingValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/OnboardingValues.java @@ -73,7 +73,7 @@ public void setShowAddPhoto(boolean value) { putBoolean(SHOW_ADD_PHOTO, value); } - public boolean shouldShowAddPhoto(){ + public boolean shouldShowAddPhoto() { return getBoolean(SHOW_ADD_PHOTO, false); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PhoneNumberPrivacyValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PhoneNumberPrivacyValues.java index 7837158a1a..94765d05f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PhoneNumberPrivacyValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PhoneNumberPrivacyValues.java @@ -11,9 +11,10 @@ public final class PhoneNumberPrivacyValues extends SignalStoreValues { - public static final String SHARING_MODE = "phoneNumberPrivacy.sharingMode"; - public static final String LISTING_MODE = "phoneNumberPrivacy.listingMode"; - public static final String LISTING_TIMESTAMP = "phoneNumberPrivacy.listingMode.timestamp"; + public static final String SHARING_MODE = "phoneNumberPrivacy.sharingMode"; + public static final String LISTING_MODE = "phoneNumberPrivacy.listingMode"; + public static final String LISTING_TIMESTAMP = "phoneNumberPrivacy.listingMode.timestamp"; + public static final String USERNAME_OUT_OF_SYNC = "phoneNumberPrivacy.usernameOutOfSync"; private static final Collection REGULAR_CERTIFICATE = Collections.singletonList(CertificateType.UUID_AND_E164); private static final Collection PRIVACY_CERTIFICATE = Collections.singletonList(CertificateType.UUID_ONLY); @@ -68,6 +69,18 @@ public long getPhoneNumberListingModeTimestamp() { return getLong(LISTING_TIMESTAMP, 0); } + public void markUsernameOutOfSync() { + putBoolean(USERNAME_OUT_OF_SYNC, true); + } + + public void clearUsernameOutOfSync() { + putBoolean(USERNAME_OUT_OF_SYNC, false); + } + + public boolean isUsernameOutOfSync() { + return getBoolean(USERNAME_OUT_OF_SYNC, false); + } + /** * If you respect {@link #getPhoneNumberSharingMode}, then you will only ever need to fetch and store * these certificates types. diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PlainTextSharedPrefsDataStore.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PlainTextSharedPrefsDataStore.kt index b252979a52..250411029a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PlainTextSharedPrefsDataStore.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/PlainTextSharedPrefsDataStore.kt @@ -25,6 +25,7 @@ class PlainTextSharedPrefsDataStore(private val context: Context) { */ var smsMigrationIdOffset: Long get() = sharedPrefs.getLong(SMS_MIGRATION_ID_OFFSET, -1) + @SuppressLint("ApplySharedPref") set(value) { sharedPrefs.edit().putLong(SMS_MIGRATION_ID_OFFSET, value).commit() diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java index 0b955064cc..273cff76f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java @@ -2,6 +2,7 @@ import androidx.annotation.CheckResult; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.Collections; import java.util.List; @@ -11,6 +12,8 @@ public final class RegistrationValues extends SignalStoreValues { private static final String REGISTRATION_COMPLETE = "registration.complete"; private static final String PIN_REQUIRED = "registration.pin_required"; private static final String HAS_UPLOADED_PROFILE = "registration.has_uploaded_profile"; + private static final String SESSION_E164 = "registration.session_e164"; + private static final String SESSION_ID = "registration.session_id"; RegistrationValues(@NonNull KeyValueStore store) { super(store); @@ -60,4 +63,22 @@ public void markHasUploadedProfile() { public void clearHasUploadedProfile() { putBoolean(HAS_UPLOADED_PROFILE, false); } + + public void setSessionId(String sessionId) { + putString(SESSION_ID, sessionId); + } + + @Nullable + public String getSessionId() { + return getString(SESSION_ID, null); + } + + public void setSessionE164(String sessionE164) { + putString(SESSION_E164, sessionE164); + } + + @Nullable + public String getSessionE164() { + return getString(SESSION_E164, null); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java index 41a410c12b..b3e2585194 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java @@ -203,7 +203,7 @@ private void onLearnMore() { private void onPinSkipped() { PinOptOutDialog.show(requireContext(), () -> { - RegistrationUtil.maybeMarkRegistrationComplete(requireContext()); + RegistrationUtil.maybeMarkRegistrationComplete(); closeNavGraphBranch(); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt index bce9bad942..440dcd7ca0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/ConfirmKbsPinFragment.kt @@ -85,12 +85,12 @@ internal class ConfirmKbsPinFragment : BaseKbsPinFragment { confirm.cancelSpinning() - RegistrationUtil.maybeMarkRegistrationComplete(requireContext()) + RegistrationUtil.maybeMarkRegistrationComplete() displayFailedDialog() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java index 2afd6b86f3..464ed30fac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/KbsSplashFragment.java @@ -87,16 +87,15 @@ public void onPrepareOptionsMenu(@NonNull Menu menu) { @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_pin_learn_more: - onLearnMore(); - return true; - case R.id.menu_pin_skip: - onPinSkipped(); - return true; + if (item.getItemId() == R.id.menu_pin_learn_more) { + onLearnMore(); + return true; + } else if (item.getItemId() == R.id.menu_pin_skip) { + onPinSkipped(); + return true; + } else { + return false; } - - return false; } private void setUpRegLockEnabled() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java b/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java index 2d261dbd75..4cd67e88eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/maps/PlacePickerActivity.java @@ -67,13 +67,14 @@ public static AddressData addressFromData(@NonNull Intent data) { return data.getParcelableExtra(ADDRESS_INTENT); } + @SuppressLint("MissingInflatedId") @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_place_picker); bottomSheet = findViewById(R.id.bottom_sheet); - View markerImage = findViewById(R.id.marker_image_view); + View markerImage = findViewById(R.id.marker_image_view); View fab = findViewById(R.id.place_chosen_button); findViewById(R.id.btnMapTypeNormal).setOnClickListener(v -> handleMapType("normal")); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java index 37a758e4a0..ebca48a55b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java @@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.database.MediaTable; import org.thoughtcrime.securesms.database.MediaTable.MediaRecord; import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader.GroupedThreadMedia; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache; import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; @@ -345,7 +346,10 @@ public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord media } thumbnailView.setImageResource(glideRequests, slide, false, false); - thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); + thumbnailView.setOnClickListener(view -> { + MediaPreviewCache.INSTANCE.setDrawable(thumbnailView.getImageDrawable()); + itemClickListener.onMediaClicked(thumbnailView, mediaRecord); + }); thumbnailView.setOnLongClickListener(view -> onLongClick()); } @@ -411,7 +415,7 @@ public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord media line1.setText(fileName.orElse(fileTypeDescription)); line2.setText(getLine2(context, mediaRecord, slide)); - itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); + itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(getTransitionAnchor(), mediaRecord)); itemView.setOnLongClickListener(view -> onLongClick()); selectForMarque = () -> line1.setSelected(true); handler = new Handler(Looper.getMainLooper()); @@ -459,6 +463,10 @@ public void onChanged(Pair fromToPair) { return fileName.orElse(null); } + protected @NonNull View getTransitionAnchor() { + return itemView; + } + private @NonNull String describe(@NonNull Recipient from, @NonNull Recipient thread) { if (from == Recipient.UNKNOWN && thread == Recipient.UNKNOWN) { return fileName.orElse(fileTypeDescription); @@ -541,8 +549,8 @@ public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord media audioView.setAudio((AudioSlide) slide, new AudioViewCallbacksAdapter(audioItemListener, mmsId), true, true); audioItemListener.registerPlaybackStateObserver(audioView.getPlaybackStateObserver()); - audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); - itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); + audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(audioView, mediaRecord)); + itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(audioView, mediaRecord)); } @Override @@ -584,10 +592,16 @@ public void bind(@NonNull Context context, @NonNull MediaTable.MediaRecord media super.bind(context, mediaRecord, slide); this.slide = slide; thumbnailView.setImageResource(glideRequests, slide, false, false); - thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); + thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(thumbnailView, mediaRecord)); thumbnailView.setOnLongClickListener(view -> onLongClick()); } + @Override + protected @NonNull View getTransitionAnchor() { + MediaPreviewCache.INSTANCE.setDrawable(null); + return thumbnailView; + } + @Override protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { if (slide.hasVideo()) return context.getString(R.string.MediaOverviewActivity_video); @@ -648,7 +662,7 @@ public void onProgressUpdated(long durationMillis, long playheadMillis) { } interface ItemClickListener { - void onMediaClicked(@NonNull MediaTable.MediaRecord mediaRecord); + void onMediaClicked(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord); void onMediaLongClicked(MediaTable.MediaRecord mediaRecord); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java index 897dd8c154..b61bc7997c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewActivity.java @@ -34,6 +34,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.tabs.TabLayout; +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback; import org.signal.libsignal.protocol.util.Pair; import org.thoughtcrime.securesms.PassphraseRequiredActivity; @@ -91,6 +92,7 @@ protected void onPreCreate() { @Override protected void onCreate(Bundle bundle, boolean ready) { + setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback()); setContentView(R.layout.media_overview_activity); initializeResources(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java index 438b9ede39..73ca6a3ad1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -1,11 +1,13 @@ package org.thoughtcrime.securesms.mediaoverview; +import android.app.ActivityOptions; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; +import android.util.Size; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -27,6 +29,7 @@ import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; +import org.signal.core.util.DimensionUnit; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; @@ -38,6 +41,7 @@ import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader; import org.thoughtcrime.securesms.database.loaders.MediaLoader; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.BottomOffsetDecoration; @@ -196,11 +200,11 @@ public void onLoaderReset(@NonNull Loader events.singleTapOnMedia()); - return zoomingImageView; + lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> { + zoomingImageView.setAlpha(state.isInSharedAnimation() ? 0f : 1f); + })); + + return view; } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt index ad920519d7..339f7a9079 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaIntentFactory.kt @@ -6,6 +6,8 @@ import android.net.Uri import android.os.Bundle import android.os.Parcelable import kotlinx.parcelize.Parcelize +import org.signal.core.util.dp +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.database.MediaTable import org.thoughtcrime.securesms.database.MediaTable.MediaRecord @@ -15,15 +17,16 @@ object MediaIntentFactory { const val NOT_IN_A_THREAD = -2 const val UNKNOWN_TIMESTAMP = -2 - const val THREAD_ID_EXTRA = "thread_id" - const val DATE_EXTRA = "date" - const val SIZE_EXTRA = "size" - const val CAPTION_EXTRA = "caption" - const val LEFT_IS_RECENT_EXTRA = "left_is_recent" - const val HIDE_ALL_MEDIA_EXTRA = "came_from_all_media" - const val SHOW_THREAD_EXTRA = "show_thread" - const val SORTING_EXTRA = "sorting" - const val IS_VIDEO_GIF = "is_video_gif" + + @Parcelize + data class SharedElementArgs( + val width: Int = 1, + val height: Int = 1, + val topLeft: Float = 0f, + val topRight: Float = 0f, + val bottomRight: Float = 0f, + val bottomLeft: Float = 0f + ) : Parcelable @Parcelize data class MediaPreviewArgs( @@ -38,11 +41,12 @@ object MediaIntentFactory { val showThread: Boolean = false, val allMediaInRail: Boolean = false, val sorting: MediaTable.Sorting, - val isVideoGif: Boolean + val isVideoGif: Boolean, + val sharedElementArgs: SharedElementArgs = SharedElementArgs() ) : Parcelable @JvmStatic - fun requireArguments(bundle: Bundle): MediaPreviewArgs = bundle.getParcelable(ARGS_KEY)!! + fun requireArguments(bundle: Bundle): MediaPreviewArgs = bundle.getParcelableCompat(ARGS_KEY, MediaPreviewArgs::class.java)!! @JvmStatic fun create(context: Context, args: MediaPreviewArgs): Intent { @@ -68,7 +72,15 @@ object MediaIntentFactory { leftIsRecent, allMediaInRail = allMediaInRail, sorting = MediaTable.Sorting.Newest, - isVideoGif = attachment.isVideoGif + isVideoGif = attachment.isVideoGif, + sharedElementArgs = SharedElementArgs( + attachment.width, + attachment.height, + 12.dp.toFloat(), + 12.dp.toFloat(), + 12.dp.toFloat(), + 12.dp.toFloat() + ) ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewCache.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewCache.kt new file mode 100644 index 0000000000..5684a7162b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewCache.kt @@ -0,0 +1,12 @@ +package org.thoughtcrime.securesms.mediapreview + +import android.graphics.drawable.Drawable + +/** + * Stores the bitmap for a thumbnail we are animating from via a shared + * element transition. This prevents us from having to load anything on the + * receiving end. + */ +object MediaPreviewCache { + var drawable: Drawable? = null +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt index 13f0e2533c..908210b1bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt @@ -36,7 +36,7 @@ class MediaPreviewRepository { /** * Accessor for database attachments. - * @param startingUri the initial position to select from + * @param startingAttachmentId the initial position to select from * @param threadId the thread to select from * @param sorting the ordering of the results * @param limit the maximum quantity of the results diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt index 03aef514c8..e52bc5bf82 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt @@ -2,36 +2,136 @@ package org.thoughtcrime.securesms.mediapreview import android.content.Context import android.os.Bundle +import android.view.View +import android.view.ViewGroup.LayoutParams +import android.widget.ImageView +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.ContextCompat +import androidx.core.transition.addListener +import androidx.core.view.animation.PathInterpolatorCompat +import androidx.core.view.updateLayoutParams import androidx.fragment.app.commit +import com.google.android.material.shape.ShapeAppearanceModel +import com.google.android.material.transition.platform.MaterialContainerTransform +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner +import org.thoughtcrime.securesms.util.LifecycleDisposable +import org.thoughtcrime.securesms.util.WindowUtil class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner { override lateinit var voiceNoteMediaController: VoiceNoteMediaController + private val viewModel: MediaPreviewV2ViewModel by viewModels() + private val lifecycleDisposable = LifecycleDisposable() + private val args by lazy { + MediaIntentFactory.requireArguments(intent.extras!!) + } + + private lateinit var transitionImageView: ImageView + override fun attachBaseContext(newBase: Context) { delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES super.attachBaseContext(newBase) } override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + if (MediaPreviewCache.drawable != null) { + val originalCorners = ShapeAppearanceModel.Builder() + .setTopLeftCornerSize(args.sharedElementArgs.topLeft) + .setTopRightCornerSize(args.sharedElementArgs.topRight) + .setBottomRightCornerSize(args.sharedElementArgs.bottomRight) + .setBottomLeftCornerSize(args.sharedElementArgs.bottomLeft) + .build() + + setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback()) + window.sharedElementEnterTransition = MaterialContainerTransform().apply { + addTarget(SHARED_ELEMENT_TRANSITION_NAME) + startShapeAppearanceModel = originalCorners + endShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build() + duration = 250L + interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f) + addListener( + onStart = { + transitionImageView.alpha = 1f + viewModel.setIsInSharedAnimation(true) + }, + onEnd = { + viewModel.setIsInSharedAnimation(false) + } + ) + } + + window.sharedElementExitTransition = MaterialContainerTransform().apply { + addTarget(SHARED_ELEMENT_TRANSITION_NAME) + startShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build() + endShapeAppearanceModel = originalCorners + duration = 250L + interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f) + addListener( + onStart = { + transitionImageView.alpha = 1f + viewModel.setIsInSharedAnimation(true) + }, + onEnd = { + viewModel.setIsInSharedAnimation(false) + } + ) + } + } + super.onCreate(savedInstanceState, ready) setTheme(R.style.TextSecure_MediaPreview) setContentView(R.layout.activity_mediapreview_v2) + + transitionImageView = findViewById(R.id.transition_image_view) + val cacheDrawable = MediaPreviewCache.drawable + if (cacheDrawable != null) { + val bounds = cacheDrawable.bounds + val aspectRatio = bounds.width().toFloat() / bounds.height() + val screenRatio = resources.displayMetrics.widthPixels.toFloat() / resources.displayMetrics.heightPixels + if (aspectRatio > screenRatio) { + transitionImageView.updateLayoutParams { + width = LayoutParams.MATCH_PARENT + } + } else { + transitionImageView.updateLayoutParams { + height = LayoutParams.MATCH_PARENT + } + } + + transitionImageView.setImageDrawable(MediaPreviewCache.drawable) + + lifecycleDisposable += viewModel.state.map { + it.isInSharedAnimation to it.loadState + }.distinctUntilChanged().subscribe { (isInSharedAnimation, loadState) -> + if (!isInSharedAnimation && loadState == MediaPreviewV2State.LoadState.MEDIA_READY) { + transitionImageView.clearAnimation() + transitionImageView.animate() + .setInterpolator(PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)) + .setDuration(200) + .alpha(0f) + } + } + } else { + transitionImageView.visibility = View.INVISIBLE + viewModel.setIsInSharedAnimation(false) + } + voiceNoteMediaController = VoiceNoteMediaController(this) val systemBarColor = ContextCompat.getColor(this, R.color.signal_dark_colorSurface) window.statusBarColor = systemBarColor window.navigationBarColor = systemBarColor + WindowUtil.clearLightStatusBar(window) + WindowUtil.clearLightNavigationBar(window) if (savedInstanceState == null) { val bundle = Bundle() - val args = MediaIntentFactory.requireArguments(intent.extras!!) bundle.putParcelable(MediaPreviewV2Fragment.ARGS_KEY, args) supportFragmentManager.commit { setReorderingAllowed(true) @@ -40,7 +140,21 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr } } + override fun onPause() { + super.onPause() + MediaPreviewCache.drawable = null + } + + override fun finishAfterTransition() { + if (viewModel.shouldFinishAfterTransition(args.initialMediaUri)) { + super.finishAfterTransition() + } else { + super.finish() + } + } + companion object { private const val FRAGMENT_TAG = "media_preview_fragment_v2" + const val SHARED_ELEMENT_TRANSITION_NAME = "thumb" } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt index d775e32f59..473cd1e282 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt @@ -29,7 +29,7 @@ class MediaPreviewV2Adapter(fragment: Fragment) : FragmentStateAdapter(fragment) MediaPreviewFragment.DATA_CONTENT_TYPE to contentType, MediaPreviewFragment.DATA_SIZE to attachment.size, MediaPreviewFragment.AUTO_PLAY to attachment.isVideoGif, - MediaPreviewFragment.VIDEO_GIF to attachment.isVideoGif, + MediaPreviewFragment.VIDEO_GIF to attachment.isVideoGif ) val fragment = if (MediaUtil.isVideo(contentType)) { VideoMediaPreviewFragment() diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index a6be6e1cdc..87e867a1cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -79,7 +79,9 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v private val lifecycleDisposable = LifecycleDisposable() private val binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind) - private val viewModel: MediaPreviewV2ViewModel by viewModels() + private val viewModel: MediaPreviewV2ViewModel by viewModels(ownerProducer = { + requireActivity() + }) private val debouncer = Debouncer(2, TimeUnit.SECONDS) private lateinit var pagerAdapter: MediaPreviewV2Adapter @@ -90,7 +92,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v override fun onAttach(context: Context) { super.onAttach(context) - fullscreenHelper = FullscreenHelper(requireActivity()) + fullscreenHelper = FullscreenHelper(requireActivity(), true) individualItemWidth = context.resources.getDimension(R.dimen.media_rail_item_size).roundToInt() } @@ -135,7 +137,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v @SuppressLint("RestrictedApi") private fun initializeToolbar(toolbar: MaterialToolbar) { toolbar.setNavigationOnClickListener { - requireActivity().onBackPressed() + requireActivity().onBackPressedDispatcher.onBackPressed() } toolbar.setTitleTextAppearance(requireContext(), R.style.Signal_Text_TitleMedium) @@ -253,7 +255,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v val messageId: Long? = currentItem.attachment?.mmsId if (messageId != null) { binding.toolbar.setOnClickListener { v -> - viewModel.jumpToFragment(v.context, messageId).subscribeBy( + lifecycleDisposable += viewModel.jumpToFragment(v.context, messageId).subscribeBy( onSuccess = { startActivity(it) requireActivity().finish() @@ -350,6 +352,10 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v } private fun scrollAlbumRailToCurrentAdapterPosition(smooth: Boolean = true) { + if (!isResumed) { + return + } + val currentItemPosition = albumRailAdapter.findSelectedItemPosition() val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView val offsetFromStart = (albumRail.width - individualItemWidth) / 2 diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt index 56c3439fe9..bfde97ec2a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt @@ -13,6 +13,7 @@ data class MediaPreviewV2State( val leftIsRecent: Boolean = false, val albums: Map> = mapOf(), val messageBodies: Map = mapOf(), + val isInSharedAnimation: Boolean = true ) { enum class LoadState { INIT, DATA_LOADED, MEDIA_READY } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt index a75739eef1..3632ace6d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.mediapreview import android.content.Context import android.content.Intent +import android.net.Uri import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable @@ -27,6 +28,14 @@ class MediaPreviewV2ViewModel : ViewModel() { val currentPosition: Int get() = store.state.position + fun setIsInSharedAnimation(isInSharedAnimation: Boolean) { + store.update { it.copy(isInSharedAnimation = isInSharedAnimation) } + } + + fun shouldFinishAfterTransition(initialMediaUri: Uri): Boolean { + return currentPosition in store.state.mediaRecords.indices && store.state.mediaRecords[currentPosition].toMedia()?.uri == initialMediaUri + } + fun fetchAttachments(context: Context, startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting, forceRefresh: Boolean = false) { if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) { disposables += store.update(repository.getAttachments(context, startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State -> @@ -44,7 +53,7 @@ class MediaPreviewV2ViewModel : ViewModel() { mediaRecords = result.records, messageBodies = result.messageBodies, albums = albums, - loadState = MediaPreviewV2State.LoadState.DATA_LOADED, + loadState = MediaPreviewV2State.LoadState.DATA_LOADED ) } else { oldState.copy( @@ -52,7 +61,7 @@ class MediaPreviewV2ViewModel : ViewModel() { mediaRecords = result.records.reversed(), messageBodies = result.messageBodies, albums = albums.mapValues { it.value.reversed() }, - loadState = MediaPreviewV2State.LoadState.DATA_LOADED, + loadState = MediaPreviewV2State.LoadState.DATA_LOADED ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java index f413ffc787..7a30967ffd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java @@ -9,6 +9,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; import com.google.android.exoplayer2.ui.PlayerControlView; @@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner; import org.thoughtcrime.securesms.mms.VideoSlide; +import org.thoughtcrime.securesms.util.LifecycleDisposable; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.video.VideoPlayer; @@ -27,8 +29,11 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { private static final Long MINIMUM_DURATION_FOR_SKIP_MS = TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS); - private VideoPlayer videoView; - private boolean isVideoGif; + private VideoPlayer videoView; + private boolean isVideoGif; + private MediaPreviewV2ViewModel viewModel; + private LifecycleDisposable lifecycleDisposable; + @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -45,7 +50,14 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c throw new AssertionError("This fragment can only display video"); } - videoView = itemView.findViewById(R.id.video_player); + videoView = itemView.findViewById(R.id.video_player); + viewModel = new ViewModelProvider(requireActivity()).get(MediaPreviewV2ViewModel.class); + lifecycleDisposable = new LifecycleDisposable(); + + lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> { + Log.d(TAG, "ANIM" + state.isInSharedAnimation()); + itemView.setVisibility(state.isInSharedAnimation() ? View.INVISIBLE : View.VISIBLE); + })); videoView.setWindow(requireActivity().getWindow()); videoView.setVideoSource(new VideoSlide(getContext(), uri, size, false), autoPlay, TAG); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/caption/ExpandingCaptionView.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/caption/ExpandingCaptionView.kt index 2f47f45b2c..7f55d4a9dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/caption/ExpandingCaptionView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/caption/ExpandingCaptionView.kt @@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiTextView class ExpandingCaptionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0, + defStyleAttr: Int = 0 ) : EmojiTextView(context, attrs, defStyleAttr) { var expandedHeight = 0 diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java index 7040f9311d..4f075e6cbf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java @@ -29,7 +29,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.cardview.widget.CardView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; @@ -39,6 +38,7 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.transition.Transition; +import com.google.android.material.card.MaterialCardView; import org.signal.core.util.Stopwatch; import org.signal.core.util.logging.Log; @@ -360,9 +360,9 @@ private void initControls() { } private void initializeViewFinderAndControlsPositioning() { - CardView cameraCard = requireView().findViewById(R.id.camera_preview_parent); - View controls = requireView().findViewById(R.id.camera_controls_container); - CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity()); + MaterialCardView cameraCard = requireView().findViewById(R.id.camera_preview_parent); + View controls = requireView().findViewById(R.id.camera_controls_container); + CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity()); if (!cameraDisplay.getRoundViewFinderCorners()) { cameraCard.setRadius(0f); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java index a97d695674..f5bc3c96f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -26,7 +26,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; @@ -35,13 +34,13 @@ import androidx.camera.view.LifecycleCameraController; import androidx.camera.view.PreviewView; import androidx.camera.view.video.ExperimentalVideo; -import androidx.cardview.widget.CardView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import androidx.core.content.ContextCompat; import com.bumptech.glide.Glide; import com.bumptech.glide.util.Executors; +import com.google.android.material.card.MaterialCardView; import org.signal.core.util.Stopwatch; import org.signal.core.util.concurrent.SimpleTask; @@ -293,9 +292,9 @@ private void updateGalleryVisibility() { } private void initializeViewFinderAndControlsPositioning() { - CardView cameraCard = requireView().findViewById(R.id.camerax_camera_parent); - View controls = requireView().findViewById(R.id.camerax_controls_container); - CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity()); + MaterialCardView cameraCard = requireView().findViewById(R.id.camerax_camera_parent); + View controls = requireView().findViewById(R.id.camerax_controls_container); + CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity()); if (!cameraDisplay.getRoundViewFinderCorners()) { cameraCard.setRadius(0f); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java index 3764f8a857..455d55368a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java @@ -308,14 +308,12 @@ private Optional getMostRecentItem(@NonNull Context context) { return media.size() > 0 ? Optional.of(media.get(0)) : Optional.empty(); } - @TargetApi(16) @SuppressWarnings("SuspiciousNameCombination") private String getWidthColumn(int orientation) { if (orientation == 0 || orientation == 180) return Images.Media.WIDTH; else return Images.Media.HEIGHT; } - @TargetApi(16) @SuppressWarnings("SuspiciousNameCombination") private String getHeightColumn(int orientation) { if (orientation == 0 || orientation == 180) return Images.Media.HEIGHT; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.kt index 5581dde6f7..10219168eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivityResult.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import kotlinx.parcelize.Parceler import kotlinx.parcelize.Parcelize import kotlinx.parcelize.TypeParceler +import org.signal.core.util.getParcelableExtraCompat import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.database.model.Mention import org.thoughtcrime.securesms.database.model.StoryType @@ -43,7 +44,7 @@ class MediaSendActivityResult( @JvmStatic fun fromData(data: Intent): MediaSendActivityResult { - return data.getParcelableExtra(EXTRA_RESULT) ?: throw IllegalArgumentException() + return data.getParcelableExtraCompat(EXTRA_RESULT, MediaSendActivityResult::class.java) ?: throw IllegalArgumentException() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index 5fb86b138b..93ebc52902 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -22,6 +22,8 @@ import androidx.transition.AutoTransition import androidx.transition.TransitionManager import com.google.android.material.animation.ArgbEvaluatorCompat import org.signal.core.util.BreakIteratorCompat +import org.signal.core.util.getParcelableArrayListExtraCompat +import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R @@ -89,8 +91,8 @@ class MediaSelectionActivity : WindowUtil.setNavigationBarColor(this, 0x01000000) WindowUtil.setStatusBarColor(window, Color.TRANSPARENT) - val sendType: MessageSendType = requireNotNull(intent.getParcelableExtra(MESSAGE_SEND_TYPE)) - val initialMedia: List = intent.getParcelableArrayListExtra(MEDIA) ?: listOf() + val sendType: MessageSendType = requireNotNull(intent.getParcelableExtraCompat(MESSAGE_SEND_TYPE, MessageSendType::class.java)) + val initialMedia: List = intent.getParcelableArrayListExtraCompat(MEDIA, Media::class.java) ?: listOf() val message: CharSequence? = if (shareToTextStory) null else draftText val isReply: Boolean = intent.getBooleanExtra(IS_REPLY, false) val isAddToGroupStoryFlow: Boolean = intent.getBooleanExtra(IS_ADD_TO_GROUP_STORY_FLOW, false) @@ -434,7 +436,7 @@ class MediaSelectionActivity : @JvmStatic fun editor( context: Context, - media: List, + media: List ): Intent { return buildIntent( context = context, diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionDestination.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionDestination.kt index 414603ceca..362cc2a547 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionDestination.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionDestination.kt @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.mediasend.v2 import android.os.Bundle +import org.signal.core.util.getParcelableArrayListCompat +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.recipients.RecipientId @@ -89,9 +91,9 @@ sealed class MediaSelectionDestination { return when { bundle.containsKey(WALLPAPER) -> Wallpaper bundle.containsKey(AVATAR) -> Avatar - bundle.containsKey(RECIPIENT) -> SingleRecipient(requireNotNull(bundle.getParcelable(RECIPIENT))) - bundle.containsKey(STORY) -> SingleStory(requireNotNull(bundle.getParcelable(STORY))) - bundle.containsKey(RECIPIENT_LIST) -> MultipleRecipients.fromParcel(requireNotNull(bundle.getParcelableArrayList(RECIPIENT_LIST))) + bundle.containsKey(RECIPIENT) -> SingleRecipient(requireNotNull(bundle.getParcelableCompat(RECIPIENT, RecipientId::class.java))) + bundle.containsKey(STORY) -> SingleStory(requireNotNull(bundle.getParcelableCompat(STORY, RecipientId::class.java))) + bundle.containsKey(RECIPIENT_LIST) -> MultipleRecipients.fromParcel(requireNotNull(bundle.getParcelableArrayListCompat(RECIPIENT_LIST, ContactSearchKey.RecipientSearchKey::class.java))) else -> ChooseAfterMediaSelection } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt index fa92da803b..f651d60d6c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt @@ -153,7 +153,9 @@ class MediaSelectionRepository(context: Context) { }.map { media -> Stories.MediaTransform.clipMediaToStoryDuration(media) }.flatten() - } else emptyList() + } else { + emptyList() + } uploadRepository.applyMediaUpdates(oldToNewMediaMap, singleRecipient) uploadRepository.updateCaptions(updatedMedia) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt index 6ca26a2a21..9e28b0f30b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt @@ -19,6 +19,8 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.Subject import org.signal.core.util.BreakIteratorCompat +import org.signal.core.util.getParcelableArrayListCompat +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.components.mention.MentionAnnotation import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.MessageSendType @@ -408,16 +410,16 @@ class MediaSelectionViewModel( } fun onRestoreState(savedInstanceState: Bundle) { - val selection: List = savedInstanceState.getParcelableArrayList(STATE_SELECTION) ?: emptyList() - val focused: Media? = savedInstanceState.getParcelable(STATE_FOCUSED) + val selection: List = savedInstanceState.getParcelableArrayListCompat(STATE_SELECTION, Media::class.java) ?: emptyList() + val focused: Media? = savedInstanceState.getParcelableCompat(STATE_FOCUSED, Media::class.java) val quality: SentMediaQuality = SentMediaQuality.fromCode(savedInstanceState.getInt(STATE_QUALITY)) val message: CharSequence? = savedInstanceState.getCharSequence(STATE_MESSAGE) val viewOnce: MediaSelectionState.ViewOnceToggleState = MediaSelectionState.ViewOnceToggleState.fromCode(savedInstanceState.getInt(STATE_VIEW_ONCE)) val touchEnabled: Boolean = savedInstanceState.getBoolean(STATE_TOUCH_ENABLED) val sent: Boolean = savedInstanceState.getBoolean(STATE_SENT) - val cameraFirstCapture: Media? = savedInstanceState.getParcelable(STATE_CAMERA_FIRST_CAPTURE) + val cameraFirstCapture: Media? = savedInstanceState.getParcelableCompat(STATE_CAMERA_FIRST_CAPTURE, Media::class.java) - val editorStates: List = savedInstanceState.getParcelableArrayList(STATE_EDITORS) ?: emptyList() + val editorStates: List = savedInstanceState.getParcelableArrayListCompat(STATE_EDITORS, Bundle::class.java) ?: emptyList() val editorStateMap = editorStates.associate { it.toAssociation() } selectedMediaSubject.onNext(selection) @@ -438,7 +440,7 @@ class MediaSelectionViewModel( } private fun Bundle.toAssociation(): Pair { - val key: Uri = requireNotNull(getParcelable(BUNDLE_URI)) + val key: Uri = requireNotNull(getParcelableCompat(BUNDLE_URI, Uri::class.java)) val value: Any = if (getBoolean(BUNDLE_IS_IMAGE)) { ImageEditorFragment.Data(this) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryViewModel.kt index d8b5ff48f9..28f908a22a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryViewModel.kt @@ -34,7 +34,8 @@ class MediaGalleryViewModel(bucketId: String?, bucketTitle: String?, private val repository.getFolders { folders -> store.update { state -> state.copy( - bucketId = bucketId, bucketTitle = bucketTitle, + bucketId = bucketId, + bucketTitle = bucketTitle, items = folders.map { MediaGallerySelectableItem.FolderModel(it) } @@ -45,7 +46,8 @@ class MediaGalleryViewModel(bucketId: String?, bucketTitle: String?, private val repository.getMedia(bucketId) { media -> store.update { state -> state.copy( - bucketId = bucketId, bucketTitle = bucketTitle, + bucketId = bucketId, + bucketTitle = bucketTitle, items = media.map { MediaGallerySelectableItem.FileModel(it, false, 0) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gif/MediaReviewGifPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gif/MediaReviewGifPageFragment.kt index 28470095fc..2aece3bfa0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gif/MediaReviewGifPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gif/MediaReviewGifPageFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.MediaSendGifFragment import org.thoughtcrime.securesms.mediasend.v2.HudCommand @@ -49,7 +50,7 @@ class MediaReviewGifPageFragment : Fragment(R.layout.fragment_container) { } } - private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelable(ARG_URI)) + private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelableCompat(ARG_URI, Uri::class.java)) companion object { private const val ARG_URI = "arg.uri" diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/images/MediaReviewImagePageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/images/MediaReviewImagePageFragment.kt index 4032f9c897..f7c56ad59c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/images/MediaReviewImagePageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/images/MediaReviewImagePageFragment.kt @@ -6,6 +6,7 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import io.reactivex.rxjava3.disposables.Disposable +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.v2.HudCommand import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel @@ -97,7 +98,7 @@ class MediaReviewImagePageFragment : Fragment(R.layout.fragment_container), Imag } } - private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelable(ARG_URI)) + private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelableCompat(ARG_URI, Uri::class.java)) override fun onTouchEventsNeeded(needed: Boolean) { if (isResumed) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt index 092a00a399..d4809b4399 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt @@ -214,7 +214,6 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a if (!recipient.isPushV2Group) { annotations } else { - val validRecipientIds: Set = recipient.participantIds .map { id -> MentionAnnotation.idToMentionAnnotationValue(id) } .toSet() diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index 74ba0be737..a8a85a3344 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -85,7 +85,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul private lateinit var progressWrapper: TouchInterceptingFrameLayout private val navigator = MediaSelectionNavigator( - toGallery = R.id.action_mediaReviewFragment_to_mediaGalleryFragment, + toGallery = R.id.action_mediaReviewFragment_to_mediaGalleryFragment ) private var animatorSet: AnimatorSet? = null @@ -502,24 +502,22 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul } private fun computeSendButtonAnimators(state: MediaSelectionState): List { - val slideIn = listOf( - MediaReviewAnimatorController.getSlideInAnimator(sendButton), + MediaReviewAnimatorController.getSlideInAnimator(sendButton) ) return slideIn + if (state.isTouchEnabled) { listOf( - MediaReviewAnimatorController.getFadeInAnimator(sendButton, state.canSend), + MediaReviewAnimatorController.getFadeInAnimator(sendButton, state.canSend) ) } else { listOf( - MediaReviewAnimatorController.getFadeOutAnimator(sendButton, state.canSend), + MediaReviewAnimatorController.getFadeOutAnimator(sendButton, state.canSend) ) } } private fun computeSaveButtonAnimators(state: MediaSelectionState): List { - val slideIn = listOf( MediaReviewAnimatorController.getSlideInAnimator(saveButton) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt index 926153d19c..79ba7a32b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseGroupStoryBottomSheet.kt @@ -13,6 +13,7 @@ import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.setFragmentResult import androidx.recyclerview.widget.RecyclerView import org.signal.core.util.DimensionUnit +import org.signal.core.util.getParcelableArrayListCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter @@ -67,7 +68,7 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment( selectionLimits = FeatureFlags.shareSelectionLimit(), displayCheckBox = true, displaySmsTag = ContactSearchAdapter.DisplaySmsTag.DEFAULT, - displayPhoneNumber = ContactSearchAdapter.DisplayPhoneNumber.NEVER, + displaySecondaryInformation = ContactSearchAdapter.DisplaySecondaryInformation.NEVER, mapStateToConfiguration = { state -> ContactSearchConfiguration.build { query = state.query @@ -159,7 +160,7 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment( object ResultContract { fun getRecipientIds(bundle: Bundle): List { - return bundle.getParcelableArrayList(RESULT_SET)!! + return bundle.getParcelableArrayListCompat(RESULT_SET, RecipientId::class.java)!! } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseStoryTypeBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseStoryTypeBottomSheet.kt index cd6e360eef..fab8e9f3b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseStoryTypeBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/ChooseStoryTypeBottomSheet.kt @@ -24,7 +24,8 @@ class ChooseStoryTypeBottomSheet : DSLSettingsBottomSheetFragment( textPref( title = DSLSettingsText.from( stringId = R.string.ChooseStoryTypeBottomSheet__choose_your_story_type, - DSLSettingsText.CenterModifier, DSLSettingsText.TitleMediumModifier + DSLSettingsText.CenterModifier, + DSLSettingsText.TitleMediumModifier ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt index 879e21b49e..cc25974559 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt @@ -11,6 +11,7 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.fragment.app.FragmentManager import com.bumptech.glide.Glide import kotlinx.parcelize.Parcelize +import org.signal.core.util.getParcelableArrayListExtraCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey @@ -34,7 +35,7 @@ class StoriesMultiselectForwardActivity : MultiselectForwardActivity() { val preview1View: ImageView = findViewById(R.id.preview_media_1) val preview2View: ImageView = findViewById(R.id.preview_media_2) - val previewMedia: List = intent.getParcelableArrayListExtra(PREVIEW_MEDIA)!! + val previewMedia: List = intent.getParcelableArrayListExtraCompat(PREVIEW_MEDIA, Uri::class.java)!! preview1View.visible = previewMedia.isNotEmpty() preview2View.visible = previewMedia.size > 1 diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryBackgroundColors.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryBackgroundColors.kt index a810393cb2..ed801ec441 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryBackgroundColors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryBackgroundColors.kt @@ -29,10 +29,16 @@ object TextStoryBackgroundColors { id = ChatColors.Id.NotSet, linearGradient = ChatColors.LinearGradient( colors = intArrayOf( - 0xFF19A9FA.toInt(), 0xFF7097D7.toInt(), 0xFFD1998D.toInt(), 0xFFFFC369.toInt() + 0xFF19A9FA.toInt(), + 0xFF7097D7.toInt(), + 0xFFD1998D.toInt(), + 0xFFFFC369.toInt() ), positions = floatArrayOf( - 0f, 0.33f, 0.66f, 1f + 0f, + 0.33f, + 0.66f, + 1f ), degrees = 180f ) @@ -41,10 +47,16 @@ object TextStoryBackgroundColors { id = ChatColors.Id.NotSet, linearGradient = ChatColors.LinearGradient( colors = intArrayOf( - 0xFF4437D8.toInt(), 0xFF6B70DE.toInt(), 0xFFB774E0.toInt(), 0xFFFF8E8E.toInt() + 0xFF4437D8.toInt(), + 0xFF6B70DE.toInt(), + 0xFFB774E0.toInt(), + 0xFFFF8E8E.toInt() ), positions = floatArrayOf( - 0f, 0.33f, 0.66f, 1f + 0f, + 0.33f, + 0.66f, + 1f ), degrees = 180f ) @@ -53,10 +65,16 @@ object TextStoryBackgroundColors { id = ChatColors.Id.NotSet, linearGradient = ChatColors.LinearGradient( colors = intArrayOf( - 0xFF004044.toInt(), 0xFF2C5F45.toInt(), 0xFF648E52.toInt(), 0xFF93B864.toInt() + 0xFF004044.toInt(), + 0xFF2C5F45.toInt(), + 0xFF648E52.toInt(), + 0xFF93B864.toInt() ), positions = floatArrayOf( - 0f, 0.33f, 0.66f, 1f + 0f, + 0.33f, + 0.66f, + 1f ), degrees = 180f ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationState.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationState.kt index 02db1646c6..13ea17f1be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationState.kt @@ -21,7 +21,7 @@ data class TextStoryPostCreationState( val textFont: TextFont = TextFont.REGULAR, @IntRange(from = 0, to = 100) val textScale: Int = 50, val backgroundColor: ChatColors = TextStoryBackgroundColors.getInitialBackgroundColor(), - val linkPreviewUri: String? = null, + val linkPreviewUri: String? = null ) : Parcelable { @ColorInt diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt index 0079155c55..3054270978 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt @@ -15,6 +15,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.BehaviorSubject import io.reactivex.rxjava3.subjects.Subject +import org.signal.core.util.getParcelableCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.dependencies.ApplicationDependencies @@ -67,7 +68,7 @@ class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRe fun restoreFromInstanceState(inState: Bundle) { if (inState.containsKey(TEXT_STORY_INSTANCE_STATE)) { - val state: TextStoryPostCreationState = inState.getParcelable(TEXT_STORY_INSTANCE_STATE)!! + val state: TextStoryPostCreationState = inState.getParcelableCompat(TEXT_STORY_INSTANCE_STATE, TextStoryPostCreationState::class.java)!! textFontSubject.onNext(store.state.textFont) store.update { state } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt index 3afb821ff8..5a462c2498 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/videos/MediaReviewVideoPageFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.mediasend.VideoEditorFragment import org.thoughtcrime.securesms.mediasend.v2.HudCommand @@ -99,7 +100,7 @@ class MediaReviewVideoPageFragment : Fragment(R.layout.fragment_container), Vide } } - private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelable(ARG_URI)) + private fun requireUri(): Uri = requireNotNull(requireArguments().getParcelableCompat(ARG_URI, Uri::class.java)) private fun requireMaxCompressedVideoSize(): Long = sharedViewModel.getMediaConstraints().getCompressedVideoMaxSize(requireContext()).toLong() private fun requireMaxAttachmentSize(): Long = sharedViewModel.getMediaConstraints().getVideoMaxSize(requireContext()).toLong() private fun requireIsVideoGif(): Boolean = requireNotNull(requireArguments().getBoolean(ARG_IS_VIDEO_GIF)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneText.kt b/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneText.kt index 5a3a40e6bb..c0727a0e74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneText.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/MegaphoneText.kt @@ -7,7 +7,8 @@ import androidx.annotation.StringRes * Allows setting of megaphone text by string resource or string literal. */ data class MegaphoneText(@StringRes private val stringRes: Int = 0, private val string: String? = null) { - @get:JvmName("hasText") val hasText = stringRes != 0 || string != null + @get:JvmName("hasText") + val hasText = stringRes != 0 || string != null fun resolve(context: Context): String? { return if (stringRes != 0) context.getString(stringRes) else string diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/OnboardingMegaphoneView.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/OnboardingMegaphoneView.java index 1aec38830a..80ba1064e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/OnboardingMegaphoneView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/OnboardingMegaphoneView.java @@ -7,19 +7,19 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; +import androidx.annotation.ColorRes; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.StringRes; -import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.InviteActivity; import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.databinding.OnboardingMegaphoneCardBinding; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity; import org.thoughtcrime.securesms.stories.settings.story.StoriesPrivacySettingsRepository; @@ -56,18 +56,16 @@ private void initialize(@NonNull Context context) { } public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController listener) { - this.cardList.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); this.cardList.setAdapter(new CardAdapter(getContext(), listener)); } private static class CardAdapter extends RecyclerView.Adapter implements ActionClickListener { - // MOLLY: New group card is replaced by Stories, that is opt-in + // MOLLY: New group card is replaced by stories, that is opt-in private static final int TYPE_STORIES = 0; private static final int TYPE_INVITE = 1; - //private static final int TYPE_SMS = 2; - private static final int TYPE_APPEARANCE = 3; - private static final int TYPE_ADD_PHOTO = 4; + private static final int TYPE_APPEARANCE = 2; + private static final int TYPE_ADD_PHOTO = 3; private final Context context; private final MegaphoneActionController controller; @@ -76,7 +74,7 @@ private static class CardAdapter extends RecyclerView.Adapter im CardAdapter(@NonNull Context context, @NonNull MegaphoneActionController controller) { this.context = context; this.controller = controller; - this.data = buildData(context); + this.data = buildData(); if (data.isEmpty()) { Log.i(TAG, "Nothing to show (constructor)! Considering megaphone completed."); @@ -98,7 +96,7 @@ public long getItemId(int position) { @Override public @NonNull CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.onboarding_megaphone_list_item, parent, false); + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.onboarding_megaphone_card, parent, false); switch (viewType) { case TYPE_STORIES: return new StoriesViewHolder(view); case TYPE_INVITE: return new InviteCardViewHolder(view); @@ -121,7 +119,7 @@ public int getItemCount() { @Override public void onClick() { data.clear(); - data.addAll(buildData(context)); + data.addAll(buildData()); if (data.isEmpty()) { Log.i(TAG, "Nothing to show! Considering megaphone completed."); controller.onMegaphoneCompleted(Megaphones.Event.ONBOARDING); @@ -129,7 +127,7 @@ public void onClick() { notifyDataSetChanged(); } - private static List buildData(@NonNull Context context) { + private static List buildData() { List data = new ArrayList<>(); if (SignalStore.onboarding().shouldShowStories() && SignalStore.storyValues().isFeatureDisabled()) { @@ -157,25 +155,22 @@ private interface ActionClickListener { } private static abstract class CardViewHolder extends RecyclerView.ViewHolder { - private final ImageView image; - private final TextView actionButton; - private final View closeButton; + private final OnboardingMegaphoneCardBinding binding; public CardViewHolder(@NonNull View itemView) { super(itemView); - this.image = itemView.findViewById(R.id.onboarding_megaphone_item_image); - this.actionButton = itemView.findViewById(R.id.onboarding_megaphone_item_button); - this.closeButton = itemView.findViewById(R.id.onboarding_megaphone_item_close); + binding = OnboardingMegaphoneCardBinding.bind(itemView); } public void bind(@NonNull ActionClickListener listener, @NonNull MegaphoneActionController controller) { - image.setImageResource(getImageRes()); - actionButton.setText(getButtonStringRes()); - actionButton.setOnClickListener(v -> { + binding.getRoot().setCardBackgroundColor(ContextCompat.getColor(binding.getRoot().getContext(), getBackgroundColor())); + binding.icon.setImageResource(getImageRes()); + binding.text.setText(getButtonStringRes()); + binding.getRoot().setOnClickListener(v -> { onActionClicked(controller); listener.onClick(); }); - closeButton.setOnClickListener(v -> { + binding.close.setOnClickListener(v -> { onCloseClicked(); listener.onClick(); }); @@ -183,6 +178,7 @@ public void bind(@NonNull ActionClickListener listener, @NonNull MegaphoneAction abstract @StringRes int getButtonStringRes(); abstract @DrawableRes int getImageRes(); + abstract @ColorRes int getBackgroundColor(); abstract void onActionClicked(@NonNull MegaphoneActionController controller); abstract void onCloseClicked(); } @@ -205,7 +201,12 @@ int getButtonStringRes() { @Override int getImageRes() { - return R.drawable.ic_megaphone_start_group; + return R.drawable.symbol_group_24; + } + + @Override + int getBackgroundColor() { + return R.color.onboarding_background_1; } @Override @@ -235,7 +236,12 @@ int getButtonStringRes() { @Override int getImageRes() { - return R.drawable.ic_megaphone_invite_friends; + return R.drawable.symbol_invite_24; + } + + @Override + int getBackgroundColor() { + return R.color.onboarding_background_2; } @Override @@ -257,12 +263,17 @@ public AppearanceCardViewHolder(@NonNull View itemView) { @Override int getButtonStringRes() { - return R.string.Megaphones_appearance; + return R.string.Megaphones_chat_colors; } @Override int getImageRes() { - return R.drawable.ic_signal_appearance; + return R.drawable.ic_color_24; + } + + @Override + int getBackgroundColor() { + return R.color.onboarding_background_3; } @Override @@ -285,12 +296,17 @@ public AddPhotoCardViewHolder(@NonNull View itemView) { @Override int getButtonStringRes() { - return R.string.Megaphones_add_photo; + return R.string.Megaphones_add_a_profile_photo; } @Override int getImageRes() { - return R.drawable.ic_signal_add_photo; + return R.drawable.symbol_person_circle_24; + } + + @Override + int getBackgroundColor() { + return R.color.onboarding_background_4; } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java b/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java index b7e89e5521..1367328b52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java @@ -33,8 +33,6 @@ public class BackgroundMessageRetriever { private static final Semaphore ACTIVE_LOCK = new Semaphore(2); - private static final long NORMAL_TIMEOUT = TimeUnit.SECONDS.toMillis(10); - public static final long DO_NOT_SHOW_IN_FOREGROUND = DelayedNotificationController.DO_NOT_SHOW; /** @@ -108,7 +106,7 @@ private boolean executeBackgroundRetrieval(@NonNull Context context, long startT Log.i(TAG, "Attempting strategy: " + strategy.toString() + logSuffix(startTime)); - if (strategy.execute(NORMAL_TIMEOUT)) { + if (strategy.execute()) { Log.i(TAG, "Strategy succeeded: " + strategy.toString() + logSuffix(startTime)); success = true; break; diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java deleted file mode 100644 index 86d679b132..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.java +++ /dev/null @@ -1,324 +0,0 @@ -package org.thoughtcrime.securesms.messages; - -import android.app.Application; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.os.IBinder; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; - -import org.signal.core.util.ThreadUtil; -import org.signal.core.util.concurrent.SignalExecutors; -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil; -import org.thoughtcrime.securesms.jobs.PushDecryptDrainedJob; -import org.thoughtcrime.securesms.jobs.UnableToStartException; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.messages.IncomingMessageProcessor.Processor; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; -import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.util.AppForegroundObserver; -import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.signalservice.api.SignalWebSocket; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * The application-level manager of our websocket connection. - *

- * This class is responsible for opening/closing the websocket based on the app's state and observing new inbound messages received on the websocket. - */ -public class IncomingMessageObserver { - - private static final String TAG = Log.tag(IncomingMessageObserver.class); - - public static final int FOREGROUND_ID = 313399; - private static final long REQUEST_TIMEOUT_MINUTES = 1; - private static final long OLD_REQUEST_WINDOW_MS = TimeUnit.MINUTES.toMillis(5); - - private final Application context; - private final SignalServiceNetworkAccess networkAccess; - private final List decryptionDrainedListeners; - private final BroadcastReceiver connectionReceiver; - private final Map keepAliveTokens; - - private boolean appVisible; - private boolean isForegroundService; - - private volatile boolean networkDrained; - private volatile boolean decryptionDrained; - private volatile boolean terminated; - - public IncomingMessageObserver(@NonNull Application context) { - this.context = context; - this.networkAccess = ApplicationDependencies.getSignalServiceNetworkAccess(); - this.decryptionDrainedListeners = new CopyOnWriteArrayList<>(); - this.keepAliveTokens = new HashMap<>(); - - new MessageRetrievalThread().start(); - - // MOLLY: Foreground service startup is handled inside the connection loop - - ApplicationDependencies.getAppForegroundObserver().addListener(new AppForegroundObserver.Listener() { - @Override - public void onForeground() { - onAppForegrounded(); - } - - @Override - public void onBackground() { - onAppBackgrounded(); - } - }); - - connectionReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (IncomingMessageObserver.this) { - if (!NetworkConstraint.isMet(context)) { - Log.w(TAG, "Lost network connection. Shutting down our websocket connections and resetting the drained state."); - networkDrained = false; - decryptionDrained = false; - disconnect(); - } - IncomingMessageObserver.this.notifyAll(); - } - } - }; - - context.registerReceiver(connectionReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - } - - public void stopForegroundService() { - context.stopService(new Intent(context, ForegroundService.class)); - } - - public synchronized void notifyRegistrationChanged() { - notifyAll(); - } - - public synchronized void addDecryptionDrainedListener(@NonNull Runnable listener) { - decryptionDrainedListeners.add(listener); - if (decryptionDrained) { - listener.run(); - } - } - - public boolean isDecryptionDrained() { - return decryptionDrained; - } - - public void notifyDecryptionsDrained() { - List listenersToTrigger = new ArrayList<>(decryptionDrainedListeners.size()); - - synchronized (this) { - if (networkDrained && !decryptionDrained) { - Log.i(TAG, "Decryptions newly drained."); - decryptionDrained = true; - listenersToTrigger.addAll(decryptionDrainedListeners); - } - } - - for (Runnable listener : listenersToTrigger) { - listener.run(); - } - } - - private synchronized void onAppForegrounded() { - appVisible = true; - notifyAll(); - } - - private synchronized void onAppBackgrounded() { - appVisible = false; - notifyAll(); - } - - private synchronized boolean isConnectionNecessary() { - if (KeyCachingService.isLocked()) { - Log.i(TAG, "Don't connect anymore. App is locked."); - return false; - } - - boolean registered = SignalStore.account().isRegistered(); - boolean fcmEnabled = SignalStore.account().isFcmEnabled(); - boolean hasNetwork = NetworkConstraint.isMet(context); - boolean hasProxy = ApplicationDependencies.getNetworkManager().isProxyEnabled(); - boolean forceWebsocket = SignalStore.internalValues().isWebsocketModeForced(); - long oldRequest = System.currentTimeMillis() - OLD_REQUEST_WINDOW_MS; - - if ((!fcmEnabled || forceWebsocket) && registered && !isForegroundService) { - try { - ForegroundServiceUtil.startWhenCapable(context, new Intent(context, ForegroundService.class)); - isForegroundService = true; - } catch (UnableToStartException e) { - Log.w(TAG, "Unable to start foreground service for websocket!", e); - } - } - - boolean removedRequests = keepAliveTokens.entrySet().removeIf(e -> e.getValue() < oldRequest); - if (removedRequests) { - Log.d(TAG, "Removed old keep web socket open requests."); - } - - Log.d(TAG, String.format("Network: %s, Foreground: %s, FCM: %s, Stay open requests: [%s], Censored: %s, Registered: %s, Proxy: %s, Force websocket: %s", - hasNetwork, appVisible, fcmEnabled, Util.join(keepAliveTokens.entrySet(), ","), networkAccess.isCensored(), registered, hasProxy, forceWebsocket)); - - return registered && - (appVisible || !fcmEnabled || forceWebsocket || Util.hasItems(keepAliveTokens)) && - hasNetwork; - } - - private synchronized void waitForConnectionNecessary() { - try { - while (!isConnectionNecessary()) wait(); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - } - - public void terminateAsync() { - context.unregisterReceiver(connectionReceiver); - - SignalExecutors.BOUNDED.execute(() -> { - Log.w(TAG, "Beginning termination."); - terminated = true; - disconnect(); - }); - } - - private void disconnect() { - ApplicationDependencies.getSignalWebSocket().disconnect(); - } - - public synchronized void registerKeepAliveToken(String key) { - keepAliveTokens.put(key, System.currentTimeMillis()); - notifyAll(); - } - - public synchronized void removeKeepAliveToken(String key) { - keepAliveTokens.remove(key); - notifyAll(); - } - - private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler { - - MessageRetrievalThread() { - super("MessageRetrievalService"); - Log.i(TAG, "Initializing! (" + this.hashCode() + ")"); - setUncaughtExceptionHandler(this); - } - - @Override - public void run() { - int attempts = 0; - - while (!terminated) { - Log.i(TAG, "Waiting for websocket state change...."); - if (attempts > 1) { - long backoff = BackoffUtil.exponentialBackoff(attempts, TimeUnit.SECONDS.toMillis(30)); - Log.w(TAG, "Too many failed connection attempts, attempts: " + attempts + " backing off: " + backoff); - ThreadUtil.sleep(backoff); - } - waitForConnectionNecessary(); - - Log.i(TAG, "Making websocket connection...."); - SignalWebSocket signalWebSocket = ApplicationDependencies.getSignalWebSocket(); - signalWebSocket.connect(); - - try { - while (isConnectionNecessary()) { - try { - Log.d(TAG, "Reading message..."); - Optional result = signalWebSocket.readOrEmpty(TimeUnit.MINUTES.toMillis(REQUEST_TIMEOUT_MINUTES), envelope -> { - Log.i(TAG, "Retrieved envelope! " + envelope.getTimestamp()); - try (Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) { - processor.processEnvelope(envelope); - } - }); - attempts = 0; - - if (!result.isPresent() && !networkDrained) { - Log.i(TAG, "Network was newly-drained. Enqueuing a job to listen for decryption draining."); - networkDrained = true; - ApplicationDependencies.getJobManager().add(new PushDecryptDrainedJob()); - } - } catch (WebSocketUnavailableException e) { - Log.i(TAG, "Pipe unexpectedly unavailable, connecting"); - signalWebSocket.connect(); - } catch (TimeoutException e) { - Log.w(TAG, "Application level read timeout..."); - attempts = 0; - } - } - } catch (Throwable e) { - attempts++; - Log.w(TAG, e); - } finally { - Log.w(TAG, "Shutting down pipe..."); - disconnect(); - } - - Log.i(TAG, "Looping..."); - } - - Log.w(TAG, "Terminated! (" + this.hashCode() + ")"); - } - - @Override - public void uncaughtException(Thread t, Throwable e) { - Log.w(TAG, "*** Uncaught exception!"); - Log.w(TAG, e); - } - } - - public static class ForegroundService extends Service { - - @Override - public @Nullable IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); - - if (intent == null) { - stopForeground(true); - return Service.START_STICKY; - } - - NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), NotificationChannels.getInstance().BACKGROUND); - builder.setContentTitle(getApplicationContext().getString(R.string.app_name)); - builder.setContentText(getApplicationContext().getString(R.string.MessageRetrievalService_ready_to_receive_messages)); - builder.setPriority(NotificationCompat.PRIORITY_MIN); - builder.setCategory(NotificationCompat.CATEGORY_SERVICE); - builder.setWhen(0); - builder.setSmallIcon(R.drawable.ic_molly_background_connection); - startForeground(FOREGROUND_ID, builder.build()); - - return Service.START_STICKY; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt new file mode 100644 index 0000000000..5fd43ec3e4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -0,0 +1,522 @@ +package org.thoughtcrime.securesms.messages + +import android.annotation.SuppressLint +import android.app.Application +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.os.IBinder +import androidx.annotation.VisibleForTesting +import androidx.core.app.NotificationCompat +import org.signal.core.util.ThreadUtil +import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.database.MessageTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.JobTracker +import org.thoughtcrime.securesms.jobmanager.JobTracker.JobListener +import org.thoughtcrime.securesms.jobmanager.impl.BackoffUtil +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil.startWhenCapable +import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob +import org.thoughtcrime.securesms.jobs.PushProcessMessageJob +import org.thoughtcrime.securesms.jobs.UnableToStartException +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.service.KeyCachingService +import org.thoughtcrime.securesms.util.AppForegroundObserver +import org.thoughtcrime.securesms.util.Util +import org.whispersystems.signalservice.api.messages.SignalServiceContent +import org.whispersystems.signalservice.api.messages.SignalServiceMetadata +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.api.util.UuidUtil +import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState +import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException +import org.whispersystems.signalservice.internal.push.SignalServiceProtos +import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer +import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer +import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto +import java.util.* +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import java.util.concurrent.locks.Condition +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * The application-level manager of our websocket connection. + * + * + * This class is responsible for opening/closing the websocket based on the app's state and observing new inbound messages received on the websocket. + */ +class IncomingMessageObserver(private val context: Application) { + + companion object { + private val TAG = Log.tag(IncomingMessageObserver::class.java) + private val WEBSOCKET_READ_TIMEOUT = TimeUnit.MINUTES.toMillis(1) + private val KEEP_ALIVE_TOKEN_MAX_AGE = TimeUnit.MINUTES.toMillis(5) + private val MAX_BACKGROUND_TIME = TimeUnit.MINUTES.toMillis(5) + + const val FOREGROUND_ID = 313399 + } + + private val decryptionDrainedListeners: MutableList = CopyOnWriteArrayList() + private val keepAliveTokens: MutableMap = mutableMapOf() + private val connectionReceiver: BroadcastReceiver + + private val lock: ReentrantLock = ReentrantLock() + private val condition: Condition = lock.newCondition() + + private var appVisible = false + private var isForegroundService = false + private var lastInteractionTime: Long = System.currentTimeMillis() + + @Volatile + private var terminated = false + + @Volatile + var decryptionDrained = false + private set + + init { + MessageRetrievalThread().start() + + // MOLLY: Foreground service startup is handled inside the connection loop + + ApplicationDependencies.getAppForegroundObserver().addListener(object : AppForegroundObserver.Listener { + override fun onForeground() { + onAppForegrounded() + } + + override fun onBackground() { + onAppBackgrounded() + } + }) + + connectionReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + lock.withLock { + if (!NetworkConstraint.isMet(context)) { + Log.w(TAG, "Lost network connection. Shutting down our websocket connections and resetting the drained state.") + decryptionDrained = false + disconnect() + } + condition.signalAll() + } + } + } + + context.registerReceiver(connectionReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) + } + + fun stopForegroundService() { + context.stopService(Intent(context, ForegroundService::class.java)) + } + + fun notifyRegistrationChanged() { + lock.withLock { + condition.signalAll() + } + } + + fun addDecryptionDrainedListener(listener: Runnable) { + decryptionDrainedListeners.add(listener) + if (decryptionDrained) { + listener.run() + } + } + + fun removeDecryptionDrainedListener(listener: Runnable) { + decryptionDrainedListeners.remove(listener) + } + + fun notifyDecryptionsDrained() { + if (ApplicationDependencies.getJobManager().isQueueEmpty(PushDecryptMessageJob.QUEUE)) { + Log.i(TAG, "Queue was empty when notified. Signaling change.") + lock.withLock { + condition.signalAll() + } + } else { + Log.i(TAG, "Queue still had items when notified. Registering listener to signal change.") + ApplicationDependencies.getJobManager().addListener( + { it.parameters.queue == PushDecryptMessageJob.QUEUE }, + DecryptionDrainedQueueListener() + ) + } + } + + private fun onAppForegrounded() { + lock.withLock { + appVisible = true + BackgroundService.start(context) + condition.signalAll() + } + } + + private fun onAppBackgrounded() { + lock.withLock { + appVisible = false + lastInteractionTime = System.currentTimeMillis() + condition.signalAll() + } + } + + private fun isConnectionNecessary(): Boolean { + lock.withLock { + if (KeyCachingService.isLocked()) { + Log.i(TAG, "Don't connect anymore. App is locked.") + return false + } + + val registered = SignalStore.account().isRegistered + val fcmEnabled = SignalStore.account().fcmEnabled + val hasNetwork = NetworkConstraint.isMet(context) + val hasProxy = ApplicationDependencies.getNetworkManager().isProxyEnabled + val forceWebsocket = SignalStore.internalValues().isWebsocketModeForced + val keepAliveCutoffTime = System.currentTimeMillis() - KEEP_ALIVE_TOKEN_MAX_AGE + val timeIdle = if (appVisible) 0 else System.currentTimeMillis() - lastInteractionTime + val removedRequests = keepAliveTokens.entries.removeIf { (_, createTime) -> createTime < keepAliveCutoffTime } + val decryptQueueEmpty = ApplicationDependencies.getJobManager().isQueueEmpty(PushDecryptMessageJob.QUEUE) + + if ((!fcmEnabled || forceWebsocket) && registered && !isForegroundService) { + try { + startWhenCapable(context, Intent(context, ForegroundService::class.java)) + isForegroundService = true + } catch (e: UnableToStartException) { + Log.w(TAG, "Unable to start foreground service for websocket!", e) + } + } + + if (removedRequests) { + Log.d(TAG, "Removed old keep web socket open requests.") + } + + val lastInteractionString = if (appVisible) "N/A" else timeIdle.toString() + " ms (" + (if (timeIdle < MAX_BACKGROUND_TIME) "within limit" else "over limit") + ")" + val conclusion = registered && + (appVisible || timeIdle < MAX_BACKGROUND_TIME || !fcmEnabled || Util.hasItems(keepAliveTokens)) && + hasNetwork && + decryptQueueEmpty + + val needsConnectionString = if (conclusion) "Needs Connection" else "Does Not Need Connection" + + Log.d(TAG, "[$needsConnectionString] Network: $hasNetwork, Foreground: $appVisible, Time Since Last Interaction: $lastInteractionString, FCM: $fcmEnabled, Stay open requests: [${keepAliveTokens.entries}], Registered: $registered, Proxy: $hasProxy, Force websocket: $forceWebsocket, Decrypt Queue Empty: $decryptQueueEmpty") + return conclusion + } + } + + private fun waitForConnectionNecessary() { + lock.withLock { + try { + while (!isConnectionNecessary()) { + condition.await() + } + } catch (e: InterruptedException) { + throw AssertionError(e) + } + } + } + + fun terminateAsync() { + context.unregisterReceiver(connectionReceiver) + + SignalExecutors.BOUNDED.execute { + Log.w(TAG, "Beginning termination.") + terminated = true + disconnect() + } + } + + private fun disconnect() { + ApplicationDependencies.getSignalWebSocket().disconnect() + } + + fun registerKeepAliveToken(key: String) { + lock.withLock { + keepAliveTokens[key] = System.currentTimeMillis() + lastInteractionTime = System.currentTimeMillis() + condition.signalAll() + } + } + + fun removeKeepAliveToken(key: String) { + lock.withLock { + keepAliveTokens.remove(key) + lastInteractionTime = System.currentTimeMillis() + condition.signalAll() + } + } + + @VisibleForTesting + fun processEnvelope(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) { + when (envelope.type.number) { + SignalServiceProtos.Envelope.Type.RECEIPT_VALUE -> processReceipt(envelope) + SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, + SignalServiceProtos.Envelope.Type.CIPHERTEXT_VALUE, + SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE, + SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE -> processMessage(envelope, serverDeliveredTimestamp) + else -> Log.w(TAG, "Received envelope of unknown type: " + envelope.type) + } + } + + private fun processMessage(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) { + val result = MessageDecryptor.decrypt(context, envelope, serverDeliveredTimestamp) + + when (result) { + is MessageDecryptor.Result.Success -> { + ApplicationDependencies.getJobManager().add( + PushProcessMessageJob( + result.toMessageState(), + result.toSignalServiceContent(), + null, + -1, + result.envelope.timestamp + ) + ) + } + + is MessageDecryptor.Result.Error -> { + ApplicationDependencies.getJobManager().add( + PushProcessMessageJob( + result.toMessageState(), + null, + result.errorMetadata.toExceptionMetadata(), + -1, + result.envelope.timestamp + ) + ) + } + + is MessageDecryptor.Result.Ignore -> { + // No action needed + } + + else -> { + throw AssertionError("Unexpected result! ${result.javaClass.simpleName}") + } + } + + result.followUpOperations.forEach { it.run() } + } + + private fun processReceipt(envelope: SignalServiceProtos.Envelope) { + if (!UuidUtil.isUuid(envelope.sourceUuid)) { + Log.w(TAG, "Invalid envelope source UUID!") + return + } + + val senderId = RecipientId.from(ServiceId.parseOrThrow(envelope.sourceUuid)) + + Log.i(TAG, "Received server receipt. Sender: $senderId, Device: ${envelope.sourceDevice}, Timestamp: ${envelope.timestamp}") + SignalDatabase.messages.incrementDeliveryReceiptCount(MessageTable.SyncMessageId(senderId, envelope.timestamp), System.currentTimeMillis()) + SignalDatabase.messageLog.deleteEntryForRecipient(envelope.timestamp, senderId, envelope.sourceDevice) + } + + private fun MessageDecryptor.Result.toMessageState(): MessageContentProcessor.MessageState { + return when (this) { + is MessageDecryptor.Result.DecryptionError -> MessageContentProcessor.MessageState.DECRYPTION_ERROR + is MessageDecryptor.Result.Ignore -> MessageContentProcessor.MessageState.NOOP + is MessageDecryptor.Result.InvalidVersion -> MessageContentProcessor.MessageState.INVALID_VERSION + is MessageDecryptor.Result.LegacyMessage -> MessageContentProcessor.MessageState.LEGACY_MESSAGE + is MessageDecryptor.Result.Success -> MessageContentProcessor.MessageState.DECRYPTED_OK + is MessageDecryptor.Result.UnsupportedDataMessage -> MessageContentProcessor.MessageState.UNSUPPORTED_DATA_MESSAGE + } + } + + private fun MessageDecryptor.Result.Success.toSignalServiceContent(): SignalServiceContent { + val localAddress = SignalServiceAddress(this.metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164)) + val metadata = SignalServiceMetadata( + SignalServiceAddress(this.metadata.sourceServiceId, Optional.ofNullable(this.metadata.sourceE164)), + this.metadata.sourceDeviceId, + this.envelope.timestamp, + this.envelope.serverTimestamp, + this.serverDeliveredTimestamp, + this.metadata.sealedSender, + this.envelope.serverGuid, + Optional.ofNullable(this.metadata.groupId), + this.metadata.destinationServiceId.toString() + ) + + val contentProto = SignalServiceContentProto.newBuilder() + .setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress)) + .setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(metadata)) + .setContent(content) + .build() + + return SignalServiceContent.createFromProto(contentProto)!! + } + + private fun MessageDecryptor.ErrorMetadata.toExceptionMetadata(): MessageContentProcessor.ExceptionMetadata { + return MessageContentProcessor.ExceptionMetadata( + this.sender, + this.senderDevice, + this.groupId + ) + } + + private inner class MessageRetrievalThread : Thread("MessageRetrievalService"), Thread.UncaughtExceptionHandler { + + init { + Log.i(TAG, "Initializing! (" + this.hashCode() + ")") + uncaughtExceptionHandler = this + } + + override fun run() { + var attempts = 0 + + while (!terminated) { + Log.i(TAG, "Waiting for websocket state change....") + if (attempts > 1) { + val backoff = BackoffUtil.exponentialBackoff(attempts, TimeUnit.SECONDS.toMillis(30)) + Log.w(TAG, "Too many failed connection attempts, attempts: $attempts backing off: $backoff") + ThreadUtil.sleep(backoff) + } + + waitForConnectionNecessary() + Log.i(TAG, "Making websocket connection....") + + val signalWebSocket = ApplicationDependencies.getSignalWebSocket() + val webSocketDisposable = signalWebSocket.webSocketState.subscribe { state: WebSocketConnectionState -> + Log.d(TAG, "WebSocket State: $state") + + // Any state change at all means that we are not drained + decryptionDrained = false + } + + signalWebSocket.connect() + try { + while (isConnectionNecessary()) { + try { + Log.d(TAG, "Reading message...") + + val hasMore = signalWebSocket.readMessage(WEBSOCKET_READ_TIMEOUT) { envelope, serverDeliveredTimestamp -> + Log.i(TAG, "Retrieved envelope! " + envelope.timestamp) + processEnvelope(envelope, serverDeliveredTimestamp) + true + } + + attempts = 0 + + if (!hasMore && !decryptionDrained) { + Log.i(TAG, "Decryptions newly-drained.") + decryptionDrained = true + + for (listener in decryptionDrainedListeners.toList()) { + listener.run() + } + } else if (!hasMore) { + Log.w(TAG, "Got tombstone, but we thought the network was already drained!") + } + } catch (e: WebSocketUnavailableException) { + Log.i(TAG, "Pipe unexpectedly unavailable, connecting") + signalWebSocket.connect() + } catch (e: TimeoutException) { + Log.w(TAG, "Application level read timeout...") + attempts = 0 + } + } + + if (!appVisible) { + BackgroundService.stop(context) + } + } catch (e: Throwable) { + attempts++ + Log.w(TAG, e) + } finally { + Log.w(TAG, "Shutting down pipe...") + disconnect() + webSocketDisposable.dispose() + } + Log.i(TAG, "Looping...") + } + Log.w(TAG, "Terminated! (" + this.hashCode() + ")") + } + + override fun uncaughtException(t: Thread, e: Throwable) { + Log.w(TAG, "Uncaught exception in message thread!", e) + } + } + + private inner class DecryptionDrainedQueueListener : JobListener { + @SuppressLint("WrongThread") + override fun onStateChanged(job: Job, jobState: JobTracker.JobState) { + if (jobState.isComplete) { + if (ApplicationDependencies.getJobManager().isQueueEmpty(PushDecryptMessageJob.QUEUE)) { + Log.i(TAG, "Queue is now empty. Signaling change.") + lock.withLock { + condition.signalAll() + } + ApplicationDependencies.getJobManager().removeListener(this) + } else { + Log.i(TAG, "Item finished in queue, but it's still not empty. Waiting to signal change.") + } + } + } + } + + class ForegroundService : Service() { + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) + + if (intent == null) { + stopForeground(true) + return START_STICKY + } + + val notification = NotificationCompat.Builder(applicationContext, NotificationChannels.getInstance().BACKGROUND) + .setContentTitle(applicationContext.getString(R.string.app_name)) + .setContentText(applicationContext.getString(R.string.MessageRetrievalService_ready_to_receive_messages)) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setWhen(0) + .setSmallIcon(R.drawable.ic_molly_background_connection) + .build() + + startForeground(FOREGROUND_ID, notification) + + return START_STICKY + } + } + + /** + * A service that exists just to encourage the system to keep our process alive a little longer. + */ + class BackgroundService : Service() { + override fun onBind(intent: Intent?): IBinder? = null + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d(TAG, "Background service started.") + return START_STICKY + } + + override fun onDestroy() { + Log.d(TAG, "Background service destroyed.") + } + + companion object { + fun start(context: Context) { + try { + context.startService(Intent(context, BackgroundService::class.java)) + } catch (e: Exception) { + Log.w(TAG, "Failed to start background service.", e) + } + } + + fun stop(context: Context) { + context.stopService(Intent(context, BackgroundService::class.java)) + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java deleted file mode 100644 index 40e57cb95b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java +++ /dev/null @@ -1,204 +0,0 @@ -package org.thoughtcrime.securesms.messages; - -import android.app.Application; -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; -import org.thoughtcrime.securesms.database.GroupTable; -import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.groups.GroupChangeBusyException; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.JobManager; -import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; -import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; -import org.thoughtcrime.securesms.messages.MessageDecryptionUtil.DecryptionResult; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.GroupUtil; -import org.signal.core.util.SetUtil; -import org.signal.core.util.Stopwatch; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.api.SignalSessionLock; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; - -import java.io.Closeable; -import java.io.IOException; -import java.util.concurrent.locks.ReentrantLock; - -/** - * The central entry point for all envelopes that have been retrieved. Envelopes must be processed - * here to guarantee proper ordering. - */ -public class IncomingMessageProcessor { - - private static final String TAG = Log.tag(IncomingMessageProcessor.class); - - private final Application context; - private final ReentrantLock lock; - - public IncomingMessageProcessor(@NonNull Application context) { - this.context = context; - this.lock = new ReentrantLock(); - } - - /** - * @return An instance of a Processor that will allow you to process messages in a thread safe - * way. Must be closed. - */ - public Processor acquire() { - lock.lock(); - return new Processor(context); - } - - private void release() { - lock.unlock(); - } - - public class Processor implements Closeable { - - private final Context context; - private final JobManager jobManager; - - private Processor(@NonNull Context context) { - this.context = context; - this.jobManager = ApplicationDependencies.getJobManager(); - } - - /** - * @return The id of the {@link PushDecryptMessageJob} that was scheduled to process the message, if - * one was created. Otherwise null. - */ - public @Nullable String processEnvelope(@NonNull SignalServiceEnvelope envelope) { - if (envelope.hasSourceUuid()) { - Recipient.externalPush(envelope.getSourceAddress()); - } - - if (envelope.isReceipt()) { - processReceipt(envelope); - return null; - } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isPlaintextContent()) { - return processMessage(envelope); - } else { - Log.w(TAG, "Received envelope of unknown type: " + envelope.getType()); - return null; - } - } - - private @Nullable String processMessage(@NonNull SignalServiceEnvelope envelope) { - return processMessageDeferred(envelope); - } - - private @Nullable String processMessageDeferred(@NonNull SignalServiceEnvelope envelope) { - Job job = new PushDecryptMessageJob(context, envelope); - jobManager.add(job); - return job.getId(); - } - - private @Nullable String processMessageInline(@NonNull SignalServiceEnvelope envelope) { - Log.i(TAG, "Received message " + envelope.getTimestamp() + "."); - - Stopwatch stopwatch = new Stopwatch("message"); - - if (needsToEnqueueDecryption()) { - Log.d(TAG, "Need to enqueue decryption."); - PushDecryptMessageJob job = new PushDecryptMessageJob(context, envelope); - jobManager.add(job); - return job.getId(); - } - - stopwatch.split("queue-check"); - - try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - Log.i(TAG, "Acquired lock while processing message " + envelope.getTimestamp() + "."); - - DecryptionResult result = MessageDecryptionUtil.decrypt(context, envelope); - Log.d(TAG, "Decryption finished for " + envelope.getTimestamp()); - stopwatch.split("decrypt"); - - for (Job job : result.getJobs()) { - jobManager.add(job); - } - - stopwatch.split("jobs"); - - if (needsToEnqueueProcessing(result)) { - Log.d(TAG, "Need to enqueue processing."); - jobManager.add(new PushProcessMessageJob(result.getState(), result.getContent(), result.getException(), -1, envelope.getTimestamp())); - return null; - } - - stopwatch.split("group-check"); - - try { - MessageContentProcessor processor = MessageContentProcessor.forNormalContent(context); - processor.process(result.getState(), result.getContent(), result.getException(), envelope.getTimestamp(), -1); - return null; - } catch (IOException | GroupChangeBusyException e) { - Log.w(TAG, "Exception during message processing.", e); - jobManager.add(new PushProcessMessageJob(result.getState(), result.getContent(), result.getException(), -1, envelope.getTimestamp())); - } - } finally { - stopwatch.split("process"); - stopwatch.stop(TAG); - } - - return null; - } - - private void processReceipt(@NonNull SignalServiceEnvelope envelope) { - Recipient sender = Recipient.externalPush(envelope.getSourceAddress()); - Log.i(TAG, "Received server receipt. Sender: " + sender.getId() + ", Device: " + envelope.getSourceDevice() + ", Timestamp: " + envelope.getTimestamp()); - - SignalDatabase.messages().incrementDeliveryReceiptCount(new SyncMessageId(sender.getId(), envelope.getTimestamp()), System.currentTimeMillis()); - SignalDatabase.messageLog().deleteEntryForRecipient(envelope.getTimestamp(), sender.getId(), envelope.getSourceDevice()); - } - - private boolean needsToEnqueueDecryption() { - return !jobManager.areQueuesEmpty(SetUtil.newHashSet(Job.Parameters.MIGRATION_QUEUE_KEY, PushDecryptMessageJob.QUEUE)); - } - - private boolean needsToEnqueueProcessing(@NonNull DecryptionResult result) { - SignalServiceGroupV2 groupContext = GroupUtil.getGroupContextIfPresent(result.getContent()); - - if (groupContext != null) { - GroupId groupId = GroupId.v2(groupContext.getMasterKey()); - - if (groupId.isV2()) { - String queueName = PushProcessMessageJob.getQueueName(Recipient.externalPossiblyMigratedGroup(groupId).getId()); - GroupTable groupDatabase = SignalDatabase.groups(); - - return !jobManager.isQueueEmpty(queueName) || - groupContext.getRevision() > groupDatabase.getGroupV2Revision(groupId.requireV2()) || - groupDatabase.getGroupV1ByExpectedV2(groupId.requireV2()).isPresent(); - } else { - return false; - } - } else if (result.getContent() != null) { - RecipientId recipientId = RecipientId.from(result.getContent().getSender()); - String queueKey = PushProcessMessageJob.getQueueName(recipientId); - - return !jobManager.isQueueEmpty(queueKey); - } else if (result.getException() != null) { - RecipientId recipientId = Recipient.external(context, result.getException().getSender()).getId(); - String queueKey = PushProcessMessageJob.getQueueName(recipientId); - - return !jobManager.isQueueEmpty(queueKey); - } else { - return false; - } - } - - @Override - public void close() { - release(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index dc0614883e..bd2915e5af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; @@ -15,6 +16,7 @@ import com.mobilecoin.lib.exceptions.SerializationException; import org.signal.core.util.Hex; +import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.ecc.ECPublicKey; @@ -198,24 +200,19 @@ * data to our data stores. */ @SuppressWarnings({ "OptionalGetWithoutIsPresent", "OptionalIsPresent" }) -public final class MessageContentProcessor { +public class MessageContentProcessor { private static final String TAG = Log.tag(MessageContentProcessor.class); private final Context context; - private final boolean processingEarlyContent; - public static MessageContentProcessor forNormalContent(@NonNull Context context) { - return new MessageContentProcessor(context, false); + public static MessageContentProcessor create(@NonNull Context context) { + return new MessageContentProcessor(context); } - public static MessageContentProcessor forEarlyContent(@NonNull Context context) { - return new MessageContentProcessor(context, true); - } - - private MessageContentProcessor(@NonNull Context context, boolean processingEarlyContent) { - this.context = context; - this.processingEarlyContent = processingEarlyContent; + @VisibleForTesting + MessageContentProcessor(@NonNull Context context) { + this.context = context; } /** @@ -225,7 +222,24 @@ private MessageContentProcessor(@NonNull Context context, boolean processingEarl * This is super-stateful, and it's recommended that this be run in a transaction so that no * intermediate results are persisted to the database if the app were to crash. */ - public void process(MessageState messageState, @Nullable SignalServiceContent content, @Nullable ExceptionMetadata exceptionMetadata, long timestamp, long smsMessageId) + public void process(MessageState messageState, @Nullable SignalServiceContent content, @Nullable ExceptionMetadata exceptionMetadata, long envelopeTimestamp, long smsMessageId) + throws IOException, GroupChangeBusyException + { + process(messageState, content, exceptionMetadata, envelopeTimestamp, smsMessageId, false); + } + + /** + * The same as {@link #process(MessageState, SignalServiceContent, ExceptionMetadata, long, long)}, except specifically targeted at early content. + * Using this method will *not* store or enqueue early content jobs if we detect this as being early, to avoid recursive scenarios. + */ + public void processEarlyContent(MessageState messageState, @Nullable SignalServiceContent content, @Nullable ExceptionMetadata exceptionMetadata, long envelopeTimestamp, long smsMessageId) + throws IOException, GroupChangeBusyException + { + process(messageState, content, exceptionMetadata, envelopeTimestamp, smsMessageId, true); + } + + + private void process(MessageState messageState, @Nullable SignalServiceContent content, @Nullable ExceptionMetadata exceptionMetadata, long envelopeTimestamp, long smsMessageId, boolean processingEarlyContent) throws IOException, GroupChangeBusyException { Optional optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.empty(); @@ -235,22 +249,22 @@ public void process(MessageState messageState, @Nullable SignalServiceContent co if (content != null) { Recipient senderRecipient = Recipient.externalPush(content.getSender()); - handleMessage(content, timestamp, senderRecipient, optionalSmsMessageId); + handleMessage(content, senderRecipient, optionalSmsMessageId, processingEarlyContent); Optional> earlyContent = ApplicationDependencies.getEarlyMessageCache() .retrieve(senderRecipient.getId(), content.getTimestamp()); - if (earlyContent.isPresent()) { + if (!processingEarlyContent && earlyContent.isPresent()) { log(String.valueOf(content.getTimestamp()), "Found " + earlyContent.get().size() + " dependent item(s) that were retrieved earlier. Processing."); for (SignalServiceContent earlyItem : earlyContent.get()) { - handleMessage(earlyItem, timestamp, senderRecipient, Optional.empty()); + handleMessage(earlyItem, senderRecipient, Optional.empty(), true); } } } else { warn("null", "Null content. Ignoring message."); } } else if (exceptionMetadata != null) { - handleExceptionMessage(messageState, exceptionMetadata, timestamp, optionalSmsMessageId); + handleExceptionMessage(messageState, exceptionMetadata, envelopeTimestamp, optionalSmsMessageId); } else if (messageState == MessageState.NOOP) { Log.d(TAG, "Nothing to do: " + messageState.name()); } else { @@ -258,11 +272,11 @@ public void process(MessageState messageState, @Nullable SignalServiceContent co } } - private void handleMessage(@NonNull SignalServiceContent content, long timestamp, @NonNull Recipient senderRecipient, @NonNull Optional smsMessageId) + private void handleMessage(@NonNull SignalServiceContent content, @NonNull Recipient senderRecipient, @NonNull Optional smsMessageId, boolean processingEarlyContent) throws IOException, GroupChangeBusyException { try { - Recipient threadRecipient = getMessageDestination(content); + Recipient threadRecipient = getMessageDestination(content, senderRecipient); if (shouldIgnore(content, senderRecipient, threadRecipient)) { log(content.getTimestamp(), "Ignoring message."); @@ -293,8 +307,8 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp else if (message.isEndSession()) messageId = handleEndSessionMessage(content, smsMessageId, senderRecipient); else if (message.isExpirationUpdate()) messageId = handleExpirationUpdate(content, message, smsMessageId, groupId, senderRecipient, threadRecipient, receivedTime, false); else if (message.getReaction().isPresent() && message.getStoryContext().isPresent()) messageId = handleStoryReaction(content, message, senderRecipient); - else if (message.getReaction().isPresent()) messageId = handleReaction(content, message, senderRecipient); - else if (message.getRemoteDelete().isPresent()) messageId = handleRemoteDelete(content, message, senderRecipient); + else if (message.getReaction().isPresent()) messageId = handleReaction(content, message, senderRecipient, processingEarlyContent); + else if (message.getRemoteDelete().isPresent()) messageId = handleRemoteDelete(content, message, senderRecipient, processingEarlyContent); else if (message.isActivatePaymentsRequest()) messageId = handlePaymentActivation(content, message, smsMessageId, senderRecipient, receivedTime, true, false); else if (message.isPaymentsActivated()) messageId = handlePaymentActivation(content, message, smsMessageId, senderRecipient, receivedTime, false, true); else if (message.getPayment().isPresent()) messageId = handlePayment(content, message, smsMessageId, senderRecipient, receivedTime); @@ -313,7 +327,7 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp } if (content.isNeedsReceipt() && messageId != null) { - handleNeedsDeliveryReceipt(content, message, messageId); + handleNeedsDeliveryReceipt(senderRecipient.getId(), message, messageId); } else if (!content.isNeedsReceipt()) { if (RecipientUtil.shouldHaveProfileKey(threadRecipient)) { Log.w(TAG, "Received an unsealed sender message from " + senderRecipient.getId() + ", but they should already have our profile key. Correcting."); @@ -341,11 +355,11 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); - if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get(), senderRecipient); + if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get(), senderRecipient, processingEarlyContent); else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get(), content.getTimestamp()); - else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(content, syncMessage.getRead().get(), content.getTimestamp()); + else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(content, syncMessage.getRead().get(), content.getTimestamp(), processingEarlyContent); else if (syncMessage.getViewed().isPresent()) handleSynchronizeViewedMessage(syncMessage.getViewed().get(), content.getTimestamp()); - else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(content, syncMessage.getViewOnceOpen().get(), content.getTimestamp()); + else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(content, syncMessage.getViewOnceOpen().get(), content.getTimestamp(), processingEarlyContent); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get(), content.getTimestamp()); else if (syncMessage.getConfiguration().isPresent()) handleSynchronizeConfigurationMessage(syncMessage.getConfiguration().get(), content.getTimestamp()); @@ -377,9 +391,9 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp } else if (content.getReceiptMessage().isPresent()) { SignalServiceReceiptMessage message = content.getReceiptMessage().get(); - if (message.isReadReceipt()) handleReadReceipt(content, message, senderRecipient); + if (message.isReadReceipt()) handleReadReceipt(content, message, senderRecipient, processingEarlyContent); else if (message.isDeliveryReceipt()) handleDeliveryReceipt(content, message, senderRecipient); - else if (message.isViewedReceipt()) handleViewedReceipt(content, message, senderRecipient); + else if (message.isViewedReceipt()) handleViewedReceipt(content, message, senderRecipient, processingEarlyContent); } else if (content.getTypingMessage().isPresent()) { handleTypingMessage(content, content.getTypingMessage().get(), senderRecipient); } else if (content.getStoryMessage().isPresent()) { @@ -402,7 +416,7 @@ private void handleMessage(@NonNull SignalServiceContent content, long timestamp } } catch (StorageFailedException e) { warn(String.valueOf(content.getTimestamp()), e); - handleCorruptMessage(e.getSender(), e.getSenderDevice(), timestamp, smsMessageId); + handleCorruptMessage(e.getSender(), e.getSenderDevice(), content.getTimestamp(), smsMessageId); } catch (BadGroupIdException e) { warn(String.valueOf(content.getTimestamp()), "Ignoring message with bad group id", e); } @@ -575,6 +589,11 @@ private void handleExceptionMessage(@NonNull MessageState messageState, @NonNull } switch (messageState) { + case DECRYPTION_ERROR: + warn(String.valueOf(timestamp), "Handling encryption error."); + SignalDatabase.messages().insertBadDecryptMessage(sender.getId(), e.senderDevice, timestamp, System.currentTimeMillis(), getThreadIdForException(e)); + break; + case INVALID_VERSION: warn(String.valueOf(timestamp), "Handling invalid version."); handleInvalidVersionMessage(e.sender, e.senderDevice, timestamp, smsMessageId); @@ -605,6 +624,15 @@ private void handleExceptionMessage(@NonNull MessageState messageState, @NonNull } } + private long getThreadIdForException(ExceptionMetadata metadata) { + if (metadata.groupId != null) { + Recipient groupRecipient = Recipient.externalPossiblyMigratedGroup(metadata.groupId); + return SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient); + } else { + return SignalDatabase.threads().getOrCreateThreadIdFor(Recipient.external(context, metadata.sender)); + } + } + private void handleCallOfferMessage(@NonNull SignalServiceContent content, @NonNull OfferMessage message, @NonNull Optional smsMessageId, @@ -814,7 +842,7 @@ private void handleUnknownGroupMessage(@NonNull SignalServiceContent content, warn(content.getTimestamp(), "Group message missing destination uuid, defaulting to ACI"); authServiceId = SignalStore.account().requireAci(); } - SignalDatabase.groups().fixMissingMasterKey(authServiceId, group.getMasterKey()); + SignalDatabase.groups().fixMissingMasterKey(group.getMasterKey()); } /** @@ -903,7 +931,7 @@ private void handlePossibleExpirationUpdate(@NonNull SignalServiceContent conten boolean sideEffect) throws StorageFailedException { - log(content.getTimestamp(), "Expiration update."); + log(content.getTimestamp(), "Expiration update. Side effect: " + sideEffect); if (groupId.isPresent() && groupId.get().isV2()) { warn(String.valueOf(content.getTimestamp()), "Expiration update received for GV2. Ignoring."); @@ -963,7 +991,7 @@ private void handlePossibleExpirationUpdate(@NonNull SignalServiceContent conten return null; } - private @Nullable MessageId handleReaction(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Recipient senderRecipient) throws StorageFailedException { + private @Nullable MessageId handleReaction(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Recipient senderRecipient, boolean processingEarlyContent) throws StorageFailedException { log(content.getTimestamp(), "Handle reaction for message " + message.getReaction().get().getTargetSentTimestamp()); SignalServiceDataMessage.Reaction reaction = message.getReaction().get(); @@ -1023,7 +1051,7 @@ private void handlePossibleExpirationUpdate(@NonNull SignalServiceContent conten return new MessageId(targetMessage.getId()); } - private @Nullable MessageId handleRemoteDelete(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Recipient senderRecipient) { + private @Nullable MessageId handleRemoteDelete(@NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message, @NonNull Recipient senderRecipient, boolean processingEarlyContent) { log(content.getTimestamp(), "Remote delete for message " + message.getRemoteDelete().get().getTargetSentTimestamp()); SignalServiceDataMessage.RemoteDelete delete = message.getRemoteDelete().get(); @@ -1275,7 +1303,8 @@ private void handleSynchronizeCallEvent(@NonNull SyncMessage.CallEvent callEvent private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, @NonNull SentTranscriptMessage message, - @NonNull Recipient senderRecipient) + @NonNull Recipient senderRecipient, + boolean processingEarlyContent) throws StorageFailedException, BadGroupIdException, IOException, GroupChangeBusyException { log(String.valueOf(content.getTimestamp()), "Processing sent transcript for message with ID " + message.getTimestamp()); @@ -1314,10 +1343,10 @@ private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content, } else if (dataMessage.getStoryContext().isPresent()) { threadId = handleSynchronizeSentStoryReply(message, content.getTimestamp()); } else if (dataMessage.getReaction().isPresent()) { - handleReaction(content, dataMessage, senderRecipient); + handleReaction(content, dataMessage, senderRecipient, processingEarlyContent); threadId = SignalDatabase.threads().getOrCreateThreadIdFor(getSyncMessageDestination(message)); } else if (dataMessage.getRemoteDelete().isPresent()) { - handleRemoteDelete(content, dataMessage, senderRecipient); + handleRemoteDelete(content, dataMessage, senderRecipient, processingEarlyContent); } else if (dataMessage.getAttachments().isPresent() || dataMessage.getQuote().isPresent() || dataMessage.getPreviews().isPresent() || dataMessage.getSticker().isPresent() || dataMessage.isViewOnce() || dataMessage.getMentions().isPresent()) { threadId = handleSynchronizeSentMediaMessage(message, content.getTimestamp()); } else { @@ -1407,7 +1436,8 @@ private void handleSynchronizeRequestMessage(@NonNull RequestMessage message, lo private void handleSynchronizeReadMessage(@NonNull SignalServiceContent content, @NonNull List readMessages, - long envelopeTimestamp) + long envelopeTimestamp, + boolean processingEarlyContent) { log(envelopeTimestamp, "Synchronize read message. Count: " + readMessages.size() + ", Timestamps: " + Stream.of(readMessages).map(ReadMessage::getTimestamp).toList()); @@ -1473,7 +1503,7 @@ private void handleSynchronizeViewedMessage(@NonNull List viewedM messageNotifier.updateNotification(context); } - private void handleSynchronizeViewOnceOpenMessage(@NonNull SignalServiceContent content, @NonNull ViewOnceOpenMessage openMessage, long envelopeTimestamp) { + private void handleSynchronizeViewOnceOpenMessage(@NonNull SignalServiceContent content, @NonNull ViewOnceOpenMessage openMessage, long envelopeTimestamp, boolean processingEarlyContent) { log(envelopeTimestamp, "Handling a view-once open for message: " + openMessage.getTimestamp()); RecipientId author = Recipient.externalPush(openMessage.getSender()).getId(); @@ -2638,16 +2668,17 @@ private void handleProfileKey(@NonNull SignalServiceContent content, } } - private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content, + private void handleNeedsDeliveryReceipt(@NonNull RecipientId senderId, @NonNull SignalServiceDataMessage message, @NonNull MessageId messageId) { - ApplicationDependencies.getJobManager().add(new SendDeliveryReceiptJob(RecipientId.from(content.getSender()), message.getTimestamp(), messageId)); + SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(new SendDeliveryReceiptJob(senderId, message.getTimestamp(), messageId))); } private void handleViewedReceipt(@NonNull SignalServiceContent content, @NonNull SignalServiceReceiptMessage message, - @NonNull Recipient senderRecipient) + @NonNull Recipient senderRecipient, + boolean processingEarlyContent) { boolean readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context); boolean storyViewedReceipts = SignalStore.storyValues().getViewedReceiptsEnabled(); @@ -2722,7 +2753,8 @@ private void handleDeliveryReceipt(@NonNull SignalServiceContent content, @SuppressLint("DefaultLocale") private void handleReadReceipt(@NonNull SignalServiceContent content, @NonNull SignalServiceReceiptMessage message, - @NonNull Recipient senderRecipient) + @NonNull Recipient senderRecipient, + boolean processingEarlyContent) { if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { log("Ignoring read receipts for IDs: " + Util.join(message.getTimestamps(), ", ")); @@ -3141,24 +3173,28 @@ private Optional insertPlaceholder(@NonNull String sender, int sen } private Recipient getSyncMessageDestination(@NonNull SentTranscriptMessage message) { - return getGroupRecipient(message.getDataMessage().get().getGroupContext()).orElseGet(() -> Recipient.externalPush(message.getDestination().get())); + if (message.getDataMessage().get().getGroupContext().isPresent()) { + return Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.getDataMessage().get().getGroupContext().get().getMasterKey())); + } else { + return Recipient.externalPush(message.getDestination().get()); + } } - private Recipient getMessageDestination(@NonNull SignalServiceContent content) throws BadGroupIdException { + private Recipient getMessageDestination(@NonNull SignalServiceContent content, @NonNull Recipient sender) throws BadGroupIdException { if (content.getStoryMessage().isPresent()) { SignalServiceStoryMessage message = content.getStoryMessage().get(); - return getGroupRecipient(message.getGroupContext()).orElseGet(() -> Recipient.externalPush(content.getSender())); + return getGroupRecipient(message.getGroupContext(), sender); } else { SignalServiceDataMessage message = content.getDataMessage().orElse(null); - return getGroupRecipient(message != null ? message.getGroupContext() : Optional.empty()).orElseGet(() -> Recipient.externalPush(content.getSender())); + return getGroupRecipient(message != null ? message.getGroupContext() : Optional.empty(), sender); } } - private Optional getGroupRecipient(Optional message) { - if (message.isPresent()) { - return Optional.of(Recipient.externalPossiblyMigratedGroup(GroupId.v2(message.get().getMasterKey()))); + private @NonNull Recipient getGroupRecipient(Optional signalServiceGroup, @NonNull Recipient senderRecipient) { + if (signalServiceGroup.isPresent()) { + return Recipient.externalPossiblyMigratedGroup(GroupId.v2(signalServiceGroup.get().getMasterKey())); } else { - return Optional.empty(); + return senderRecipient; } } @@ -3352,7 +3388,8 @@ public enum MessageState { LEGACY_MESSAGE, DUPLICATE_MESSAGE, UNSUPPORTED_DATA_MESSAGE, - NOOP + NOOP, + DECRYPTION_ERROR } public static final class ExceptionMetadata { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java deleted file mode 100644 index d033d35ecb..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java +++ /dev/null @@ -1,308 +0,0 @@ -package org.thoughtcrime.securesms.messages; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; - -import org.signal.core.util.PendingIntentFlags; -import org.signal.core.util.logging.Log; -import org.signal.libsignal.metadata.InvalidMetadataMessageException; -import org.signal.libsignal.metadata.InvalidMetadataVersionException; -import org.signal.libsignal.metadata.ProtocolDuplicateMessageException; -import org.signal.libsignal.metadata.ProtocolException; -import org.signal.libsignal.metadata.ProtocolInvalidKeyException; -import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException; -import org.signal.libsignal.metadata.ProtocolInvalidMessageException; -import org.signal.libsignal.metadata.ProtocolInvalidVersionException; -import org.signal.libsignal.metadata.ProtocolLegacyMessageException; -import org.signal.libsignal.metadata.ProtocolNoSessionException; -import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; -import org.signal.libsignal.metadata.SelfSendException; -import org.signal.libsignal.protocol.message.CiphertextMessage; -import org.signal.libsignal.protocol.message.DecryptionErrorMessage; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; -import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.groups.BadGroupIdException; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob; -import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; -import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity; -import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata; -import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.notifications.NotificationIds; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.FeatureFlags; -import org.whispersystems.signalservice.api.InvalidMessageStructureException; -import org.whispersystems.signalservice.api.SignalServiceAccountDataStore; -import org.whispersystems.signalservice.api.crypto.ContentHint; -import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; -import org.whispersystems.signalservice.api.messages.SignalServiceContent; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.push.ServiceId; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.internal.push.SignalServiceProtos; -import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; - -/** - * Handles taking an encrypted {@link SignalServiceEnvelope} and turning it into a plaintext model. - */ -public final class MessageDecryptionUtil { - - private static final String TAG = Log.tag(MessageDecryptionUtil.class); - - private MessageDecryptionUtil() {} - - /** - * Takes a {@link SignalServiceEnvelope} and returns a {@link DecryptionResult}, which has either - * a plaintext {@link SignalServiceContent} or information about an error that happened. - * - * Excluding the data updated in our protocol stores that results from decrypting a message, this - * method is side-effect free, preferring to return the decryption results to be handled by the - * caller. - */ - public static @NonNull DecryptionResult decrypt(@NonNull Context context, @NonNull SignalServiceEnvelope envelope) { - ServiceId aci = SignalStore.account().requireAci(); - ServiceId pni = SignalStore.account().requirePni(); - - ServiceId destination; - if (!FeatureFlags.phoneNumberPrivacy()) { - destination = aci; - } else if (envelope.hasDestinationUuid()) { - destination = ServiceId.parseOrThrow(envelope.getDestinationUuid()); - } else { - Log.w(TAG, "No destinationUuid set! Defaulting to ACI."); - destination = aci; - } - - if (destination.equals(pni)) { - if (envelope.hasSourceUuid()) { - RecipientId sender = RecipientId.from(envelope.getSourceAddress()); - SignalDatabase.recipients().markNeedsPniSignature(sender); - } else { - Log.w(TAG, "[" + envelope.getTimestamp() + "] Got a sealed sender message to our PNI? Invalid message, ignoring."); - return DecryptionResult.forNoop(Collections.emptyList()); - } - } - - if (!destination.equals(aci) && !destination.equals(pni)) { - Log.w(TAG, "Destination of " + destination + " does not match our ACI (" + aci + ") or PNI (" + pni + ")! Defaulting to ACI."); - destination = aci; - } - - SignalServiceAccountDataStore protocolStore = ApplicationDependencies.getProtocolStore().get(destination); - SignalServiceAddress localAddress = new SignalServiceAddress(SignalStore.account().requireAci(), SignalStore.account().getE164()); - SignalServiceCipher cipher = new SignalServiceCipher(localAddress, SignalStore.account().getDeviceId(), protocolStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator()); - List jobs = new LinkedList<>(); - - if (envelope.isPreKeySignalMessage()) { - PreKeysSyncJob.enqueue(); - } - - try { - try { - return DecryptionResult.forSuccess(cipher.decrypt(envelope), jobs); - } catch (ProtocolInvalidVersionException e) { - Log.w(TAG, String.valueOf(envelope.getTimestamp()), e); - return DecryptionResult.forError(MessageState.INVALID_VERSION, toExceptionMetadata(e), jobs); - - } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException | ProtocolNoSessionException | ProtocolInvalidMessageException e) { - Log.w(TAG, String.valueOf(envelope.getTimestamp()), e, true); - Recipient sender = Recipient.external(context, e.getSender()); - - if (FeatureFlags.retryReceipts()) { - jobs.add(handleRetry(context, sender, envelope, e)); - postInternalErrorNotification(context); - } else { - jobs.add(new AutomaticSessionResetJob(sender.getId(), e.getSenderDevice(), envelope.getTimestamp())); - } - - return DecryptionResult.forNoop(jobs); - } catch (ProtocolLegacyMessageException e) { - Log.w(TAG, "[" + envelope.getTimestamp() + "] " + envelope.getSourceIdentifier() + ":" + envelope.getSourceDevice(), e); - return DecryptionResult.forError(MessageState.LEGACY_MESSAGE, toExceptionMetadata(e), jobs); - } catch (ProtocolDuplicateMessageException e) { - Log.w(TAG, "[" + envelope.getTimestamp() + "] " + envelope.getSourceIdentifier() + ":" + envelope.getSourceDevice(), e); - return DecryptionResult.forError(MessageState.DUPLICATE_MESSAGE, toExceptionMetadata(e), jobs); - } catch (InvalidMetadataVersionException | InvalidMetadataMessageException | InvalidMessageStructureException e) { - Log.w(TAG, "[" + envelope.getTimestamp() + "] " + envelope.getSourceIdentifier() + ":" + envelope.getSourceDevice(), e); - return DecryptionResult.forNoop(jobs); - } catch (SelfSendException e) { - Log.i(TAG, "Dropping UD message from self."); - return DecryptionResult.forNoop(jobs); - } catch (UnsupportedDataMessageException e) { - Log.w(TAG, "[" + envelope.getTimestamp() + "] " + envelope.getSourceIdentifier() + ":" + envelope.getSourceDevice(), e); - return DecryptionResult.forError(MessageState.UNSUPPORTED_DATA_MESSAGE, toExceptionMetadata(e), jobs); - } - } catch (NoSenderException e) { - Log.w(TAG, "Invalid message, but no sender info!"); - return DecryptionResult.forNoop(jobs); - } - } - - private static @NonNull Job handleRetry(@NonNull Context context, @NonNull Recipient sender, @NonNull SignalServiceEnvelope envelope, @NonNull ProtocolException protocolException) { - ContentHint contentHint = ContentHint.fromType(protocolException.getContentHint()); - int senderDevice = protocolException.getSenderDevice(); - long receivedTimestamp = System.currentTimeMillis(); - Optional groupId = Optional.empty(); - - if (protocolException.getGroupId().isPresent()) { - try { - groupId = Optional.of(GroupId.push(protocolException.getGroupId().get())); - } catch (BadGroupIdException e) { - Log.w(TAG, "[" + envelope.getTimestamp() + "] Bad groupId!", true); - } - } - - Log.w(TAG, "[" + envelope.getTimestamp() + "] Could not decrypt a message with a type of " + contentHint, true); - - long threadId; - - if (groupId.isPresent()) { - Recipient groupRecipient = Recipient.externalPossiblyMigratedGroup(groupId.get()); - threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient); - } else { - threadId = SignalDatabase.threads().getOrCreateThreadIdFor(sender); - } - - switch (contentHint) { - case DEFAULT: - Log.w(TAG, "[" + envelope.getTimestamp() + "] Inserting an error right away because it's " + contentHint, true); - SignalDatabase.messages().insertBadDecryptMessage(sender.getId(), senderDevice, envelope.getTimestamp(), receivedTimestamp, threadId); - break; - case RESENDABLE: - Log.w(TAG, "[" + envelope.getTimestamp() + "] Inserting into pending retries store because it's " + contentHint, true); - ApplicationDependencies.getPendingRetryReceiptCache().insert(sender.getId(), senderDevice, envelope.getTimestamp(), receivedTimestamp, threadId); - ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary(); - break; - case IMPLICIT: - Log.w(TAG, "[" + envelope.getTimestamp() + "] Not inserting any error because it's " + contentHint, true); - break; - } - - byte[] originalContent; - int envelopeType; - if (protocolException.getUnidentifiedSenderMessageContent().isPresent()) { - originalContent = protocolException.getUnidentifiedSenderMessageContent().get().getContent(); - envelopeType = protocolException.getUnidentifiedSenderMessageContent().get().getType(); - } else { - originalContent = envelope.getContent(); - envelopeType = envelopeTypeToCiphertextMessageType(envelope.getType()); - } - - DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent, envelopeType, envelope.getTimestamp(), senderDevice); - - return new SendRetryReceiptJob(sender.getId(), groupId, decryptionErrorMessage); - } - - private static ExceptionMetadata toExceptionMetadata(@NonNull UnsupportedDataMessageException e) - throws NoSenderException - { - String sender = e.getSender(); - - if (sender == null) throw new NoSenderException(); - - GroupId groupId = e.getGroup().isPresent() ? GroupId.v2(e.getGroup().get().getMasterKey()) : null; - - return new ExceptionMetadata(sender, e.getSenderDevice(), groupId); - } - - private static ExceptionMetadata toExceptionMetadata(@NonNull ProtocolException e) throws NoSenderException { - String sender = e.getSender(); - - if (sender == null) throw new NoSenderException(); - - return new ExceptionMetadata(sender, e.getSenderDevice()); - } - - private static void postInternalErrorNotification(@NonNull Context context) { - if (!FeatureFlags.internalUser()) return; - - NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, - new NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) - .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(context.getString(R.string.MessageDecryptionUtil_failed_to_decrypt_message)) - .setContentText(context.getString(R.string.MessageDecryptionUtil_tap_to_send_a_debug_log)) - .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, SubmitDebugLogActivity.class), PendingIntentFlags.mutable())) - .build()); - } - - private static int envelopeTypeToCiphertextMessageType(int envelopeType) { - switch (envelopeType) { - case SignalServiceProtos.Envelope.Type.CIPHERTEXT_VALUE: return CiphertextMessage.WHISPER_TYPE; - case SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE: return CiphertextMessage.PREKEY_TYPE; - case SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE: return CiphertextMessage.SENDERKEY_TYPE; - case SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE: return CiphertextMessage.PLAINTEXT_CONTENT_TYPE; - default: return CiphertextMessage.WHISPER_TYPE; - } - } - - - private static class NoSenderException extends Exception {} - - public static class DecryptionResult { - private final @NonNull MessageState state; - private final @Nullable SignalServiceContent content; - private final @Nullable ExceptionMetadata exception; - private final @NonNull List jobs; - - static @NonNull DecryptionResult forSuccess(@NonNull SignalServiceContent content, @NonNull List jobs) { - return new DecryptionResult(MessageState.DECRYPTED_OK, content, null, jobs); - } - - static @NonNull DecryptionResult forError(@NonNull MessageState messageState, - @NonNull ExceptionMetadata exception, - @NonNull List jobs) - { - return new DecryptionResult(messageState, null, exception, jobs); - } - - static @NonNull DecryptionResult forNoop(@NonNull List jobs) { - return new DecryptionResult(MessageState.NOOP, null, null, jobs); - } - - private DecryptionResult(@NonNull MessageState state, - @Nullable SignalServiceContent content, - @Nullable ExceptionMetadata exception, - @NonNull List jobs) - { - this.state = state; - this.content = content; - this.exception = exception; - this.jobs = jobs; - } - - public @NonNull MessageState getState() { - return state; - } - - public @Nullable SignalServiceContent getContent() { - return content; - } - - public @Nullable ExceptionMetadata getException() { - return exception; - } - - public @NonNull List getJobs() { - return jobs; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt new file mode 100644 index 0000000000..79b1b3c9ef --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -0,0 +1,498 @@ +package org.thoughtcrime.securesms.messages + +import android.app.Notification +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.squareup.wire.internal.toUnmodifiableList +import org.signal.core.util.PendingIntentFlags +import org.signal.core.util.logging.Log +import org.signal.libsignal.metadata.InvalidMetadataMessageException +import org.signal.libsignal.metadata.InvalidMetadataVersionException +import org.signal.libsignal.metadata.ProtocolDuplicateMessageException +import org.signal.libsignal.metadata.ProtocolException +import org.signal.libsignal.metadata.ProtocolInvalidKeyException +import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException +import org.signal.libsignal.metadata.ProtocolInvalidMessageException +import org.signal.libsignal.metadata.ProtocolInvalidVersionException +import org.signal.libsignal.metadata.ProtocolLegacyMessageException +import org.signal.libsignal.metadata.ProtocolNoSessionException +import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException +import org.signal.libsignal.metadata.SelfSendException +import org.signal.libsignal.protocol.SignalProtocolAddress +import org.signal.libsignal.protocol.message.CiphertextMessage +import org.signal.libsignal.protocol.message.DecryptionErrorMessage +import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage +import org.signal.libsignal.zkgroup.groups.GroupMasterKey +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.crypto.ReentrantSessionLock +import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.groups.BadGroupIdException +import org.thoughtcrime.securesms.groups.GroupId +import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob +import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.notifications.NotificationIds +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.FeatureFlags +import org.whispersystems.signalservice.api.InvalidMessageStructureException +import org.whispersystems.signalservice.api.SignalServiceAccountDataStore +import org.whispersystems.signalservice.api.crypto.ContentHint +import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata +import org.whispersystems.signalservice.api.crypto.SignalServiceCipher +import org.whispersystems.signalservice.api.crypto.SignalServiceCipherResult +import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator +import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.PniSignatureMessage +import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException +import java.util.Optional + +/** + * This class is designed to handle everything around the process of taking an [Envelope] and decrypting it into something + * that you can use (or provide an appropriate error if something goes wrong). We'll also use this space to go over some + * high-level concepts in message decryption. + */ +object MessageDecryptor { + + private val TAG = Log.tag(MessageDecryptor::class.java) + + /** + * Decrypts an envelope and provides a [Result]. This method has side effects, but all of them are limited to [SignalDatabase]. + * That means that this operation should be atomic when performed within a transaction. + * To keep that property, there may be [Result.followUpOperations] you have to perform after your transaction is committed. + * These can vary from enqueueing jobs to inserting items into the [org.thoughtcrime.securesms.database.PendingRetryReceiptCache]. + */ + fun decrypt(context: Context, envelope: Envelope, serverDeliveredTimestamp: Long): Result { + val selfAci: ServiceId = SignalStore.account().requireAci() + val selfPni: ServiceId = SignalStore.account().requirePni() + + val destination: ServiceId = envelope.getDestination(selfAci, selfPni) + + if (destination == selfPni && envelope.hasSourceUuid()) { + Log.i(TAG, "${logPrefix(envelope)} Received a message at our PNI. Marking as needing a PNI signature.") + + val sourceServiceId = ServiceId.parseOrNull(envelope.sourceUuid) + + if (sourceServiceId != null) { + val sender = RecipientId.from(sourceServiceId) + SignalDatabase.recipients.markNeedsPniSignature(sender) + } else { + Log.w(TAG, "${logPrefix(envelope)} Could not mark sender as needing a PNI signature because the sender serviceId was invalid!") + } + } + + if (destination == selfPni && !envelope.hasSourceUuid()) { + Log.w(TAG, "${logPrefix(envelope)} Got a sealed sender message to our PNI? Invalid message, ignoring.") + return Result.Ignore(envelope, serverDeliveredTimestamp, emptyList()) + } + + val followUpOperations: MutableList = mutableListOf() + + if (envelope.type == Envelope.Type.PREKEY_BUNDLE) { + followUpOperations += Runnable { + PreKeysSyncJob.enqueue() + } + } + + val protocolStore: SignalServiceAccountDataStore = ApplicationDependencies.getProtocolStore().get(destination) + val localAddress = SignalServiceAddress(selfAci, SignalStore.account().e164) + val cipher = SignalServiceCipher(localAddress, SignalStore.account().deviceId, protocolStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator()) + + return try { + val cipherResult: SignalServiceCipherResult? = cipher.decrypt(envelope, serverDeliveredTimestamp) + + if (cipherResult == null) { + Log.w(TAG, "${logPrefix(envelope)} Decryption resulted in a null result!", true) + return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) + } + + Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope.") + + val validationResult: EnvelopeContentValidator.Result = EnvelopeContentValidator.validate(envelope, cipherResult.content) + + if (validationResult is EnvelopeContentValidator.Result.Invalid) { + Log.w(TAG, "${logPrefix(envelope, cipherResult)} Invalid content! ${validationResult.reason}", validationResult.throwable) + return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) + } + + if (validationResult is EnvelopeContentValidator.Result.UnsupportedDataMessage) { + Log.w(TAG, "${logPrefix(envelope, cipherResult)} Unsupported DataMessage! Our version: ${validationResult.ourVersion}, their version: ${validationResult.theirVersion}") + return Result.UnsupportedDataMessage(envelope, serverDeliveredTimestamp, cipherResult.toErrorMetadata(), followUpOperations) + } + + // Must handle SKDM's immediately, because subsequent decryptions could rely on it + if (cipherResult.content.hasSenderKeyDistributionMessage()) { + handleSenderKeyDistributionMessage( + envelope, + cipherResult.metadata.sourceServiceId, + cipherResult.metadata.sourceDeviceId, + SenderKeyDistributionMessage(cipherResult.content.senderKeyDistributionMessage.toByteArray()) + ) + } + + if (FeatureFlags.phoneNumberPrivacy() && cipherResult.content.hasPniSignatureMessage()) { + handlePniSignatureMessage( + envelope, + cipherResult.metadata.sourceServiceId, + cipherResult.metadata.sourceE164, + cipherResult.metadata.sourceDeviceId, + cipherResult.content.pniSignatureMessage + ) + } else if (cipherResult.content.hasPniSignatureMessage()) { + Log.w(TAG, "${logPrefix(envelope)} Ignoring PNI signature because the feature flag is disabled!") + } + + // TODO We can move this to the "message processing" stage once we give it access to the envelope. But for now it'll stay here. + if (envelope.hasReportingToken() && envelope.reportingToken != null && envelope.reportingToken.size() > 0) { + val sender = RecipientId.from(cipherResult.metadata.sourceServiceId) + SignalDatabase.recipients.setReportingToken(sender, envelope.reportingToken.toByteArray()) + } + + Result.Success(envelope, serverDeliveredTimestamp, cipherResult.content, cipherResult.metadata, followUpOperations.toUnmodifiableList()) + } catch (e: Exception) { + when (e) { + is ProtocolInvalidKeyIdException, + is ProtocolInvalidKeyException, + is ProtocolUntrustedIdentityException, + is ProtocolNoSessionException, + is ProtocolInvalidMessageException -> { + check(e is ProtocolException) + Log.w(TAG, "${logPrefix(envelope, e)} Decryption error!", e, true) + + if (FeatureFlags.internalUser()) { + postErrorNotification(context) + } + + if (FeatureFlags.retryReceipts()) { + buildResultForDecryptionError(context, envelope, serverDeliveredTimestamp, followUpOperations, e) + } else { + Log.w(TAG, "${logPrefix(envelope, e)} Retry receipts disabled! Enqueuing a session reset job, which will also insert an error message.", e, true) + + followUpOperations += Runnable { + val sender: Recipient = Recipient.external(context, e.sender) + ApplicationDependencies.getJobManager().add(AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp)) + } + + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) + } + } + + is ProtocolDuplicateMessageException -> { + Log.w(TAG, "${logPrefix(envelope, e)} Duplicate message!", e) + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) + } + + is InvalidMetadataVersionException, + is InvalidMetadataMessageException, + is InvalidMessageStructureException -> { + Log.w(TAG, "${logPrefix(envelope)} Invalid message structure!", e, true) + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) + } + + is SelfSendException -> { + Log.i(TAG, "[${envelope.timestamp}] Dropping sealed sender message from self!", e) + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) + } + + is ProtocolInvalidVersionException -> { + Log.w(TAG, "${logPrefix(envelope, e)} Invalid version!", e, true) + Result.InvalidVersion(envelope, serverDeliveredTimestamp, e.toErrorMetadata(), followUpOperations.toUnmodifiableList()) + } + + is ProtocolLegacyMessageException -> { + Log.w(TAG, "${logPrefix(envelope, e)} Legacy message!", e, true) + Result.LegacyMessage(envelope, serverDeliveredTimestamp, e.toErrorMetadata(), followUpOperations) + } + + else -> { + Log.w(TAG, "Encountered an unexpected exception! Throwing!", e, true) + throw e + } + } + } + } + + private fun buildResultForDecryptionError( + context: Context, + envelope: Envelope, + serverDeliveredTimestamp: Long, + followUpOperations: MutableList, + protocolException: ProtocolException + ): Result { + val contentHint: ContentHint = ContentHint.fromType(protocolException.contentHint) + val senderDevice: Int = protocolException.senderDevice + val receivedTimestamp: Long = System.currentTimeMillis() + val sender: Recipient = Recipient.external(context, protocolException.sender) + + followUpOperations += Runnable { + ApplicationDependencies.getJobManager().add(buildSendRetryReceiptJob(envelope, protocolException, sender)) + } + + return when (contentHint) { + ContentHint.DEFAULT -> { + Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we need to insert an error right away.", true) + Result.DecryptionError(envelope, serverDeliveredTimestamp, protocolException.toErrorMetadata(), followUpOperations.toUnmodifiableList()) + } + + ContentHint.RESENDABLE -> { + Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we can try to resend the message.", true) + + followUpOperations += Runnable { + val groupId: GroupId? = protocolException.parseGroupId(envelope) + val threadId: Long = if (groupId != null) { + val groupRecipient: Recipient = Recipient.externalPossiblyMigratedGroup(groupId) + SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient) + } else { + SignalDatabase.threads.getOrCreateThreadIdFor(sender) + } + + ApplicationDependencies.getPendingRetryReceiptCache().insert(sender.id, senderDevice, envelope.timestamp, receivedTimestamp, threadId) + ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary() + } + + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) + } + + ContentHint.IMPLICIT -> { + Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so no error message is needed.", true) + Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations) + } + } + } + + private fun handleSenderKeyDistributionMessage(envelope: Envelope, serviceId: ServiceId, deviceId: Int, message: SenderKeyDistributionMessage) { + Log.i(TAG, "${logPrefix(envelope, serviceId)} Processing SenderKeyDistributionMessage") + val sender = ApplicationDependencies.getSignalServiceMessageSender() + sender.processSenderKeyDistributionMessage(SignalProtocolAddress(serviceId.toString(), deviceId), message) + } + + private fun handlePniSignatureMessage(envelope: Envelope, serviceId: ServiceId, e164: String?, deviceId: Int, pniSignatureMessage: PniSignatureMessage) { + Log.i(TAG, "${logPrefix(envelope, serviceId)} Processing PniSignatureMessage") + + val pni: PNI = PNI.parseOrThrow(pniSignatureMessage.pni.toByteArray()) + + if (SignalDatabase.recipients.isAssociated(serviceId, pni)) { + Log.i(TAG, "${logPrefix(envelope, serviceId)}[handlePniSignatureMessage] ACI ($serviceId) and PNI ($pni) are already associated.") + return + } + + val identityStore = ApplicationDependencies.getProtocolStore().aci().identities() + val aciAddress = SignalProtocolAddress(serviceId.toString(), deviceId) + val pniAddress = SignalProtocolAddress(pni.toString(), deviceId) + val aciIdentity = identityStore.getIdentity(aciAddress) + val pniIdentity = identityStore.getIdentity(pniAddress) + + if (aciIdentity == null) { + Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] No identity found for ACI address $aciAddress") + return + } + + if (pniIdentity == null) { + Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] No identity found for PNI address $pniAddress") + return + } + + if (pniIdentity.verifyAlternateIdentity(aciIdentity, pniSignatureMessage.signature.toByteArray())) { + Log.i(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] PNI signature is valid. Associating ACI ($serviceId) with PNI ($pni)") + SignalDatabase.recipients.getAndPossiblyMergePnpVerified(serviceId, pni, e164) + } else { + Log.w(TAG, "${logPrefix(envelope, serviceId)}[validatePniSignature] Invalid PNI signature! Cannot associate ACI ($serviceId) with PNI ($pni)") + } + } + + private fun postErrorNotification(context: Context) { + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(context.getString(R.string.MessageDecryptionUtil_failed_to_decrypt_message)) + .setContentText(context.getString(R.string.MessageDecryptionUtil_tap_to_send_a_debug_log)) + .setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable())) + .build() + + NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification) + } + + private fun logPrefix(envelope: Envelope): String { + return logPrefix(envelope.timestamp, envelope.sourceUuid ?: "", envelope.sourceDevice) + } + + private fun logPrefix(envelope: Envelope, sender: ServiceId): String { + return logPrefix(envelope.timestamp, sender.toString(), envelope.sourceDevice) + } + + private fun logPrefix(envelope: Envelope, cipherResult: SignalServiceCipherResult): String { + return logPrefix(envelope.timestamp, cipherResult.metadata.sourceServiceId.toString(), envelope.sourceDevice) + } + + private fun logPrefix(envelope: Envelope, exception: ProtocolException): String { + return if (exception.sender != null) { + logPrefix(envelope.timestamp, exception.sender, exception.senderDevice) + } else { + logPrefix(envelope.timestamp, envelope.sourceUuid, envelope.sourceDevice) + } + } + + private fun logPrefix(envelope: Envelope, exception: UnsupportedDataMessageException): String { + return if (exception.sender != null) { + logPrefix(envelope.timestamp, exception.sender, exception.senderDevice) + } else { + logPrefix(envelope.timestamp, envelope.sourceUuid, envelope.sourceDevice) + } + } + + private fun logPrefix(timestamp: Long, sender: String?, deviceId: Int): String { + val senderString = sender ?: "null" + return "[$timestamp] $senderString:$deviceId |" + } + + private fun buildSendRetryReceiptJob(envelope: Envelope, protocolException: ProtocolException, sender: Recipient): SendRetryReceiptJob { + val originalContent: ByteArray + val envelopeType: Int + + if (protocolException.unidentifiedSenderMessageContent.isPresent) { + originalContent = protocolException.unidentifiedSenderMessageContent.get().content + envelopeType = protocolException.unidentifiedSenderMessageContent.get().type + } else { + originalContent = envelope.content.toByteArray() + envelopeType = envelope.type.number.toCiphertextMessageType() + } + + val decryptionErrorMessage: DecryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent, envelopeType, envelope.timestamp, protocolException.senderDevice) + val groupId: GroupId? = protocolException.parseGroupId(envelope) + return SendRetryReceiptJob(sender.id, Optional.ofNullable(groupId), decryptionErrorMessage) + } + + private fun ProtocolException.parseGroupId(envelope: Envelope): GroupId? { + return if (this.groupId.isPresent) { + try { + GroupId.push(this.groupId.get()) + } catch (e: BadGroupIdException) { + Log.w(TAG, "[${envelope.timestamp}] Bad groupId!", true) + null + } + } else { + null + } + } + + private fun Envelope.getDestination(selfAci: ServiceId, selfPni: ServiceId): ServiceId { + return if (!FeatureFlags.phoneNumberPrivacy()) { + selfAci + } else if (this.hasDestinationUuid()) { + val serviceId = ServiceId.parseOrThrow(this.destinationUuid) + if (serviceId == selfAci || serviceId == selfPni) { + serviceId + } else { + Log.w(TAG, "Destination of $serviceId does not match our ACI ($selfAci) or PNI ($selfPni)! Defaulting to ACI.") + selfAci + } + } else { + Log.w(TAG, "No destinationUuid set! Defaulting to ACI.") + selfAci + } + } + + private fun Int.toCiphertextMessageType(): Int { + return when (this) { + Envelope.Type.CIPHERTEXT_VALUE -> CiphertextMessage.WHISPER_TYPE + Envelope.Type.PREKEY_BUNDLE_VALUE -> CiphertextMessage.PREKEY_TYPE + Envelope.Type.UNIDENTIFIED_SENDER_VALUE -> CiphertextMessage.SENDERKEY_TYPE + Envelope.Type.PLAINTEXT_CONTENT_VALUE -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE + else -> CiphertextMessage.WHISPER_TYPE + } + } + + private fun ProtocolException.toErrorMetadata(): ErrorMetadata { + return ErrorMetadata( + sender = this.sender, + senderDevice = this.senderDevice, + groupId = if (this.groupId.isPresent) GroupId.v2(GroupMasterKey(this.groupId.get())) else null + ) + } + + private fun SignalServiceCipherResult.toErrorMetadata(): ErrorMetadata { + return ErrorMetadata( + sender = this.metadata.sourceServiceId.toString(), + senderDevice = this.metadata.sourceDeviceId, + groupId = null + ) + } + + sealed interface Result { + val envelope: Envelope + val serverDeliveredTimestamp: Long + val followUpOperations: List + + /** Successfully decrypted the envelope content. The plaintext [Content] is available. */ + class Success( + override val envelope: Envelope, + override val serverDeliveredTimestamp: Long, + val content: Content, + val metadata: EnvelopeMetadata, + override val followUpOperations: List + ) : Result + + /** We could not decrypt the message, and an error should be inserted into the user's chat history. */ + class DecryptionError( + override val envelope: Envelope, + override val serverDeliveredTimestamp: Long, + override val errorMetadata: ErrorMetadata, + override val followUpOperations: List + ) : Result, Error + + /** The envelope used an invalid version of the Signal protocol. */ + class InvalidVersion( + override val envelope: Envelope, + override val serverDeliveredTimestamp: Long, + override val errorMetadata: ErrorMetadata, + override val followUpOperations: List + ) : Result, Error + + /** The envelope used an old format that hasn't been used since 2015. This shouldn't be happening. */ + class LegacyMessage( + override val envelope: Envelope, + override val serverDeliveredTimestamp: Long, + override val errorMetadata: ErrorMetadata, + override val followUpOperations: List + ) : Result, Error + + /** + * Indicates the that the [org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.getRequiredProtocolVersion] + * is higher than we support. + */ + class UnsupportedDataMessage( + override val envelope: Envelope, + override val serverDeliveredTimestamp: Long, + override val errorMetadata: ErrorMetadata, + override val followUpOperations: List + ) : Result, Error + + /** There are no further results from this envelope that need to be processed. There may still be [followUpOperations]. */ + class Ignore( + override val envelope: Envelope, + override val serverDeliveredTimestamp: Long, + override val followUpOperations: List + ) : Result + + interface Error { + val errorMetadata: ErrorMetadata + } + } + + data class ErrorMetadata( + val sender: String, + val senderDevice: Int, + val groupId: GroupId? + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageRetrievalStrategy.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageRetrievalStrategy.java index 2e93ad8538..45085dc169 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageRetrievalStrategy.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageRetrievalStrategy.java @@ -20,53 +20,14 @@ */ public abstract class MessageRetrievalStrategy { - private volatile boolean canceled; - /** * Fetches and processes any pending messages. This method should block until the messages are * actually stored and processed -- not just retrieved. * - * @param timeout Hint for how long this will run. The strategy will also be canceled after the - * timeout ends, but having the timeout available may be useful for setting things - * like socket timeouts. - * * @return True if everything was successful up until cancelation, false otherwise. */ @WorkerThread - abstract boolean execute(long timeout); - - /** - * Marks the strategy as canceled. It is the responsibility of the implementation of - * {@link #execute(long)} to check {@link #isCanceled()} to know if execution should stop. - */ - void cancel() { - this.canceled = true; - } - - protected boolean isCanceled() { - return canceled; - } - - protected static void blockUntilQueueDrained(@NonNull String tag, @NonNull String queue, long timeoutMs) { - long startTime = System.currentTimeMillis(); - final JobManager jobManager = ApplicationDependencies.getJobManager(); - final MarkerJob markerJob = new MarkerJob(queue); - - Optional jobState = jobManager.runSynchronously(markerJob, timeoutMs); - - if (!jobState.isPresent()) { - Log.w(tag, "Timed out waiting for " + queue + " job(s) to finish!"); - } - - long endTime = System.currentTimeMillis(); - long duration = endTime - startTime; - - Log.d(tag, "Waited " + duration + " ms for the " + queue + " job(s) to finish."); - } - - protected static String timeSuffix(long startTime) { - return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)"; - } + abstract boolean execute(); protected static class QueueFindingJobListener implements JobTracker.JobListener { private final Set queues = new HashSet<>(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java b/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java deleted file mode 100644 index 772dc7ac78..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.thoughtcrime.securesms.messages; - -import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.JobManager; -import org.thoughtcrime.securesms.jobmanager.JobTracker; -import org.thoughtcrime.securesms.jobs.MarkerJob; -import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; -import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.stories.Stories; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; -import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Retrieves messages over the REST endpoint. - */ -public class RestStrategy extends MessageRetrievalStrategy { - - private static final String TAG = Log.tag(RestStrategy.class); - - @WorkerThread - @Override - public boolean execute(long timeout) { - long startTime = System.currentTimeMillis(); - JobManager jobManager = ApplicationDependencies.getJobManager(); - QueueFindingJobListener queueListener = new QueueFindingJobListener(); - - try (IncomingMessageProcessor.Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) { - jobManager.addListener(job -> job.getParameters().getQueue() != null && job.getParameters().getQueue().startsWith(PushProcessMessageJob.QUEUE_PREFIX), queueListener); - - int jobCount = enqueuePushDecryptJobs(processor, startTime, timeout); - - if (jobCount == 0) { - Log.d(TAG, "No PushDecryptMessageJobs were enqueued."); - return true; - } else { - Log.d(TAG, jobCount + " PushDecryptMessageJob(s) were enqueued."); - } - - long timeRemainingMs = blockUntilQueueDrained(PushDecryptMessageJob.QUEUE, TimeUnit.SECONDS.toMillis(10)); - Set processQueues = queueListener.getQueues(); - - Log.d(TAG, "Discovered " + processQueues.size() + " queue(s): " + processQueues); - - if (timeRemainingMs > 0) { - Iterator iter = processQueues.iterator(); - - while (iter.hasNext() && timeRemainingMs > 0) { - timeRemainingMs = blockUntilQueueDrained(iter.next(), timeRemainingMs); - } - - if (timeRemainingMs <= 0) { - Log.w(TAG, "Ran out of time while waiting for queues to drain."); - } - } else { - Log.w(TAG, "Ran out of time before we could even wait on individual queues!"); - } - - return true; - } catch (IOException e) { - Log.w(TAG, "Failed to retrieve messages. Resetting the SignalServiceMessageReceiver.", e); - ApplicationDependencies.resetSignalServiceMessageReceiver(); - if (e instanceof AuthorizationFailedException && SignalStore.account().isRegistered() && SignalStore.account().getAci() != null) { - TextSecurePreferences.setUnauthorizedReceived(ApplicationDependencies.getApplication(), true); - } - return false; - } finally { - jobManager.removeListener(queueListener); - } - } - - private static int enqueuePushDecryptJobs(IncomingMessageProcessor.Processor processor, long startTime, long timeout) - throws IOException - { - SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); - AtomicInteger jobCount = new AtomicInteger(0); - - receiver.setSoTimeoutMillis(timeout); - - receiver.retrieveMessages(Stories.isFeatureEnabled(), envelope -> { - Log.i(TAG, "Retrieved an envelope." + timeSuffix(startTime)); - String jobId = processor.processEnvelope(envelope); - - if (jobId != null) { - jobCount.incrementAndGet(); - } - Log.i(TAG, "Successfully processed an envelope." + timeSuffix(startTime)); - }); - - return jobCount.get(); - } - - private static long blockUntilQueueDrained(@NonNull String queue, long timeoutMs) { - long startTime = System.currentTimeMillis(); - final JobManager jobManager = ApplicationDependencies.getJobManager(); - final MarkerJob markerJob = new MarkerJob(queue); - - Optional jobState = jobManager.runSynchronously(markerJob, timeoutMs); - - if (!jobState.isPresent()) { - Log.w(TAG, "Timed out waiting for " + queue + " job(s) to finish!"); - } - - long endTime = System.currentTimeMillis(); - long duration = endTime - startTime; - - Log.d(TAG, "Waited " + duration + " ms for the " + queue + " job(s) to finish."); - return timeoutMs - duration; - } - - @Override - public @NonNull String toString() { - return Log.tag(RestStrategy.class); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/WebSocketStrategy.java b/app/src/main/java/org/thoughtcrime/securesms/messages/WebSocketStrategy.java new file mode 100644 index 0000000000..58b1e3f942 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/WebSocketStrategy.java @@ -0,0 +1,101 @@ +package org.thoughtcrime.securesms.messages; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import org.signal.core.util.Stopwatch; +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobmanager.JobTracker; +import org.thoughtcrime.securesms.jobs.MarkerJob; +import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; + +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Retrieves messages over the websocket. + */ +public class WebSocketStrategy extends MessageRetrievalStrategy { + + private static final String TAG = Log.tag(WebSocketStrategy.class); + + private static final String KEEP_ALIVE_TOKEN = "WebsocketStrategy"; + private static final long QUEUE_TIMEOUT = TimeUnit.SECONDS.toMillis(30); + + @WorkerThread + @Override + public boolean execute() { + Stopwatch stopwatch = new Stopwatch("websocket-strategy"); + IncomingMessageObserver observer = ApplicationDependencies.getIncomingMessageObserver(); + + observer.registerKeepAliveToken(KEEP_ALIVE_TOKEN); + try { + JobManager jobManager = ApplicationDependencies.getJobManager(); + QueueFindingJobListener queueListener = new QueueFindingJobListener(); + + jobManager.addListener(job -> job.getParameters().getQueue() != null && job.getParameters().getQueue().startsWith(PushProcessMessageJob.QUEUE_PREFIX), queueListener); + + blockUntilWebsocketDrained(observer); + stopwatch.split("decryptions-drained"); + + Set processQueues = queueListener.getQueues(); + Log.d(TAG, "Discovered " + processQueues.size() + " queue(s): " + processQueues); + + for (String queue : processQueues) { + blockUntilJobQueueDrained(queue, QUEUE_TIMEOUT); + } + + stopwatch.split("process-drained"); + stopwatch.stop(TAG); + + return true; + } finally { + ApplicationDependencies.getIncomingMessageObserver().removeKeepAliveToken(KEEP_ALIVE_TOKEN); + } + } + + private static void blockUntilWebsocketDrained(IncomingMessageObserver observer) { + CountDownLatch latch = new CountDownLatch(1); + + observer.addDecryptionDrainedListener(new Runnable() { + @Override public void run() { + latch.countDown(); + observer.removeDecryptionDrainedListener(this); + } + }); + + try { + if (!latch.await(1, TimeUnit.MINUTES)) { + Log.w(TAG, "Hit timeout while waiting for decryptions to drain!"); + } + } catch (InterruptedException e) { + Log.w(TAG, "Interrupted!", e); + } + } + + private static void blockUntilJobQueueDrained(@NonNull String queue, long timeoutMs) { + long startTime = System.currentTimeMillis(); + final JobManager jobManager = ApplicationDependencies.getJobManager(); + final MarkerJob markerJob = new MarkerJob(queue); + + Optional jobState = jobManager.runSynchronously(markerJob, timeoutMs); + + if (!jobState.isPresent()) { + Log.w(TAG, "Timed out waiting for " + queue + " job(s) to finish!"); + } + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + Log.d(TAG, "Waited " + duration + " ms for the " + queue + " job(s) to finish."); + } + + @Override + public @NonNull String toString() { + return Log.tag(WebSocketStrategy.class); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/WebsocketStrategy.java b/app/src/main/java/org/thoughtcrime/securesms/messages/WebsocketStrategy.java deleted file mode 100644 index 88c94af22c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/WebsocketStrategy.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.thoughtcrime.securesms.messages; - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.JobManager; -import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; -import org.whispersystems.signalservice.api.SignalWebSocket; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeoutException; - -class WebsocketStrategy extends MessageRetrievalStrategy { - - private static final String TAG = Log.tag(WebsocketStrategy.class); - - private final SignalWebSocket signalWebSocket; - private final JobManager jobManager; - - public WebsocketStrategy() { - this.signalWebSocket = ApplicationDependencies.getSignalWebSocket(); - this.jobManager = ApplicationDependencies.getJobManager(); - } - - @Override - public boolean execute(long timeout) { - long startTime = System.currentTimeMillis(); - - try { - Set processJobQueues = drainWebsocket(timeout, startTime); - Iterator queueIterator = processJobQueues.iterator(); - long timeRemaining = Math.max(0, timeout - (System.currentTimeMillis() - startTime)); - - while (!isCanceled() && queueIterator.hasNext() && timeRemaining > 0) { - String queue = queueIterator.next(); - - blockUntilQueueDrained(TAG, queue, timeRemaining); - - timeRemaining = Math.max(0, timeout - (System.currentTimeMillis() - startTime)); - } - - return true; - } catch (IOException e) { - Log.w(TAG, "Encountered an exception while draining the websocket.", e); - return false; - } - } - - private @NonNull Set drainWebsocket(long timeout, long startTime) throws IOException { - QueueFindingJobListener queueListener = new QueueFindingJobListener(); - - jobManager.addListener(job -> job.getParameters().getQueue() != null && job.getParameters().getQueue().startsWith(PushProcessMessageJob.QUEUE_PREFIX), queueListener); - - try { - signalWebSocket.connect(); - while (shouldContinue()) { - try { - Optional result = signalWebSocket.readOrEmpty(timeout, envelope -> { - Log.i(TAG, "Retrieved envelope! " + envelope.getTimestamp() + timeSuffix(startTime)); - try (IncomingMessageProcessor.Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) { - processor.processEnvelope(envelope); - } - }); - - if (!result.isPresent()) { - Log.i(TAG, "Hit an empty response. Finished." + timeSuffix(startTime)); - break; - } - } catch (TimeoutException e) { - Log.w(TAG, "Websocket timeout." + timeSuffix(startTime)); - } - } - } finally { - signalWebSocket.disconnect(); - jobManager.removeListener(queueListener); - } - - return queueListener.getQueues(); - } - - - private boolean shouldContinue() { - return !isCanceled(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 25c916c09e..f12f725bdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -12,6 +12,7 @@ import org.greenrobot.eventbus.ThreadMode; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.stickers.BlessedPacks; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -108,9 +109,12 @@ static final class Version { static final int REACTION_DATABASE_MIGRATION = 75; static final int REBUILD_MESSAGE_FTS_INDEX_2 = 76; static final int GLIDE_CACHE_CLEAR = 77; + static final int SYSTEM_NAME_RESYNC = 78; + static final int RECOVERY_PASSWORD_SYNC = 79; + static final int DECRYPTIONS_DRAINED = 80; } - public static final int CURRENT_VERSION = 77; + public static final int CURRENT_VERSION = 80; /** * This *must* be called after the {@link JobManager} has been instantiated, but *before* the call @@ -476,6 +480,18 @@ private static LinkedHashMap getMigrationJobs(@NonNull Co jobs.put(Version.GLIDE_CACHE_CLEAR, new ClearGlideCacheMigrationJob()); } + if (lastSeenVersion < Version.SYSTEM_NAME_RESYNC) { + jobs.put(Version.SYSTEM_NAME_RESYNC, new StorageServiceSystemNameMigrationJob()); + } + + if (lastSeenVersion < Version.RECOVERY_PASSWORD_SYNC) { + jobs.put(Version.RECOVERY_PASSWORD_SYNC, new AttributesMigrationJob()); + } + + if (lastSeenVersion < Version.DECRYPTIONS_DRAINED) { + jobs.put(Version.DECRYPTIONS_DRAINED, new DecryptionsDrainedMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/DecryptionsDrainedMigrationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/migrations/DecryptionsDrainedMigrationJob.kt new file mode 100644 index 0000000000..e06b5b2081 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/DecryptionsDrainedMigrationJob.kt @@ -0,0 +1,36 @@ +package org.thoughtcrime.securesms.migrations + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobs.PushDecryptDrainedJob + +/** + * Kicks off a job to notify the [org.thoughtcrime.securesms.messages.IncomingMessageObserver] when the decryption queue is empty. + */ +internal class DecryptionsDrainedMigrationJob( + parameters: Parameters = Parameters.Builder().build() +) : MigrationJob(parameters) { + + companion object { + val TAG = Log.tag(DecryptionsDrainedMigrationJob::class.java) + const val KEY = "DecryptionsDrainedMigrationJob" + } + + override fun getFactoryKey(): String = KEY + + override fun isUiBlocking(): Boolean = false + + override fun performMigration() { + ApplicationDependencies.getJobManager().add(PushDecryptDrainedJob()) + } + + override fun shouldRetry(e: Exception): Boolean = false + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): DecryptionsDrainedMigrationJob { + return DecryptionsDrainedMigrationJob(parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index 351ddbbc02..e5d072a42a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity; import org.thoughtcrime.securesms.maps.PlacePickerActivity; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewCache; import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Fragment; import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity; import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog; @@ -412,12 +413,16 @@ public static void selectContactInfo(Fragment fragment, int requestCode) { } public static void selectLocation(Fragment fragment, int requestCode, @ColorInt int chatColor) { - Permissions.with(fragment) - .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) - .ifNecessary() - .withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location)) - .onAllGranted(() -> PlacePickerActivity.startActivityForResultAtCurrentLocation(fragment, requestCode, chatColor)) - .execute(); + if (Permissions.hasAny(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)) { + PlacePickerActivity.startActivityForResultAtCurrentLocation(fragment, requestCode, chatColor); + } else { + Permissions.with(fragment) + .request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) + .ifNecessary() + .withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location)) + .onSomeGranted((permissions) -> PlacePickerActivity.startActivityForResultAtCurrentLocation(fragment, requestCode, chatColor)) + .execute(); + } } public static void selectGif(Fragment fragment, int requestCode, RecipientId id, MessageSendType sendType, boolean isForMms, CharSequence textTrimmed) { @@ -527,7 +532,8 @@ private void previewImageDraft(final @NonNull Slide slide) { false, false, MediaTable.Sorting.Newest, - slide.isVideoGif()); + slide.isVideoGif(), + new MediaIntentFactory.SharedElementArgs()); context.startActivity(MediaIntentFactory.create(context, args)); } } @@ -535,7 +541,10 @@ private void previewImageDraft(final @NonNull Slide slide) { private class ThumbnailClickListener implements View.OnClickListener { @Override public void onClick(View v) { - if (slide.isPresent()) previewImageDraft(slide.get()); + if (slide.isPresent()) { + MediaPreviewCache.INSTANCE.setDrawable(((ThumbnailView) v).getImageDrawable()); + previewImageDraft(slide.get()); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt index 1743717223..ad1f22784b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/OutgoingMessage.kt @@ -50,7 +50,7 @@ data class OutgoingMessage( val isEndSession: Boolean = false, val isIdentityVerified: Boolean = false, val isIdentityDefault: Boolean = false, - val scheduledDate: Long = -1, + val scheduledDate: Long = -1 ) { val isV2Group: Boolean = messageGroupContext != null && GroupV2UpdateMessageUtil.isGroupV2(messageGroupContext) @@ -344,7 +344,7 @@ data class OutgoingMessage( sentTimeMillis = sentTimeMillis, isIdentityVerified = true, isUrgent = false, - isSecure = true, + isSecure = true ) } @@ -358,7 +358,7 @@ data class OutgoingMessage( sentTimeMillis = sentTimeMillis, isIdentityDefault = true, isUrgent = false, - isSecure = true, + isSecure = true ) } @@ -373,7 +373,7 @@ data class OutgoingMessage( sentTimeMillis = sentTimeMillis, isEndSession = true, isUrgent = false, - isSecure = true, + isSecure = true ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt index c44a9dd5be..295fc44c7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt @@ -48,12 +48,19 @@ import kotlin.math.max */ class DefaultMessageNotifier(context: Application) : MessageNotifier { @Volatile private var visibleThread: ConversationId? = null + @Volatile private var lastDesktopActivityTimestamp: Long = -1 + @Volatile private var lastAudibleNotification: Long = -1 + @Volatile private var lastScheduledReminder: Long = 0 + @Volatile private var previousLockedStatus: Boolean = KeyCachingService.isLocked() - @Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy + @Volatile private var previousScreenLockState: Boolean = ScreenLockController.lockScreenAtStart + + @Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy + @Volatile private var previousState: NotificationState = NotificationState.EMPTY private val threadReminders: MutableMap = ConcurrentHashMap() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt index 3969b80939..c6557cf4bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt @@ -201,7 +201,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N ThreadBodyUtil.getFormattedBodyFor(context, record).body } else if (record.isStoryReaction()) { ThreadBodyUtil.getFormattedBodyFor(context, record).body - } else if (record.isPaymentNotification()) { + } else if (record.isPaymentNotification) { ThreadBodyUtil.getFormattedBodyFor(context, record).body } else { MentionUtil.updateBodyWithDisplayNames(context, record) ?: "" @@ -311,6 +311,8 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va context.getString(R.string.MessageNotifier_reacted_s_to_your_sticker, EMOJI_REPLACEMENT_STRING) } else if (record.isMms && record.isViewOnce) { context.getString(R.string.MessageNotifier_reacted_s_to_your_view_once_media, EMOJI_REPLACEMENT_STRING) + } else if (record.isPaymentNotification) { + context.getString(R.string.MessageNotifier_reacted_s_to_your_payment, EMOJI_REPLACEMENT_STRING) } else if (!bodyIsEmpty) { context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, body) } else if (record.isMediaMessage() && MediaUtil.isVideoType(getMessageContentType((record as MmsMessageRecord)))) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java index bbcade0270..378d821733 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/KbsRepository.java @@ -63,6 +63,26 @@ public Single> getToken(@Nullable String authorizatio }).subscribeOn(Schedulers.io()); } + /** + * Fetch and store a new KBS authorization. + */ + public void refreshAuthorization() throws IOException { + for (KbsEnclave enclave : KbsEnclaves.all()) { + KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave); + + try { + String authorization = kbs.getAuthorization(); + backupAuthToken(authorization); + } catch (NonSuccessfulResponseCodeException e) { + if (e.getCode() == 404) { + Log.i(TAG, "Enclave decommissioned, skipping", e); + } else { + throw e; + } + } + } + } + private @NonNull TokenData getTokenSync(@Nullable String authorization) throws IOException { TokenData firstKnownTokenData = null; @@ -101,7 +121,7 @@ public Single> getToken(@Nullable String authorizatio private static void backupAuthToken(String token) { final boolean tokenIsNew = SignalStore.kbsValues().appendAuthTokenToList(token); - if (tokenIsNew) { + if (tokenIsNew && SignalStore.kbsValues().hasPin()) { new BackupManager(ApplicationDependencies.getApplication()).dataChanged(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java index ac709e4559..15e0122d60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreActivity.java @@ -1,33 +1,35 @@ package org.thoughtcrime.securesms.pin; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; +import org.thoughtcrime.securesms.util.DynamicTheme; public final class PinRestoreActivity extends AppCompatActivity { - @Override - protected void attachBaseContext(@NonNull Context newBase) { - getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO); - super.attachBaseContext(newBase); - } + private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + dynamicTheme.onCreate(this); super.onCreate(savedInstanceState); setContentView(R.layout.pin_restore_activity); } + @Override + protected void onResume() { + super.onResume(); + dynamicTheme.onResume(this); + } + void navigateToPinCreation() { final Intent main = MainActivity.clearTop(this); final Intent createPin = CreateKbsPinActivity.getIntentForPinCreate(this); diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java index 1bdcb00877..a1af3fb44b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java @@ -36,7 +36,6 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil; import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate; import org.thoughtcrime.securesms.util.CommunicationActions; -import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; @@ -243,7 +242,7 @@ private void handleSuccess() { profile.putExtra("next_intent", main); startActivity(profile); } else { - RegistrationUtil.maybeMarkRegistrationComplete(requireContext()); + RegistrationUtil.maybeMarkRegistrationComplete(); ApplicationDependencies.getJobManager().add(new ProfileUploadJob()); startActivity(MainActivity.clearTop(activity)); } @@ -271,9 +270,6 @@ private void updateKeyboard(@NonNull PinKeyboardType keyboard) { private void enableAndFocusPinEntry() { pinEntry.setEnabled(true); pinEntry.setFocusable(true); - - if (pinEntry.requestFocus()) { - ServiceUtil.getInputMethodManager(pinEntry.getContext()).showSoftInput(pinEntry, 0); - } + ViewUtil.focusAndShowKeyboard(pinEntry); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java index cb275fd72a..82a13c9f73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreRepository.java @@ -5,6 +5,7 @@ import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob; import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob; import org.thoughtcrime.securesms.jobs.StorageSyncJob; import org.signal.core.util.Stopwatch; @@ -34,7 +35,11 @@ void submitPin(@NonNull String pin, @NonNull TokenData tokenData, @NonNull Callb ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN); stopwatch.split("AccountRestore"); - ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10)); + ApplicationDependencies + .getJobManager() + .startChain(new StorageSyncJob()) + .then(new NewRegistrationUsernameSyncJob()) + .enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10)); stopwatch.split("ContactRestore"); stopwatch.stop(TAG); diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java index c056ccff3e..482cf5a6c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.Locale; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -44,7 +43,8 @@ public final class PinState { public static synchronized void onRegistration(@NonNull Context context, @Nullable KbsPinData kbsData, @Nullable String pin, - boolean hasPinToRestore) + boolean hasPinToRestore, + boolean setRegistrationLockEnabled) { Log.i(TAG, "onRegistration()"); @@ -57,9 +57,13 @@ public static synchronized void onRegistration(@NonNull Context context, TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis()); TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL); } else if (kbsData != null && pin != null) { - Log.i(TAG, "Registration Lock V2"); - TextSecurePreferences.setV1RegistrationLockEnabled(context, false); - SignalStore.kbsValues().setV2RegistrationLockEnabled(true); + if (setRegistrationLockEnabled) { + Log.i(TAG, "Registration Lock V2"); + TextSecurePreferences.setV1RegistrationLockEnabled(context, false); + SignalStore.kbsValues().setV2RegistrationLockEnabled(true); + } else { + Log.i(TAG, "ReRegistration Skip SMS"); + } SignalStore.kbsValues().setKbsMasterKey(kbsData, pin); SignalStore.pinValues().resetPinReminders(); resetPinRetryCount(context, pin); @@ -130,6 +134,7 @@ public static synchronized void onPinChangedOrCreated(@NonNull Context context, bestEffortRefreshAttributes(); } else { Log.i(TAG, "Not the first time setting a PIN. Enclave: " + kbsEnclave.getEnclaveName()); + ApplicationDependencies.getJobManager().add(new RefreshAttributesJob()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java index ed8f121cee..2509c32643 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditSelfProfileRepository.java @@ -145,7 +145,7 @@ public void uploadProfile(@NonNull ProfileName profileName, .then(Arrays.asList(new MultiDeviceProfileKeyUpdateJob(), new MultiDeviceProfileContentUpdateJob())) .enqueue(); - RegistrationUtil.maybeMarkRegistrationComplete(context); + RegistrationUtil.maybeMarkRegistrationComplete(); if (avatar != null) { SignalStore.misc().markHasEverHadAnAvatar(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/pnp/WhoCanSeeMyPhoneNumberFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/pnp/WhoCanSeeMyPhoneNumberFragment.kt index 68661bcbe2..20c5a1fce1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/pnp/WhoCanSeeMyPhoneNumberFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/pnp/WhoCanSeeMyPhoneNumberFragment.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.profiles.edit.pnp import android.os.Bundle +import androidx.core.content.ContextCompat import androidx.fragment.app.setFragmentResult import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -56,17 +57,26 @@ class WhoCanSeeMyPhoneNumberFragment : DSLSettingsFragment( return configure { radioPref( title = DSLSettingsText.from(R.string.PhoneNumberPrivacy_everyone), - summary = DSLSettingsText.from(R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has), isChecked = state == WhoCanSeeMyPhoneNumberState.EVERYONE, onClick = { viewModel.onEveryoneCanSeeMyPhoneNumberSelected() } ) radioPref( title = DSLSettingsText.from(R.string.PhoneNumberPrivacy_nobody), - summary = DSLSettingsText.from(R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal), isChecked = state == WhoCanSeeMyPhoneNumberState.NOBODY, onClick = { viewModel.onNobodyCanSeeMyPhoneNumberSelected() } ) + + textPref( + title = DSLSettingsText.from( + when (state) { + WhoCanSeeMyPhoneNumberState.EVERYONE -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + WhoCanSeeMyPhoneNumberState.NOBODY -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + }, + DSLSettingsText.TextAppearanceModifier(R.style.Signal_Text_BodyMedium), + DSLSettingsText.ColorModifier(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant)) + ) + ) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java index b3cd221559..2e0c139f1d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java @@ -24,6 +24,8 @@ import com.google.android.material.snackbar.Snackbar; import org.signal.core.util.logging.Log; +import org.signal.libsignal.usernames.BaseUsernameException; +import org.signal.libsignal.usernames.Username; import org.thoughtcrime.securesms.AvatarPreviewActivity; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; @@ -43,6 +45,7 @@ import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; +import org.whispersystems.util.Base64UrlSafe; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -235,13 +238,14 @@ private void presentProfileName(@Nullable ProfileName profileName) { private void presentUsername(@Nullable String username) { if (username == null || username.isEmpty()) { binding.manageProfileUsername.setText(R.string.ManageProfileFragment_username); + binding.manageProfileUsernameSubtitle.setText(R.string.ManageProfileFragment_your_username); binding.manageProfileUsernameShare.setVisibility(View.GONE); } else { binding.manageProfileUsername.setText(username); try { - binding.manageProfileUsernameSubtitle.setText(getString(R.string.signal_me_username_url_no_scheme, URLEncoder.encode(username, StandardCharsets.UTF_8.toString()))); - } catch (UnsupportedEncodingException e) { + binding.manageProfileUsernameSubtitle.setText(getString(R.string.signal_me_username_url_no_scheme, Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username)))); + } catch (BaseUsernameException e) { Log.w(TAG, "Could not format username link", e); binding.manageProfileUsernameSubtitle.setText(R.string.ManageProfileFragment_your_username); } @@ -319,12 +323,10 @@ private void onUserConfirmedUsernameDeletion() { private void handleUsernameDeletionResult(@NonNull UsernameEditRepository.UsernameDeleteResult usernameDeleteResult) { switch (usernameDeleteResult) { case SUCCESS: - // TODO [alex] - Final copy - Snackbar.make(requireView(), R.string.preferences_success, Snackbar.LENGTH_SHORT).show(); + Snackbar.make(requireView(), R.string.ManageProfileFragment__username_deleted, Snackbar.LENGTH_SHORT).show(); break; case NETWORK_ERROR: - // TODO [alex] - Final copy - Snackbar.make(requireView(), R.string.error, Snackbar.LENGTH_SHORT).show(); + Snackbar.make(requireView(), R.string.ManageProfileFragment__couldnt_delete_username, Snackbar.LENGTH_SHORT).show(); break; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditRepository.java index c567e6622f..7feb848e8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditRepository.java @@ -5,17 +5,23 @@ import org.signal.core.util.Result; import org.signal.core.util.logging.Log; +import org.signal.libsignal.usernames.BaseUsernameException; +import org.signal.libsignal.usernames.Username; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.UsernameUtil; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReservedException; import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException; import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException; import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse; +import org.whispersystems.util.Base64UrlSafe; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; @@ -30,12 +36,12 @@ class UsernameEditRepository { this.accountManager = ApplicationDependencies.getSignalServiceAccountManager(); } - @NonNull Single> reserveUsername(@NonNull String nickname) { + @NonNull Single> reserveUsername(@NonNull String nickname) { return Single.fromCallable(() -> reserveUsernameInternal(nickname)).subscribeOn(Schedulers.io()); } - @NonNull Single confirmUsername(@NonNull ReserveUsernameResponse reserveUsernameResponse) { - return Single.fromCallable(() -> confirmUsernameInternal(reserveUsernameResponse)).subscribeOn(Schedulers.io()); + @NonNull Single confirmUsername(@NonNull UsernameState.Reserved reserved) { + return Single.fromCallable(() -> confirmUsernameInternal(reserved)).subscribeOn(Schedulers.io()); } @NonNull Single deleteUsername() { @@ -43,11 +49,28 @@ class UsernameEditRepository { } @WorkerThread - private @NonNull Result reserveUsernameInternal(@NonNull String nickname) { + private @NonNull Result reserveUsernameInternal(@NonNull String nickname) { try { - ReserveUsernameResponse username = accountManager.reserveUsername(nickname); + List candidates = Username.generateCandidates(nickname, UsernameUtil.MIN_LENGTH, UsernameUtil.MAX_LENGTH); + List hashes = new ArrayList<>(); + + for (String candidate : candidates) { + byte[] hash = Username.hash(candidate); + hashes.add(Base64UrlSafe.encodeBytesWithoutPadding(hash)); + } + + ReserveUsernameResponse response = accountManager.reserveUsername(hashes); + int hashIndex = hashes.indexOf(response.getUsernameHash()); + if (hashIndex == -1) { + Log.w(TAG, "[reserveUsername] The response hash could not be found in our set of hashes."); + return Result.failure(UsernameSetResult.CANDIDATE_GENERATION_ERROR); + } + Log.i(TAG, "[reserveUsername] Successfully reserved username."); - return Result.success(username); + return Result.success(new UsernameState.Reserved(candidates.get(hashIndex), response)); + } catch (BaseUsernameException e) { + Log.w(TAG, "[reserveUsername] An error occurred while generating candidates."); + return Result.failure(UsernameSetResult.CANDIDATE_GENERATION_ERROR); } catch (UsernameTakenException e) { Log.w(TAG, "[reserveUsername] Username taken."); return Result.failure(UsernameSetResult.USERNAME_UNAVAILABLE); @@ -61,11 +84,11 @@ class UsernameEditRepository { } @WorkerThread - private @NonNull UsernameSetResult confirmUsernameInternal(@NonNull ReserveUsernameResponse reserveUsernameResponse) { + private @NonNull UsernameSetResult confirmUsernameInternal(@NonNull UsernameState.Reserved reserved) { try { - accountManager.confirmUsername(reserveUsernameResponse); - SignalDatabase.recipients().setUsername(Recipient.self().getId(), reserveUsernameResponse.getUsername()); - ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob()); + accountManager.confirmUsername(reserved.getUsername(), reserved.getReserveUsernameResponse()); + SignalDatabase.recipients().setUsername(Recipient.self().getId(), reserved.getUsername()); + SignalStore.phoneNumberPrivacy().clearUsernameOutOfSync(); Log.i(TAG, "[confirmUsername] Successfully reserved username."); return UsernameSetResult.SUCCESS; } catch (UsernameTakenException e) { @@ -85,6 +108,7 @@ class UsernameEditRepository { try { accountManager.deleteUsername(); SignalDatabase.recipients().setUsername(Recipient.self().getId(), null); + SignalStore.phoneNumberPrivacy().clearUsernameOutOfSync(); Log.i(TAG, "[deleteUsername] Successfully deleted the username."); return UsernameDeleteResult.SUCCESS; } catch (IOException e) { @@ -94,7 +118,7 @@ class UsernameEditRepository { } enum UsernameSetResult { - SUCCESS, USERNAME_UNAVAILABLE, USERNAME_INVALID, NETWORK_ERROR + SUCCESS, USERNAME_UNAVAILABLE, USERNAME_INVALID, NETWORK_ERROR, CANDIDATE_GENERATION_ERROR } enum UsernameDeleteResult { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java index 68a50a55b7..64db801637 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java @@ -110,7 +110,7 @@ void onUsernameSubmitted() { uiState.update(state -> new State(ButtonState.SUBMIT_LOADING, UsernameStatus.NONE, state.usernameState)); - Disposable confirmUsernameDisposable = repo.confirmUsername(((UsernameState.Reserved) usernameState).getReserveUsernameResponse()) + Disposable confirmUsernameDisposable = repo.confirmUsername((UsernameState.Reserved) usernameState) .subscribe(result -> { String nickname = usernameState.getNickname(); @@ -128,6 +128,7 @@ void onUsernameSubmitted() { onNicknameUpdated(nickname); } break; + case CANDIDATE_GENERATION_ERROR: case USERNAME_UNAVAILABLE: uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.TAKEN, state.usernameState)); events.onNext(Event.SUBMIT_FAIL_TAKEN); @@ -181,8 +182,8 @@ private void onNicknameChanged(@NonNull String nickname) { uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, UsernameState.Loading.INSTANCE)); Disposable reserveDisposable = repo.reserveUsername(nickname).subscribe(result -> { result.either( - reserveUsernameJsonResponse -> { - uiState.update(state -> new State(ButtonState.SUBMIT, UsernameStatus.NONE, new UsernameState.Reserved(reserveUsernameJsonResponse))); + reserved -> { + uiState.update(state -> new State(ButtonState.SUBMIT, UsernameStatus.NONE, reserved)); return null; }, failure -> { @@ -199,6 +200,10 @@ private void onNicknameChanged(@NonNull String nickname) { uiState.update(state -> new State(ButtonState.SUBMIT, UsernameStatus.NONE, UsernameState.NoUsername.INSTANCE)); events.onNext(Event.NETWORK_FAILURE); break; + case CANDIDATE_GENERATION_ERROR: + // TODO -- Retry + uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.TAKEN, UsernameState.NoUsername.INSTANCE)); + break; } return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEducationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEducationFragment.kt index 96b3ed9af1..8b784f91ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEducationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEducationFragment.kt @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.databinding.UsernameEducationFragmentBinding import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.megaphone.Megaphones +import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.navigation.safeNavigate /** @@ -25,7 +26,7 @@ class UsernameEducationFragment : Fragment(R.layout.username_education_fragment) } binding.usernameEducationLearnMore.setOnClickListener { - // TODO [alex] -- Launch "Learn More" page. + CommunicationActions.openBrowserLink(requireContext(), getString(R.string.username_support_url)) } binding.continueButton.setOnClickListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameShareBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameShareBottomSheet.kt index 5ef969ea80..1b88b37162 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameShareBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameShareBottomSheet.kt @@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.setFragmentResult import androidx.navigation.fragment.findNavController import org.signal.core.util.DimensionUnit +import org.signal.libsignal.usernames.Username import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter @@ -19,8 +20,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.FragmentResultContract import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.Util -import java.net.URLEncoder -import java.nio.charset.StandardCharsets +import org.whispersystems.util.Base64UrlSafe /** * Allows the user to either share their username directly or to copy it to their clipboard. @@ -50,7 +50,7 @@ class UsernameShareBottomSheet : DSLSettingsBottomSheetFragment() { DSLSettingsText.TextAppearanceModifier(R.style.Signal_Text_BodyMedium), DSLSettingsText.CenterModifier, DSLSettingsText.ColorModifier( - ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant), + ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant) ) ) ) @@ -71,7 +71,7 @@ class UsernameShareBottomSheet : DSLSettingsBottomSheetFragment() { customPref( CopyButton.Model( - text = getString(R.string.signal_me_username_url, URLEncoder.encode(username, StandardCharsets.UTF_8.toString())), + text = getString(R.string.signal_me_username_url, Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))), onClick = { copyToClipboard(it) } @@ -82,7 +82,7 @@ class UsernameShareBottomSheet : DSLSettingsBottomSheetFragment() { customPref( ShareButton.Model( - text = getString(R.string.signal_me_username_url, URLEncoder.encode(username, StandardCharsets.UTF_8.toString())), + text = getString(R.string.signal_me_username_url, Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))), onClick = { openShareSheet(it.text) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameState.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameState.kt index 1a0c3e3b46..a4b2ebcee7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameState.kt @@ -17,10 +17,9 @@ sealed class UsernameState { object NoUsername : UsernameState() data class Reserved( + override val username: String, val reserveUsernameResponse: ReserveUsernameResponse - ) : UsernameState() { - override val username: String? = reserveUsernameResponse.username - } + ) : UsernameState() data class Set( override val username: String diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java b/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java index ea7c44a42a..93eb07de4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/push/AccountManagerFactory.java @@ -22,11 +22,11 @@ public class AccountManagerFactory { public static @NonNull SignalServiceAccountManager createAuthenticated(@NonNull Context context, @NonNull ACI aci, @NonNull PNI pni, - @NonNull String number, + @NonNull String e164, int deviceId, @NonNull String password) { - if (ApplicationDependencies.getSignalServiceNetworkAccess().isCensored(number)) { + if (ApplicationDependencies.getSignalServiceNetworkAccess().isCensored(e164)) { SignalExecutors.BOUNDED.execute(() -> { try { ProviderInstaller.installIfNeeded(context); @@ -36,10 +36,10 @@ public class AccountManagerFactory { }); } - return new SignalServiceAccountManager(ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(number), + return new SignalServiceAccountManager(ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(e164), aci, pni, - number, + e164, deviceId, password, BuildConfig.SIGNAL_AGENT, @@ -51,11 +51,11 @@ public class AccountManagerFactory { * Should only be used during registration when you haven't yet been assigned an ACI. */ public static @NonNull SignalServiceAccountManager createUnauthenticated(@NonNull Context context, - @NonNull String number, + @NonNull String e164, int deviceId, @NonNull String password) { - if (new SignalServiceNetworkAccess(context).isCensored(number)) { + if (new SignalServiceNetworkAccess(context).isCensored(e164)) { SignalExecutors.BOUNDED.execute(() -> { try { ProviderInstaller.installIfNeeded(context); @@ -65,10 +65,10 @@ public class AccountManagerFactory { }); } - return new SignalServiceAccountManager(ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(number), + return new SignalServiceAccountManager(ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(e164), null, null, - number, + e164, deviceId, password, BuildConfig.SIGNAL_AGENT, diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt index a5578ab855..19cf163cf2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.push import android.content.Context +import com.google.i18n.phonenumbers.PhoneNumberUtil import okhttp3.CipherSuite import okhttp3.ConnectionSpec import okhttp3.Interceptor @@ -14,7 +15,6 @@ import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor import org.thoughtcrime.securesms.net.Network import org.thoughtcrime.securesms.net.RemoteDeprecationDetectorInterceptor import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor -import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.util.Base64 import org.whispersystems.signalservice.api.push.TrustStore import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl @@ -174,7 +174,7 @@ open class SignalServiceNetworkAccess(context: Context) { StandardUserAgentInterceptor(), RemoteDeprecationDetectorInterceptor(), DeprecatedClientPreventionInterceptor(), - DeviceTransferBlockingInterceptor.getInstance(), + DeviceTransferBlockingInterceptor.getInstance() ) private val zkGroupServerPublicParams: ByteArray = try { @@ -188,7 +188,7 @@ open class SignalServiceNetworkAccess(context: Context) { HostConfig(HTTPS_ANDROID_CLIENTS_GOOGLE_COM, G_HOST, PLAY_CONNECTION_SPEC), HostConfig(HTTPS_CLIENTS_3_GOOGLE_COM, G_HOST, GMAPS_CONNECTION_SPEC), HostConfig(HTTPS_CLIENTS_4_GOOGLE_COM, G_HOST, GMAPS_CONNECTION_SPEC), - HostConfig(HTTPS_INBOX_GOOGLE_COM, G_HOST, GMAIL_CONNECTION_SPEC), + HostConfig(HTTPS_INBOX_GOOGLE_COM, G_HOST, GMAIL_CONNECTION_SPEC) ) private val fUrls = arrayOf(HTTPS_CDN_SSTATIC_NET, HTTPS_GITHUB_GITHUBASSETS_COM, HTTPS_PINTEREST_COM, HTTPS_OPEN_SCDN_CO, HTTPS_WWW_REDDITSTATIC_COM) @@ -197,7 +197,7 @@ open class SignalServiceNetworkAccess(context: Context) { fUrls.map { SignalServiceUrl(it, F_SERVICE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), mapOf( 0 to fUrls.map { SignalCdnUrl(it, F_CDN_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), - 2 to fUrls.map { SignalCdnUrl(it, F_CDN2_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), + 2 to fUrls.map { SignalCdnUrl(it, F_CDN2_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray() ), fUrls.map { SignalKeyBackupServiceUrl(it, F_KBS_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), fUrls.map { SignalStorageUrl(it, F_STORAGE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), @@ -206,30 +206,30 @@ open class SignalServiceNetworkAccess(context: Context) { Network.socketFactory, Network.proxySelectorForSocks, Network.dns, - zkGroupServerPublicParams, + zkGroupServerPublicParams ) private val censorshipConfiguration: Map = mapOf( COUNTRY_CODE_EGYPT to buildGConfiguration( - listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_EG, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs, + listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_EG, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs ), COUNTRY_CODE_UAE to buildGConfiguration( - listOf(HostConfig(HTTPS_WWW_GOOGLE_AE, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs, + listOf(HostConfig(HTTPS_WWW_GOOGLE_AE, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs ), COUNTRY_CODE_OMAN to buildGConfiguration( - listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_OM, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs, + listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_OM, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs ), COUNTRY_CODE_QATAR to buildGConfiguration( - listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_QA, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs, + listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_QA, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs ), COUNTRY_CODE_UZBEKISTAN to buildGConfiguration( - listOf(HostConfig(HTTPS_WWW_GOOGLE_CO_UZ, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs, + listOf(HostConfig(HTTPS_WWW_GOOGLE_CO_UZ, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs ), COUNTRY_CODE_UKRAINE to buildGConfiguration( - listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_UA, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs, + listOf(HostConfig(HTTPS_WWW_GOOGLE_COM_UA, G_HOST, GMAIL_CONNECTION_SPEC)) + baseGHostConfigs ), COUNTRY_CODE_IRAN to fConfig, - COUNTRY_CODE_CUBA to fConfig, + COUNTRY_CODE_CUBA to fConfig ) private val defaultCensoredConfiguration: SignalServiceConfiguration = buildGConfiguration(baseGHostConfigs) @@ -241,7 +241,7 @@ open class SignalServiceNetworkAccess(context: Context) { COUNTRY_CODE_QATAR, COUNTRY_CODE_IRAN, COUNTRY_CODE_CUBA, - COUNTRY_CODE_UZBEKISTAN, + COUNTRY_CODE_UZBEKISTAN ) open val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration( @@ -257,19 +257,19 @@ open class SignalServiceNetworkAccess(context: Context) { Network.socketFactory, Network.proxySelectorForSocks, Network.dns, - zkGroupServerPublicParams, + zkGroupServerPublicParams ) open fun getConfiguration(): SignalServiceConfiguration { return getConfiguration(SignalStore.account().e164) } - open fun getConfiguration(localNumber: String?): SignalServiceConfiguration { - if (localNumber == null) { + open fun getConfiguration(e164: String?): SignalServiceConfiguration { + if (e164.isNullOrEmpty()) { return uncensoredConfiguration } - val countryCode: Int = PhoneNumberFormatter.getLocalCountryCode() + val countryCode: Int = PhoneNumberUtil.getInstance().parse(e164, null).countryCode return when (SignalStore.settings().censorshipCircumventionEnabled) { SettingsValues.CensorshipCircumventionEnabled.ENABLED -> { @@ -323,7 +323,7 @@ open class SignalServiceNetworkAccess(context: Context) { Network.socketFactory, Network.proxySelectorForSocks, Network.dns, - zkGroupServerPublicParams, + zkGroupServerPublicParams ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java index 35e40eb0ef..6e37a56c05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference; import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.subjects.BehaviorSubject; public final class LiveRecipient { @@ -41,15 +42,17 @@ public final class LiveRecipient { private final LiveData observableLiveDataResolved; private final Set observers; private final Observer foreverObserver; - private final AtomicReference recipient; - private final RecipientTable recipientTable; - private final GroupTable groupDatabase; - private final DistributionListTables distributionListTables; - private final MutableLiveData refreshForceNotify; + private final AtomicReference recipient; + private final RecipientTable recipientTable; + private final GroupTable groupDatabase; + private final DistributionListTables distributionListTables; + private final MutableLiveData refreshForceNotify; + private final BehaviorSubject subject; LiveRecipient(@NonNull Context context, @NonNull Recipient defaultRecipient) { this.context = context.getApplicationContext(); this.liveData = new MutableLiveData<>(defaultRecipient); + this.subject = BehaviorSubject.createDefault(defaultRecipient); this.recipient = new AtomicReference<>(defaultRecipient); this.recipientTable = SignalDatabase.recipients(); this.groupDatabase = SignalDatabase.groups(); @@ -80,6 +83,13 @@ public final class LiveRecipient { return recipient.get(); } + /** + * @return An rx-flavored {@link Observable}. + */ + public @NonNull Observable observable() { + return subject.distinctUntilChanged(Recipient::hasSameContent); + } + /** * Watch the recipient for changes. The callback will only be invoked if the provided lifecycle is * in a valid state. No need to remove the observer. If you do wish to remove the observer (if, @@ -97,19 +107,6 @@ public void removeObservers(@NonNull LifecycleOwner owner) { ThreadUtil.runOnMain(() -> observableLiveData.removeObservers(owner)); } - public Observable asObservable() { - return Observable.create(emitter -> { - Recipient current = recipient.get(); - if (current != null && current.getId() != RecipientId.UNKNOWN) { - emitter.onNext(current); - } - - RecipientForeverObserver foreverObserver = emitter::onNext; - observeForever(foreverObserver); - emitter.setCancellable(() -> removeForeverObserver(foreverObserver)); - }); - } - /** * Watch the recipient for changes. The callback could be invoked at any time. You MUST call * {@link #removeForeverObserver(RecipientForeverObserver)} when finished. You should use @@ -243,6 +240,7 @@ public void refresh(@NonNull RecipientId id) { synchronized void set(@NonNull Recipient recipient) { this.recipient.set(recipient); this.liveData.postValue(recipient); + this.subject.onNext(recipient); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 49737b41e2..1b3cb2c2f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -1047,7 +1047,11 @@ public boolean isForceSmsSelection() { } public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() { - return unidentifiedAccessMode; + if (getPni().isPresent() && getPni().equals(getServiceId())) { + return UnidentifiedAccessMode.DISABLED; + } else { + return unidentifiedAccessMode; + } } public @Nullable ChatWallpaper getWallpaper() { @@ -1291,7 +1295,6 @@ public boolean hasSameContent(@NonNull Recipient other) { expireMessages == other.expireMessages && Objects.equals(profileAvatarFileDetails, other.profileAvatarFileDetails) && profileSharing == other.profileSharing && - lastProfileFetch == other.lastProfileFetch && forceSmsSelection == other.forceSmsSelection && Objects.equals(serviceId, other.serviceId) && Objects.equals(username, other.username) && diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index d48ebb46d4..7a53daaa23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -206,16 +206,12 @@ public static void unblock(@NonNull Recipient recipient) { if (!isBlockable(recipient)) { throw new AssertionError("Recipient is not blockable!"); } - Log.i(TAG, "Unblocking " + recipient.getId() + " (group: " + recipient.isGroup() + ")"); + Log.i(TAG, "Unblocking " + recipient.getId() + " (group: " + recipient.isGroup() + ")", new Throwable()); SignalDatabase.recipients().setBlocked(recipient.getId(), false); SignalDatabase.recipients().setProfileSharing(recipient.getId(), true); ApplicationDependencies.getJobManager().add(new MultiDeviceBlockedUpdateJob()); StorageSyncHelper.scheduleSyncForDataChange(); - - if (recipient.hasServiceId()) { - ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(recipient.getId())); - } } @WorkerThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/PushChallengeRequest.java b/app/src/main/java/org/thoughtcrime/securesms/registration/PushChallengeRequest.java index bb1b8d5c70..25a90f35f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/PushChallengeRequest.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/PushChallengeRequest.java @@ -7,6 +7,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import java.io.IOException; @@ -27,25 +28,24 @@ public final class PushChallengeRequest { * * @param accountManager Account manager to request the push from. * @param fcmToken Optional FCM token. If not present will return absent. - * @param e164number Local number. + * @param sessionId Local number. * @param timeoutMs Timeout in milliseconds * @return Either returns a challenge, or absent. */ @WorkerThread public static Optional getPushChallengeBlocking(@NonNull SignalServiceAccountManager accountManager, + @NonNull String sessionId, @NonNull Optional fcmToken, - @NonNull String e164number, long timeoutMs) { - if (!fcmToken.isPresent()) { + if (fcmToken.isEmpty() || fcmToken.get().isEmpty()) { Log.w(TAG, "Push challenge not requested, as no FCM token was present"); return Optional.empty(); } long startTime = System.currentTimeMillis(); Log.i(TAG, "Requesting a push challenge"); - - Request request = new Request(accountManager, fcmToken.get(), e164number, timeoutMs); + Request request = new Request(accountManager, fcmToken.get(), sessionId, timeoutMs); Optional challenge = request.requestAndReceiveChallengeBlocking(); @@ -69,19 +69,19 @@ public static class Request { private final AtomicReference challenge; private final SignalServiceAccountManager accountManager; private final String fcmToken; - private final String e164number; + private final String sessionId; private final long timeoutMs; private Request(@NonNull SignalServiceAccountManager accountManager, @NonNull String fcmToken, - @NonNull String e164number, + @NonNull String sessionId, long timeoutMs) { this.latch = new CountDownLatch(1); this.challenge = new AtomicReference<>(); this.accountManager = accountManager; this.fcmToken = fcmToken; - this.e164number = e164number; + this.sessionId = sessionId; this.timeoutMs = timeoutMs; } @@ -91,7 +91,7 @@ private Optional requestAndReceiveChallengeBlocking() { eventBus.register(this); try { - accountManager.requestRegistrationPushChallenge(fcmToken, e164number); + accountManager.requestRegistrationPushChallenge(sessionId, fcmToken); latch.await(timeoutMs, TimeUnit.MILLISECONDS); @@ -117,5 +117,9 @@ static class PushChallengeEvent { PushChallengeEvent(String challenge) { this.challenge = challenge; } + + public String getChallenge() { + return challenge; + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt index 8ae01102dd..41a7db86d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationData.kt @@ -9,8 +9,9 @@ data class RegistrationData( val registrationId: Int, val profileKey: ProfileKey, val fcmToken: String?, - val pniRegistrationId: Int + val pniRegistrationId: Int, + val recoveryPassword: String? ) { - val isFcm: Boolean = fcmToken != null - val isNotFcm: Boolean = fcmToken == null + val isNotFcm: Boolean = fcmToken.isNullOrBlank() + val isFcm: Boolean = !isNotFcm } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationNavigationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationNavigationActivity.java index c4822da5cb..1f5b7a0c06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationNavigationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationNavigationActivity.java @@ -12,7 +12,8 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; - +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; +import org.thoughtcrime.securesms.util.DynamicTheme; public final class RegistrationNavigationActivity extends PassphraseRequiredActivity { @@ -20,6 +21,8 @@ public final class RegistrationNavigationActivity extends PassphraseRequiredActi public static final String RE_REGISTRATION_EXTRA = "re_registration"; + private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); + private SmsRetrieverReceiver smsRetrieverReceiver; private RegistrationViewModel viewModel; @@ -42,6 +45,8 @@ public static Intent newIntentForReRegistration(@NonNull Context context) { @Override protected void onCreate(Bundle savedInstanceState, boolean ready) { + dynamicTheme.onCreate(this); + viewModel = new ViewModelProvider(this, new RegistrationViewModel.Factory(this, isReregister(getIntent()))).get(RegistrationViewModel.class); setContentView(R.layout.activity_registration_navigation); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index eb94a60ae2..e30360dee7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.registration; import android.app.Application; +import android.app.backup.BackupManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -41,6 +42,7 @@ import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.ServiceResponse; +import org.whispersystems.signalservice.internal.push.BackupAuthCheckProcessor; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import java.io.IOException; @@ -95,12 +97,13 @@ public int getPniRegistrationId() { } public Single> registerAccount(@NonNull RegistrationData registrationData, - @NonNull VerifyResponse response) + @NonNull VerifyResponse response, + boolean setRegistrationLockEnabled) { return Single.>fromCallable(() -> { try { String pin = response.getPin(); - registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData()); + registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData(), setRegistrationLockEnabled); if (pin != null && !pin.isEmpty()) { PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType()); @@ -124,7 +127,8 @@ public Single> registerAccount(@NonNull Registra private void registerAccountInternal(@NonNull RegistrationData registrationData, @NonNull VerifyAccountResponse response, @Nullable String pin, - @Nullable KbsPinData kbsData) + @Nullable KbsPinData kbsData, + boolean setRegistrationLockEnabled) throws IOException { ACI aci = ACI.parseOrThrow(response.getUuid()); @@ -170,9 +174,13 @@ private void registerAccountInternal(@NonNull RegistrationData registrationData, SignalStore.account().setServicePassword(registrationData.getPassword()); SignalStore.account().setRegistered(true); TextSecurePreferences.setPromptedPushRegistration(context, true); + TextSecurePreferences.setUnauthorizedReceived(context, false); NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID); - PinState.onRegistration(context, kbsData, pin, hasPin); + PinState.onRegistration(context, kbsData, pin, hasPin, setRegistrationLockEnabled); + + ApplicationDependencies.closeConnections(); + ApplicationDependencies.getIncomingMessageObserver(); } private void generateAndRegisterPreKeys(@NonNull ServiceIdType serviceIdType, @@ -209,4 +217,16 @@ private void saveOwnIdentityKey(@NonNull RecipientId selfId, @NonNull SignalServ return null; } + + public Single getKbsAuthCredential(@NonNull RegistrationData registrationData, List usernamePasswords) { + SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword()); + + return accountManager.checkBackupAuthCredentials(registrationData.getE164(), usernamePasswords) + .map(BackupAuthCheckProcessor::new) + .doOnSuccess(processor -> { + if (SignalStore.kbsValues().removeAuthTokens(processor.getInvalid())) { + new BackupManager(context).dataChanged(); + } + }); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationSessionProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationSessionProcessor.kt new file mode 100644 index 0000000000..b24143df8a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationSessionProcessor.kt @@ -0,0 +1,194 @@ +package org.thoughtcrime.securesms.registration + +import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException +import org.whispersystems.signalservice.api.push.exceptions.ExternalServiceFailureException +import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException +import org.whispersystems.signalservice.api.push.exceptions.InvalidTransportModeException +import org.whispersystems.signalservice.api.push.exceptions.MustRequestNewCodeException +import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException +import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException +import org.whispersystems.signalservice.api.push.exceptions.RateLimitException +import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException +import org.whispersystems.signalservice.api.util.Preconditions +import org.whispersystems.signalservice.internal.ServiceResponse +import org.whispersystems.signalservice.internal.ServiceResponseProcessor +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse +import org.whispersystems.signalservice.internal.push.RegistrationSessionState +import kotlin.time.Duration.Companion.seconds + +/** + * Makes the server's response describing the state of the registration session as digestible as possible. + */ +sealed class RegistrationSessionProcessor(response: ServiceResponse) : ServiceResponseProcessor(response) { + + companion object { + const val CAPTCHA_KEY = "captcha" + const val PUSH_CHALLENGE_KEY = "pushChallenge" + val REQUESTABLE_INFORMATION = listOf(PUSH_CHALLENGE_KEY, CAPTCHA_KEY) + } + + public override fun rateLimit(): Boolean { + return error is RateLimitException + } + + public override fun getError(): Throwable? { + return super.getError() + } + + fun captchaRequired(excludedChallenges: List): Boolean { + return response.status == 402 || (hasResult() && CAPTCHA_KEY == getChallenge(excludedChallenges)) + } + + fun pushChallengeTimedOut(): Boolean { + if (response.result.isEmpty) { + return false + } else { + val state: RegistrationSessionState = response.result.get().state ?: return false + return state.pushChallengeTimedOut + } + } + + fun isTokenRejected(): Boolean { + return error is TokenNotAcceptedException + } + + fun isImpossibleNumber(): Boolean { + return error is ImpossiblePhoneNumberException + } + + fun isNonNormalizedNumber(): Boolean { + return error is NonNormalizedPhoneNumberException + } + + fun getRateLimit(): Long { + Preconditions.checkState(error is RateLimitException, "This can only be called when isRateLimited()") + return (error as RateLimitException).retryAfterMilliseconds.orElse(-1L) + } + + /** + * The soonest time at which the server will accept a request to send a new code via SMS. + * @return a unix timestamp in milliseconds, or 0 to represent null + */ + fun getNextCodeViaSmsAttempt(): Long { + return deriveTimestamp(result.body.nextSms) + } + + /** + * The soonest time at which the server will accept a request to send a new code via a voice call. + * @return a unix timestamp in milliseconds, or 0 to represent null + */ + fun getNextCodeViaCallAttempt(): Long { + return deriveTimestamp(result.body.nextCall) + } + + fun canSubmitProofImmediately(): Boolean { + Preconditions.checkState(hasResult(), "This can only be called when result is present!") + return 0 == result.body.nextVerificationAttempt + } + + /** + * The soonest time at which the server will accept a submission of proof of ownership. + * @return a unix timestamp in milliseconds, or 0 to represent null + */ + fun getNextProofSubmissionAttempt(): Long { + Preconditions.checkState(hasResult(), "This can only be called when result is present!") + return deriveTimestamp(result.body.nextVerificationAttempt) + } + + fun exhaustedVerificationCodeAttempts(): Boolean { + return rateLimit() && getRateLimit() == -1L + } + + fun isInvalidSession(): Boolean { + return error is NoSuchSessionException + } + + fun getSessionId(): String { + Preconditions.checkState(hasResult(), "This can only be called when result is present!") + return result.body.id + } + + fun isAllowedToRequestCode(): Boolean { + Preconditions.checkState(hasResult(), "This can only be called when result is present!") + return result.body.allowedToRequestCode + } + + /** + * Parse the response body for the server requested challenges that the client may submit. + * + * @param exclusions a collection of keys to ignore, used when they've already tried and failed + * @return the next challenge + */ + fun getChallenge(exclusions: Collection): String? { + Preconditions.checkState(hasResult(), "This can only be called when result is present!") + return result.body.requestedInformation.filterNot { exclusions.contains(it) }.firstOrNull { REQUESTABLE_INFORMATION.contains(it) } + } + + fun isVerified(): Boolean { + return hasResult() && result.body.verified + } + + /** Should only be called if [isNonNormalizedNumber] */ + fun getOriginalNumber(): String { + if (error !is NonNormalizedPhoneNumberException) { + throw IllegalStateException("This can only be called when isNonNormalizedNumber()") + } + + return (error as NonNormalizedPhoneNumberException).originalNumber + } + + /** Should only be called if [isNonNormalizedNumber] */ + fun getNormalizedNumber(): String { + if (error !is NonNormalizedPhoneNumberException) { + throw IllegalStateException("This can only be called when isNonNormalizedNumber()") + } + + return (error as NonNormalizedPhoneNumberException).normalizedNumber + } + + fun cannotSubmitVerificationAttempt(): Boolean { + return !hasResult() || result.body.nextVerificationAttempt == null + } + + /** + * @param deltaSeconds the number of whole seconds to be added to the server timestamp + * @return a unix timestamp in milliseconds, or 0 to represent null + */ + private fun deriveTimestamp(deltaSeconds: Int?): Long { + Preconditions.checkState(hasResult(), "This can only be called when result is present!") + + if (deltaSeconds == null) { + return 0L + } + + val timestamp: Long = result.headers.timestamp + return timestamp + deltaSeconds.seconds.inWholeMilliseconds + } + + abstract fun verificationCodeRequestSuccess(): Boolean + + class RegistrationSessionProcessorForSession(response: ServiceResponse) : RegistrationSessionProcessor(response) { + + override fun verificationCodeRequestSuccess(): Boolean = false + } + + class RegistrationSessionProcessorForVerification(response: ServiceResponse) : RegistrationSessionProcessor(response) { + override fun verificationCodeRequestSuccess(): Boolean = hasResult() + + fun isAlreadyVerified(): Boolean { + return error is AlreadyVerifiedException + } + + fun mustRequestNewCode(): Boolean { + return error is MustRequestNewCodeException + } + + fun externalServiceFailure(): Boolean { + return error is ExternalServiceFailureException + } + + fun invalidTransportModeFailure(): Boolean { + return error is InvalidTransportModeException + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java index 56b044a86c..f905aafa8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java @@ -1,9 +1,5 @@ package org.thoughtcrime.securesms.registration; -import android.content.Context; - -import androidx.annotation.NonNull; - import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; @@ -22,7 +18,7 @@ private RegistrationUtil() {} * path a user has taken. This will only truly mark registration as complete if all of the * requirements are met. */ - public static void maybeMarkRegistrationComplete(@NonNull Context context) { + public static void maybeMarkRegistrationComplete() { if (!SignalStore.registrationValues().isRegistrationComplete() && SignalStore.account().isRegistered() && !Recipient.self().getProfileName().isEmpty() && diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RequestVerificationCodeResponseProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/RequestVerificationCodeResponseProcessor.kt deleted file mode 100644 index 2dca2cefd6..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RequestVerificationCodeResponseProcessor.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.thoughtcrime.securesms.registration - -import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException -import org.whispersystems.signalservice.api.push.exceptions.LocalRateLimitException -import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException -import org.whispersystems.signalservice.internal.ServiceResponse -import org.whispersystems.signalservice.internal.ServiceResponseProcessor -import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse - -/** - * Process responses from requesting an SMS or Phone code from the server. - */ -class RequestVerificationCodeResponseProcessor(response: ServiceResponse) : ServiceResponseProcessor(response) { - public override fun captchaRequired(): Boolean { - return super.captchaRequired() - } - - public override fun rateLimit(): Boolean { - return super.rateLimit() - } - - public override fun getError(): Throwable? { - return super.getError() - } - - fun localRateLimit(): Boolean { - return error is LocalRateLimitException - } - - fun isImpossibleNumber(): Boolean { - return error is ImpossiblePhoneNumberException - } - - fun isNonNormalizedNumber(): Boolean { - return error is NonNormalizedPhoneNumberException - } - - /** Should only be called if [isNonNormalizedNumber] */ - fun getOriginalNumber(): String { - if (error !is NonNormalizedPhoneNumberException) { - throw IllegalStateException("This can only be called when isNonNormalizedNumber()") - } - - return (error as NonNormalizedPhoneNumberException).originalNumber - } - - /** Should only be called if [isNonNormalizedNumber] */ - fun getNormalizedNumber(): String { - if (error !is NonNormalizedPhoneNumberException) { - throw IllegalStateException("This can only be called when isNonNormalizedNumber()") - } - - return (error as NonNormalizedPhoneNumberException).normalizedNumber - } - - companion object { - @JvmStatic - fun forLocalRateLimit(): RequestVerificationCodeResponseProcessor { - val response: ServiceResponse = ServiceResponse.forExecutionError(LocalRateLimitException()) - return RequestVerificationCodeResponseProcessor(response) - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt index b1fbfee2d3..3de9b955ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt @@ -3,24 +3,30 @@ package org.thoughtcrime.securesms.registration import android.app.Application import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.AppCapabilities import org.thoughtcrime.securesms.gcm.FcmUtil import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException import org.thoughtcrime.securesms.push.AccountManagerFactory +import org.thoughtcrime.securesms.registration.PushChallengeRequest.PushChallengeEvent import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.api.KbsPinData import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException import org.whispersystems.signalservice.api.SignalServiceAccountManager +import org.whispersystems.signalservice.api.account.AccountAttributes import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException import org.whispersystems.signalservice.internal.ServiceResponse -import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse -import org.whispersystems.signalservice.internal.push.VerifyAccountResponse +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse +import org.whispersystems.signalservice.internal.push.RegistrationSessionState import java.io.IOException import java.util.Locale import java.util.Optional +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit /** @@ -28,31 +34,107 @@ import java.util.concurrent.TimeUnit */ class VerifyAccountRepository(private val context: Application) { + fun validateSession( + sessionId: String?, + e164: String, + password: String + ): Single> { + return if (sessionId.isNullOrBlank()) { + Single.just(ServiceResponse.forApplicationError(NoSuchSessionException(), 409, null)) + } else { + val accountManager: SignalServiceAccountManager = AccountManagerFactory.createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password) + Single.fromCallable { accountManager.getRegistrationSession(sessionId) }.subscribeOn(Schedulers.io()) + } + } + + fun requestValidSession( + e164: String, + password: String, + mcc: String?, + mnc: String? + ): Single> { + return Single.fromCallable { + val fcmToken: String? = FcmUtil.getToken(context).orElse(null) + val accountManager: SignalServiceAccountManager = AccountManagerFactory.createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password) + if (fcmToken == null) { + return@fromCallable accountManager.createRegistrationSession(null, mcc, mnc) + } else { + return@fromCallable createSessionAndBlockForPushChallenge(accountManager, fcmToken, mcc, mnc) + } + } + .subscribeOn(Schedulers.io()) + } + + private fun createSessionAndBlockForPushChallenge(accountManager: SignalServiceAccountManager, fcmToken: String, mcc: String?, mnc: String?): ServiceResponse { + val subscriber = PushTokenChallengeSubscriber() + val eventBus = EventBus.getDefault() + eventBus.register(subscriber) + + val response: ServiceResponse = accountManager.createRegistrationSession(fcmToken, mcc, mnc) + + if (!response.result.isPresent) { + return response + } + + subscriber.latch.await(PUSH_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS) + + eventBus.unregister(subscriber) + + val challenge = subscriber.challenge + + return if (challenge != null) { + accountManager.submitPushChallengeToken(response.result.get().body.id, challenge) + } else { + val registrationSessionState = RegistrationSessionState(pushChallengeTimedOut = true) + val rawResponse: RegistrationSessionMetadataResponse = response.result.get() + ServiceResponse.forResult(rawResponse.copy(state = registrationSessionState), 200, null) + } + } + + fun requestAndVerifyPushToken( + sessionId: String, + e164: String, + password: String + ): Single> { + val fcmToken: Optional = FcmUtil.getToken(context) + val accountManager = AccountManagerFactory.createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password) + val pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, sessionId, fcmToken, PUSH_REQUEST_TIMEOUT) + return Single.fromCallable { + return@fromCallable accountManager.submitPushChallengeToken(sessionId, pushChallenge.orElse(null)) + }.subscribeOn(Schedulers.io()) + } + + fun verifyCaptcha( + sessionId: String, + captcha: String, + e164: String, + password: String + ): Single> { + val accountManager = AccountManagerFactory.createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password) + return Single.fromCallable { + return@fromCallable accountManager.submitCaptchaToken(sessionId, captcha) + }.subscribeOn(Schedulers.io()) + } + fun requestVerificationCode( + sessionId: String, e164: String, password: String, - mode: Mode, - captchaToken: String? = null - ): Single> { + mode: Mode + ): Single> { Log.d(TAG, "SMS Verification requested") return Single.fromCallable { - val fcmToken: Optional = FcmUtil.getToken(context) val accountManager = AccountManagerFactory.createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password) - val pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, fcmToken, e164, PUSH_REQUEST_TIMEOUT) - if (mode == Mode.PHONE_CALL) { - accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.ofNullable(captchaToken), pushChallenge, fcmToken) + return@fromCallable accountManager.requestVoiceVerificationCode(sessionId, Locale.getDefault(), mode.isSmsRetrieverSupported) } else { - accountManager.requestSmsVerificationCode(Locale.getDefault(), mode.isSmsRetrieverSupported, Optional.ofNullable(captchaToken), pushChallenge, fcmToken) + return@fromCallable accountManager.requestSmsVerificationCode(sessionId, Locale.getDefault(), mode.isSmsRetrieverSupported) } }.subscribeOn(Schedulers.io()) } - fun verifyAccount(registrationData: RegistrationData): Single> { - val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context) - val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey) - + fun verifyAccount(sessionId: String, registrationData: RegistrationData): Single> { val accountManager: SignalServiceAccountManager = AccountManagerFactory.createUnauthenticated( context, registrationData.e164, @@ -61,21 +143,14 @@ class VerifyAccountRepository(private val context: Application) { ) return Single.fromCallable { - val response = accountManager.verifyAccount( + accountManager.verifyAccount( registrationData.code, - registrationData.registrationId, - registrationData.isNotFcm, - unidentifiedAccessKey, - universalUnidentifiedAccess, - AppCapabilities.getCapabilities(true), - SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable, - registrationData.pniRegistrationId + sessionId ) - VerifyResponse.from(response, null, null) }.subscribeOn(Schedulers.io()) } - fun verifyAccountWithPin(registrationData: RegistrationData, pin: String, kbsPinDataProducer: KbsPinDataProducer): Single> { + fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single> { val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context) val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey) @@ -86,30 +161,33 @@ class VerifyAccountRepository(private val context: Application) { registrationData.password ) + val kbsData = kbsPinDataProducer?.produceKbsPinData() + val registrationLockV2: String? = kbsData?.masterKey?.deriveRegistrationLock() + + val accountAttributes = AccountAttributes( + signalingKey = null, + registrationId = registrationData.registrationId, + isFetchesMessages = registrationData.isNotFcm, + pin = pin, + registrationLock = registrationLockV2, + unidentifiedAccessKey = unidentifiedAccessKey, + isUnrestrictedUnidentifiedAccess = universalUnidentifiedAccess, + capabilities = AppCapabilities.getCapabilities(true), + isDiscoverableByPhoneNumber = SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable, + name = null, + pniRegistrationId = registrationData.pniRegistrationId, + recoveryPassword = registrationData.recoveryPassword + ) + return Single.fromCallable { - try { - val kbsData = kbsPinDataProducer.produceKbsPinData() - val registrationLockV2: String = kbsData.masterKey.deriveRegistrationLock() - - val response: ServiceResponse = accountManager.verifyAccountWithRegistrationLockPin( - registrationData.code, - registrationData.registrationId, - registrationData.isNotFcm, - registrationLockV2, - unidentifiedAccessKey, - universalUnidentifiedAccess, - AppCapabilities.getCapabilities(true), - SignalStore.phoneNumberPrivacy().phoneNumberListingMode.isDiscoverable, - registrationData.pniRegistrationId - ) - VerifyResponse.from(response, kbsData, pin) - } catch (e: KeyBackupSystemWrongPinException) { - ServiceResponse.forExecutionError(e) - } catch (e: KeyBackupSystemNoDataException) { - ServiceResponse.forExecutionError(e) - } catch (e: IOException) { - ServiceResponse.forExecutionError(e) - } + val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, true) + VerifyResponse.from(response, kbsData, pin) + }.subscribeOn(Schedulers.io()) + } + + fun getFcmToken(): Single { + return Single.fromCallable { + return@fromCallable FcmUtil.getToken(context).orElse("") }.subscribeOn(Schedulers.io()) } @@ -124,6 +202,17 @@ class VerifyAccountRepository(private val context: Application) { PHONE_CALL(false); } + private class PushTokenChallengeSubscriber { + var challenge: String? = null + val latch = CountDownLatch(1) + + @Subscribe + fun onChallengeEvent(pushChallengeEvent: PushChallengeEvent) { + challenge = pushChallengeEvent.challenge + latch.countDown() + } + } + companion object { private val TAG = Log.tag(VerifyAccountRepository::class.java) private val PUSH_REQUEST_TIMEOUT = TimeUnit.SECONDS.toMillis(5) diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt index 057dc94ddd..e98ced9652 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.registration import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException import org.thoughtcrime.securesms.pin.TokenData import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException +import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException +import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.ServiceResponseProcessor @@ -32,6 +34,10 @@ sealed class VerifyResponseProcessor(response: ServiceResponse) return super.getError() } + fun invalidSession(): Boolean { + return error is NoSuchSessionException + } + fun getLockedException(): LockedException { return error as LockedException } @@ -40,6 +46,10 @@ sealed class VerifyResponseProcessor(response: ServiceResponse) return error is NonSuccessfulResponseCodeException } + fun isIncorrectRegistrationRecoveryPassword(): Boolean { + return error is IncorrectRegistrationRecoveryPasswordException + } + abstract fun isKbsLocked(): Boolean } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java index e2d779e138..c94e24e599 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseEnterSmsCodeFragment.java @@ -1,18 +1,20 @@ package org.thoughtcrime.securesms.registration.fragments; -import android.animation.Animator; import android.os.Bundle; import android.view.View; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.CallSuper; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.navigation.Navigation; +import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.greenrobot.eventbus.EventBus; @@ -27,11 +29,10 @@ import org.thoughtcrime.securesms.registration.ReceivedSmsEvent; import org.thoughtcrime.securesms.registration.VerifyAccountRepository; import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel; -import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.LifecycleDisposable; -import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; +import org.thoughtcrime.securesms.util.dualsim.MccMncProducer; import org.whispersystems.signalservice.internal.push.LockedException; import java.util.ArrayList; @@ -59,7 +60,9 @@ public abstract class BaseEnterSmsCodeFragment onWrongNumber()); + wrongNumber.setOnClickListener(v -> returnToPhoneEntryScreen()); + bottomSheetButton.setOnClickListener( v -> showBottomSheet()); + + callMeCountDown.setTextResources(R.string.RegistrationActivity_call, R.string.RegistrationActivity_call_me_instead_available_in); + resendSmsCountDown.setTextResources(R.string.RegistrationActivity_resend_code, R.string.RegistrationActivity_resend_sms_available_in); callMeCountDown.setOnClickListener(v -> handlePhoneCallRequest()); + resendSmsCountDown.setOnClickListener(v -> handleSmsRequest()); callMeCountDown.setListener((v, remaining) -> { if (remaining <= 30) { @@ -102,16 +112,30 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } }); + resendSmsCountDown.setListener((v, remaining) -> { + if (remaining <= 30) { + scrollView.smoothScrollTo(0, v.getBottom()); + resendSmsCountDown.setListener(null); + } + }); + disposables.bindTo(getViewLifecycleOwner().getLifecycle()); viewModel = getViewModel(); - viewModel.getSuccessfulCodeRequestAttempts().observe(getViewLifecycleOwner(), (attempts) -> { + viewModel.getIncorrectCodeAttempts().observe(getViewLifecycleOwner(), (attempts) -> { if (attempts >= 3) { - // TODO Add bottom sheet for help + bottomSheetButton.setVisibility(View.VISIBLE); } }); - viewModel.onStartEnterCode(); + requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + viewModel.resetSession(); + this.remove(); + requireActivity().getOnBackPressedDispatcher().onBackPressed(); + } + }); } protected abstract ViewModel getViewModel(); @@ -124,7 +148,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat protected abstract void navigateToKbsAccountLocked(); - private void onWrongNumber() { + private void returnToPhoneEntryScreen() { + viewModel.resetSession(); Navigation.findNavController(requireView()).navigateUp(); } @@ -132,6 +157,7 @@ private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCode verificationCodeView.setOnCompleteListener(code -> { callMeCountDown.setVisibility(View.INVISIBLE); + resendSmsCountDown.setVisibility(View.INVISIBLE); wrongNumber.setVisibility(View.INVISIBLE); keyboard.displayProgress(); @@ -181,6 +207,7 @@ public void onSuccess(Boolean r) { .setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later) .setPositiveButton(android.R.string.ok, (dialog, which) -> { callMeCountDown.setVisibility(View.VISIBLE); + resendSmsCountDown.setVisibility(View.VISIBLE); wrongNumber.setVisibility(View.VISIBLE); verificationCodeView.clear(); keyboard.displayKeyboard(); @@ -204,11 +231,14 @@ protected void handleKbsAccountLocked() { } protected void handleIncorrectCodeError() { + viewModel.incrementIncorrectCodeAttempts(); + Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_code, Toast.LENGTH_LONG).show(); keyboard.displayFailure().addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean result) { callMeCountDown.setVisibility(View.VISIBLE); + resendSmsCountDown.setVisibility(View.VISIBLE); wrongNumber.setVisibility(View.VISIBLE); verificationCodeView.clear(); keyboard.displayKeyboard(); @@ -222,6 +252,7 @@ protected void handleGeneralError() { @Override public void onSuccess(Boolean result) { callMeCountDown.setVisibility(View.VISIBLE); + resendSmsCountDown.setVisibility(View.VISIBLE); wrongNumber.setVisibility(View.VISIBLE); verificationCodeView.clear(); keyboard.displayKeyboard(); @@ -285,20 +316,29 @@ private void handlePhoneCallRequest() { showConfirmNumberDialogIfTranslated(requireContext(), R.string.RegistrationActivity_you_will_receive_a_call_to_verify_this_number, viewModel.getNumber().getE164Number(), - this::handlePhoneCallRequestAfterConfirm, - this::onWrongNumber); + () -> handleCodeCallRequestAfterConfirm(VerifyAccountRepository.Mode.PHONE_CALL), + this::returnToPhoneEntryScreen); + } + + private void handleSmsRequest() { + showConfirmNumberDialogIfTranslated(requireContext(), + R.string.RegistrationActivity_a_verification_code_will_be_sent_to, + viewModel.getNumber().getE164Number(), + () -> handleCodeCallRequestAfterConfirm(VerifyAccountRepository.Mode.SMS_WITH_LISTENER), + this::returnToPhoneEntryScreen); } - private void handlePhoneCallRequestAfterConfirm() { - Disposable request = viewModel.requestVerificationCode(VerifyAccountRepository.Mode.PHONE_CALL) + private void handleCodeCallRequestAfterConfirm(VerifyAccountRepository.Mode mode) { + MccMncProducer mccMncProducer = new MccMncProducer(requireContext()); + Disposable request = viewModel.requestVerificationCode(mode, mccMncProducer.getMcc(), mccMncProducer.getMnc()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(processor -> { if (processor.hasResult()) { - Toast.makeText(requireContext(), R.string.RegistrationActivity_call_requested, Toast.LENGTH_LONG).show(); - } else if (processor.captchaRequired()) { + Toast.makeText(requireContext(), getCodeRequestedToastText(mode), Toast.LENGTH_LONG).show(); + } else if (processor.captchaRequired(viewModel.getExcludedChallenges())) { navigateToCaptcha(); } else if (processor.rateLimit()) { - Toast.makeText(requireContext(), R.string.RegistrationActivity_rate_limited_to_service, Toast.LENGTH_LONG).show(); + handleRateLimited(); } else { Log.w(TAG, "Unable to request phone code", processor.getError()); Toast.makeText(requireContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show(); @@ -308,6 +348,19 @@ private void handlePhoneCallRequestAfterConfirm() { disposables.add(request); } + @StringRes + private int getCodeRequestedToastText(VerifyAccountRepository.Mode mode) { + switch (mode) { + case PHONE_CALL: + return R.string.RegistrationActivity_call_requested; + case SMS_WITH_LISTENER: + case SMS_WITHOUT_LISTENER: + return R.string.RegistrationActivity_sms_requested; + default: + return R.string.RegistrationActivity_code_requested; + } + } + private void connectKeyboard(VerificationCodeView verificationCodeView, VerificationPinKeyboard keyboard) { keyboard.setOnKeyPressListener(key -> { if (!autoCompleting) { @@ -323,21 +376,53 @@ private void connectKeyboard(VerificationCodeView verificationCodeView, Verifica @Override public void onResume() { super.onResume(); + String sessionE164 = viewModel.getSessionE164(); + if (sessionE164 == null) { + returnToPhoneEntryScreen(); + return; + } subheader.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, viewModel.getNumber().getFullFormattedNumber())); - viewModel.getCanCallAtTime().observe(getViewLifecycleOwner(), callAtTime -> callMeCountDown.startCountDownTo(callAtTime)); + Disposable request = viewModel.validateSession(sessionE164) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(processor -> { + if (!processor.hasResult()) { + returnToPhoneEntryScreen(); + } else if (processor.isInvalidSession()) { + returnToPhoneEntryScreen(); + } else if (processor.cannotSubmitVerificationAttempt()) { + returnToPhoneEntryScreen(); + } else if (!processor.canSubmitProofImmediately()) { + handleRateLimited(); + } + // else session state is valid and server is ready to accept code + }); + + disposables.add(request); + + viewModel.getCanCallAtTime().observe(getViewLifecycleOwner(), callAtTime -> { + if (callAtTime > 0) { + callMeCountDown.setVisibility(View.VISIBLE); + callMeCountDown.startCountDownTo(callAtTime); + } else { + callMeCountDown.setVisibility(View.INVISIBLE); + } + }); + viewModel.getCanSmsAtTime().observe(getViewLifecycleOwner(), smsAtTime -> { + if (smsAtTime > 0) { + resendSmsCountDown.setVisibility(View.VISIBLE); + resendSmsCountDown.startCountDownTo(smsAtTime); + } else { + resendSmsCountDown.setVisibility(View.INVISIBLE); + } + }); } - private void sendEmailToSupport() { - String body = SupportEmailUtil.generateSupportEmailBody(requireContext(), - R.string.RegistrationActivity_code_support_subject, - null, - null); - CommunicationActions.openEmail(requireContext(), - SupportEmailUtil.getSupportEmailAddress(requireContext()), - getString(R.string.RegistrationActivity_code_support_subject), - body); + + private void showBottomSheet() { + ContactSupportBottomSheetFragment bottomSheet = new ContactSupportBottomSheetFragment(); + bottomSheet.show(getChildFragmentManager(), "support_bottom_sheet"); } @Override @@ -347,6 +432,6 @@ public void onNoCellSignalPresent() { @Override public void onCellSignalPresent() { - // TODO animate away bottom sheet + // TODO animate away bottom sheet } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationLockFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationLockFragment.java index 3c4bf3f391..b0666e4a96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationLockFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/BaseRegistrationLockFragment.java @@ -23,7 +23,6 @@ import org.thoughtcrime.securesms.pin.TokenData; import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel; import org.thoughtcrime.securesms.util.LifecycleDisposable; -import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton; @@ -44,7 +43,7 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment { /** * Applies to both V1 and V2 pins, because some V2 pins may have been migrated from V1. */ - private static final int MINIMUM_PIN_LENGTH = 4; + public static final int MINIMUM_PIN_LENGTH = 4; private EditText pinEntry; private View forgotPin; @@ -286,10 +285,7 @@ private void updateKeyboard(@NonNull PinKeyboardType keyboard) { private void enableAndFocusPinEntry() { pinEntry.setEnabled(true); pinEntry.setFocusable(true); - - if (pinEntry.requestFocus()) { - ServiceUtil.getInputMethodManager(pinEntry.getContext()).showSoftInput(pinEntry, 0); - } + ViewUtil.focusAndShowKeyboard(pinEntry); } protected abstract void handleSuccessfulPinEntry(@NonNull String pin); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ContactSupportBottomSheetFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ContactSupportBottomSheetFragment.kt new file mode 100644 index 0000000000..76693a57e2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ContactSupportBottomSheetFragment.kt @@ -0,0 +1,164 @@ +package org.thoughtcrime.securesms.registration.fragments + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment +import org.thoughtcrime.securesms.util.CommunicationActions +import org.thoughtcrime.securesms.util.SupportEmailUtil + +/** + * Helpful bottom sheet dialog displayed during registration when the user enters the wrong verification code too many times. + */ +class ContactSupportBottomSheetFragment : ComposeBottomSheetDialogFragment() { + + @Preview + @Composable + override fun SheetContent() { + val annotatedText = buildClickableString() + + return Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .wrapContentSize(Alignment.Center) + .padding(16.dp) + ) { + Handle() + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface)) { + append(stringResource(R.string.RegistrationActivity_support_bottom_sheet_title)) + } + }, + modifier = Modifier.padding(8.dp) + ) + Text( + text = stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_suggestions), + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(8.dp) + ) + ClickableText( + text = annotatedText, + onClick = { offset -> + annotatedText.getStringAnnotations( + tag = "URL", + start = offset, + end = offset + ) + .firstOrNull()?.let { annotation -> + when (annotation.item) { + TROUBLESHOOTING_STEPS_KEY -> openTroubleshootingSteps() + CONTACT_SUPPORT_KEY -> sendEmailToSupport() + } + } + }, + modifier = Modifier.padding(8.dp) + ) + } + } + + @Composable + private fun buildClickableString(): AnnotatedString { + val troubleshootingStepsString = stringResource(R.string.RegistrationActivity_support_bottom_sheet_cta_troubleshooting_steps_substring) + val contactSupportString = stringResource(R.string.RegistrationActivity_support_bottom_sheet_cta_contact_support_substring) + val completeString = stringResource(R.string.RegistrationActivity_support_bottom_sheet_body_call_to_action) + + val troubleshootingStartIndex = completeString.indexOf(troubleshootingStepsString) + val troubleshootingEndIndex = troubleshootingStartIndex + troubleshootingStepsString.length + + val contactSupportStartIndex = completeString.indexOf(contactSupportString) + val contactSupportEndIndex = contactSupportStartIndex + contactSupportString.length + + val doesStringEndWithContactSupport = contactSupportEndIndex >= completeString.lastIndex + + return buildAnnotatedString { + withStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Normal + ) + ) { + append(completeString.substring(0, troubleshootingStartIndex)) + } + pushStringAnnotation( + tag = "URL", + annotation = TROUBLESHOOTING_STEPS_KEY + ) + withStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold + ) + ) { + append(troubleshootingStepsString) + } + pop() + withStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Normal + ) + ) { + append(completeString.substring(troubleshootingEndIndex, contactSupportStartIndex)) + } + pushStringAnnotation( + tag = "URL", + annotation = CONTACT_SUPPORT_KEY + ) + withStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold + ) + ) { + append(contactSupportString) + } + pop() + if (!doesStringEndWithContactSupport) { + append(completeString.substring(contactSupportEndIndex, completeString.lastIndex)) + } + } + } + + private fun openTroubleshootingSteps() { + CommunicationActions.openBrowserLink(requireContext(), getString(R.string.support_center_url)) + } + + private fun sendEmailToSupport() { + val body = SupportEmailUtil.generateSupportEmailBody( + requireContext(), + R.string.RegistrationActivity_code_support_subject, + null, + null + ) + CommunicationActions.openEmail( + requireContext(), + SupportEmailUtil.getSupportEmailAddress(requireContext()), + getString(R.string.RegistrationActivity_code_support_subject), + body + ) + } + + companion object { + private const val TROUBLESHOOTING_STEPS_KEY = "troubleshooting" + private const val CONTACT_SUPPORT_KEY = "contact_support" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java index 35f73c96e6..dc0325e962 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterPhoneNumberFragment.java @@ -12,8 +12,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ScrollView; -import android.widget.Spinner; -import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -53,9 +52,11 @@ import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.util.dualsim.MccMncProducer; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -113,12 +114,12 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (viewModel.isReregister()) { cancel.setVisibility(View.VISIBLE); - cancel.setOnClickListener(v -> Navigation.findNavController(v).navigateUp()); + cancel.setOnClickListener(v -> requireActivity().finish()); } else { cancel.setVisibility(View.GONE); } - viewModel.getLiveNumber().observe(getViewLifecycleOwner(), controller::updateNumber); + viewModel.getLiveNumber().observe(getViewLifecycleOwner(), controller::updateNumberFormatter); if (viewModel.hasCaptchaToken()) { ThreadUtil.runOnMainDelayed(() -> handleRegister(requireContext()), 250); @@ -130,8 +131,19 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (supportActionBar != null) { supportActionBar.setTitle(null); } - controller.prepopulateCountryCode(); + + final NumberViewState viewModelNumber = viewModel.getNumber(); + if (viewModelNumber.getCountryCode() == 0) { + controller.prepopulateCountryCode(); + } + controller.setNumberAndCountryCode(viewModelNumber); + showKeyboard(number.getEditText()); + + if (viewModel.hasUserSkippedReRegisterFlow() && viewModel.shouldAutoShowSmsConfirmDialog()) { + viewModel.setAutoShowSmsConfirmDialog(false); + ThreadUtil.runOnMainDelayed(() -> handleRegister(requireContext()), 250); + } } private void showKeyboard(View viewToFocus) { @@ -156,12 +168,12 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { } private void handleRegister(@NonNull Context context) { - if (TextUtils.isEmpty(countryCode.getEditText().getText())) { + if (viewModel.getNumber().getCountryCode() == 0) { showErrorDialog(context, getString(R.string.RegistrationActivity_you_must_specify_your_country_code)); return; } - if (TextUtils.isEmpty(this.number.getEditText().getText())) { + if (TextUtils.isEmpty(viewModel.getNumber().getNationalNumber())) { showErrorDialog(context, getString(R.string.RegistrationActivity_please_enter_a_valid_phone_number_to_register)); return; } @@ -182,9 +194,9 @@ private void handleRegister(@NonNull Context context) { PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(context); if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS) { - confirmNumberPrompt(context, e164number, () -> handleRequestVerification(context, true)); + confirmNumberPrompt(context, e164number, () -> onE164EnteredSuccessfully(context, true)); } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.DISABLED) { - confirmNumberPrompt(context, e164number, () -> handleRequestVerification(context, false)); + confirmNumberPrompt(context, e164number, () -> onE164EnteredSuccessfully(context, false)); } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.MISSING) { confirmNumberPrompt(context, e164number, () -> handlePromptForNoPlayServices(context)); } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE) { @@ -196,10 +208,26 @@ private void handleRegister(@NonNull Context context) { } } - private void handleRequestVerification(@NonNull Context context, boolean fcmSupported) { + private void onE164EnteredSuccessfully(@NonNull Context context, boolean fcmSupported) { register.setSpinning(); disableAllEntries(); + Disposable disposable = viewModel.canEnterSkipSmsFlow() + .observeOn(AndroidSchedulers.mainThread()) + .onErrorReturnItem(false) + .subscribe(canEnter -> { + if (canEnter) { + Log.i(TAG, "Enter skip flow"); + SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), EnterPhoneNumberFragmentDirections.actionReRegisterWithPinFragment()); + } else { + Log.i(TAG, "Unable to collect necessary data to enter skip flow, returning to normal"); + handleRequestVerification(context, fcmSupported); + } + }); + disposables.add(disposable); + } + + private void handleRequestVerification(@NonNull Context context, boolean fcmSupported) { if (fcmSupported) { SmsRetrieverClient client = SmsRetriever.getClient(context); Task task = client.startSmsRetriever(); @@ -253,23 +281,24 @@ private void enableAllEntries() { } private void requestVerificationCode(@NonNull Mode mode) { - NavController navController = NavHostFragment.findNavController(this); - - Disposable request = viewModel.requestVerificationCode(mode) + NavController navController = NavHostFragment.findNavController(this); + MccMncProducer mccMncProducer = new MccMncProducer(requireContext()); + Disposable request = viewModel.requestVerificationCode(mode, mccMncProducer.getMcc(), mccMncProducer.getMnc()) .doOnSubscribe(unused -> SignalStore.account().setRegistered(false)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(processor -> { - if (processor.hasResult()) { + if (processor.verificationCodeRequestSuccess()) { + disposables.add(updateFcmTokenValue()); SafeNavigation.safeNavigate(navController, EnterPhoneNumberFragmentDirections.actionEnterVerificationCode()); - } else if (processor.localRateLimit()) { - Log.i(TAG, "Unable to request sms code due to local rate limit"); - SafeNavigation.safeNavigate(navController, EnterPhoneNumberFragmentDirections.actionEnterVerificationCode()); - } else if (processor.captchaRequired()) { + } else if (processor.captchaRequired(viewModel.getExcludedChallenges())) { Log.i(TAG, "Unable to request sms code due to captcha required"); SafeNavigation.safeNavigate(navController, EnterPhoneNumberFragmentDirections.actionRequestCaptcha()); + } else if (processor.exhaustedVerificationCodeAttempts()) { + Log.i(TAG, "Unable to request sms code due to exhausting attempts"); + showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_rate_limited_to_service)); } else if (processor.rateLimit()) { Log.i(TAG, "Unable to request sms code due to rate limit"); - showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_rate_limited_to_service)); + showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_rate_limited_to_try_again, formatMillisecondsToString(processor.getRateLimit()))); } else if (processor.isImpossibleNumber()) { Log.w(TAG, "Impossible number", processor.getError()); Dialogs.showAlertDialog(requireContext(), @@ -277,6 +306,9 @@ private void requestVerificationCode(@NonNull Mode mode) { String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), viewModel.getNumber().getFullFormattedNumber())); } else if (processor.isNonNormalizedNumber()) { handleNonNormalizedNumberError(processor.getOriginalNumber(), processor.getNormalizedNumber(), mode); + } else if (processor.isTokenRejected()) { + Log.i(TAG, "The server did not accept the information.", processor.getError()); + showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)); } else { Log.i(TAG, "Unknown error during verification code request", processor.getError()); showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_unable_to_connect_to_service)); @@ -289,6 +321,18 @@ private void requestVerificationCode(@NonNull Mode mode) { disposables.add(request); } + private Disposable updateFcmTokenValue() { + return viewModel.updateFcmTokenValue().subscribe(); + } + + private String formatMillisecondsToString(long milliseconds) { + long totalSeconds = milliseconds / 1000; + long HH = totalSeconds / 3600; + long MM = (totalSeconds % 3600) / 60; + long SS = totalSeconds % 60; + return String.format(Locale.getDefault(), "%02d:%02d:%02d", HH, MM, SS); + } + public void showErrorDialog(Context context, String msg) { new MaterialAlertDialogBuilder(context).setMessage(msg).setPositiveButton(android.R.string.ok, null).show(); } @@ -314,6 +358,35 @@ public void setCountry(int countryCode) { viewModel.onCountrySelected(null, countryCode); } + @Override + public void onStart() { + super.onStart(); + String sessionE164 = viewModel.getSessionE164(); + if (sessionE164 != null && viewModel.getSessionId() != null && viewModel.getCaptchaToken() == null) { + checkIfSessionIsInProgressAndAdvance(sessionE164); + } + } + + private void checkIfSessionIsInProgressAndAdvance(@NonNull String sessionE164) { + NavController navController = NavHostFragment.findNavController(this); + Disposable request = viewModel.validateSession(sessionE164) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(processor -> { + if (processor.hasResult() && processor.canSubmitProofImmediately()) { + try { + viewModel.restorePhoneNumberStateFromE164(sessionE164); + SafeNavigation.safeNavigate(navController, EnterPhoneNumberFragmentDirections.actionEnterVerificationCode()); + } catch (NumberParseException numberParseException) { + viewModel.resetSession(); + } + } else { + viewModel.resetSession(); + } + }); + + disposables.add(request); + } + private void handleNonNormalizedNumberError(@NonNull String originalNumber, @NonNull String normalizedNumber, @NonNull Mode mode) { try { Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(normalizedNumber, null); @@ -349,14 +422,14 @@ private void handlePromptForNoPlayServices(@NonNull Context context) { new MaterialAlertDialogBuilder(context) .setTitle(R.string.RegistrationActivity_missing_google_play_services) .setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services) - .setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> handleRequestVerification(context, false)) + .setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> onE164EnteredSuccessfully(context, false)) .setNegativeButton(android.R.string.cancel, null) .show(); } - protected final void confirmNumberPrompt(@NonNull Context context, - @NonNull String e164number, - @NonNull Runnable onConfirmed) + private void confirmNumberPrompt(@NonNull Context context, + @NonNull String e164number, + @NonNull Runnable onConfirmed) { showConfirmNumberDialogIfTranslated(context, R.string.RegistrationActivity_a_verification_code_will_be_sent_to, diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt new file mode 100644 index 0000000000..f54e0504cc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/ReRegisterWithPinFragment.kt @@ -0,0 +1,260 @@ +package org.thoughtcrime.securesms.registration.fragments + +import android.os.Bundle +import android.text.InputType +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.Toast +import androidx.annotation.StringRes +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.LoggingFragment +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.databinding.PinRestoreEntryFragmentBinding +import org.thoughtcrime.securesms.lock.v2.KbsConstants +import org.thoughtcrime.securesms.lock.v2.PinKeyboardType +import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor +import org.thoughtcrime.securesms.registration.viewmodel.ReRegisterWithPinViewModel +import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel +import org.thoughtcrime.securesms.util.CommunicationActions +import org.thoughtcrime.securesms.util.LifecycleDisposable +import org.thoughtcrime.securesms.util.SupportEmailUtil +import org.thoughtcrime.securesms.util.ViewUtil +import org.thoughtcrime.securesms.util.navigation.safeNavigate + +/** + * Using a recovery password or restored KBS token attempt to register in the skip flow. + */ +class ReRegisterWithPinFragment : LoggingFragment(R.layout.pin_restore_entry_fragment) { + + companion object { + private val TAG = Log.tag(ReRegisterWithPinFragment::class.java) + } + + private var _binding: PinRestoreEntryFragmentBinding? = null + private val binding: PinRestoreEntryFragmentBinding + get() = _binding!! + + private val registrationViewModel: RegistrationViewModel by activityViewModels() + private val reRegisterViewModel: ReRegisterWithPinViewModel by viewModels() + + private val disposables = LifecycleDisposable() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = PinRestoreEntryFragmentBinding.bind(view) + + disposables.bindTo(viewLifecycleOwner.lifecycle) + + RegistrationViewDelegate.setDebugLogSubmitMultiTapView(binding.pinRestorePinTitle) + + binding.pinRestorePinDescription.setText(R.string.RegistrationLockFragment__enter_the_pin_you_created_for_your_account) + + binding.pinRestoreForgotPin.visibility = View.GONE + binding.pinRestoreForgotPin.setOnClickListener { onNeedHelpClicked() } + + binding.pinRestoreSkipButton.setOnClickListener { onSkipClicked() } + + binding.pinRestorePinInput.imeOptions = EditorInfo.IME_ACTION_DONE + binding.pinRestorePinInput.setOnEditorActionListener { v, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + ViewUtil.hideKeyboard(requireContext(), v!!) + handlePinEntry() + return@setOnEditorActionListener true + } + false + } + + enableAndFocusPinEntry() + + binding.pinRestorePinConfirm.setOnClickListener { + handlePinEntry() + } + + binding.pinRestoreKeyboardToggle.setOnClickListener { + val keyboardType: PinKeyboardType = getPinEntryKeyboardType() + updateKeyboard(keyboardType.other) + binding.pinRestoreKeyboardToggle.setText(resolveKeyboardToggleText(keyboardType)) + } + + val keyboardType: PinKeyboardType = getPinEntryKeyboardType().other + binding.pinRestoreKeyboardToggle.setText(resolveKeyboardToggleText(keyboardType)) + + reRegisterViewModel.updateTokenData(registrationViewModel.keyBackupCurrentToken) + + disposables += reRegisterViewModel.triesRemaining.subscribe(this::updateTriesRemaining) + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } + + private fun handlePinEntry() { + val pin: String? = binding.pinRestorePinInput.text?.toString() + + val trimmedLength = pin?.replace(" ", "")?.length ?: 0 + if (trimmedLength == 0) { + Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show() + enableAndFocusPinEntry() + return + } + + if (trimmedLength < BaseRegistrationLockFragment.MINIMUM_PIN_LENGTH) { + Toast.makeText(requireContext(), getString(R.string.RegistrationActivity_your_pin_has_at_least_d_digits_or_characters, BaseRegistrationLockFragment.MINIMUM_PIN_LENGTH), Toast.LENGTH_LONG).show() + enableAndFocusPinEntry() + return + } + + disposables += registrationViewModel.verifyReRegisterWithPin(pin!!) + .doOnSubscribe { + ViewUtil.hideKeyboard(requireContext(), binding.pinRestorePinInput) + binding.pinRestorePinInput.isEnabled = false + binding.pinRestorePinConfirm.setSpinning() + } + .doAfterTerminate { + binding.pinRestorePinInput.isEnabled = true + binding.pinRestorePinConfirm.cancelSpinning() + } + .subscribe { processor -> + if (processor.hasResult()) { + Log.i(TAG, "Successfully re-registered via skip flow") + findNavController().safeNavigate(R.id.action_reRegisterWithPinFragment_to_registrationCompletePlaceHolderFragment) + return@subscribe + } + + reRegisterViewModel.hasIncorrectGuess = true + + if (processor is VerifyResponseWithRegistrationLockProcessor && processor.wrongPin()) { + reRegisterViewModel.updateTokenData(processor.tokenData) + if (processor.tokenData != null) { + registrationViewModel.setKeyBackupTokenData(processor.tokenData) + } + return@subscribe + } else if (processor.isKbsLocked()) { + Log.w(TAG, "Unable to continue skip flow, KBS is locked") + onAccountLocked() + } else if (processor.isIncorrectRegistrationRecoveryPassword()) { + Log.w(TAG, "Registration recovery password was incorrect. Moving to SMS verification.") + onSkipPinEntry() + } else if (processor.isServerSentError()) { + Log.i(TAG, "Error from server, not likely recoverable", processor.error) + Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show() + } else { + Log.i(TAG, "Unexpected error occurred", processor.error) + Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show() + } + } + } + + private fun updateTriesRemaining(triesRemaining: Int) { + if (reRegisterViewModel.hasIncorrectGuess) { + if (triesRemaining == 1 && !reRegisterViewModel.isLocalVerification) { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.PinRestoreEntryFragment_incorrect_pin) + .setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining)) + .setPositiveButton(android.R.string.ok, null) + .show() + } + + if (triesRemaining > 5) { + binding.pinRestorePinInputLabel.setText(R.string.PinRestoreEntryFragment_incorrect_pin) + } else { + binding.pinRestorePinInputLabel.text = resources.getQuantityString(R.plurals.RegistrationLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining, triesRemaining) + } + binding.pinRestoreForgotPin.visibility = View.VISIBLE + } else { + if (triesRemaining == 1) { + binding.pinRestoreForgotPin.visibility = View.VISIBLE + if (!reRegisterViewModel.isLocalVerification) { + MaterialAlertDialogBuilder(requireContext()) + .setMessage(resources.getQuantityString(R.plurals.PinRestoreEntryFragment_you_have_d_attempt_remaining, triesRemaining, triesRemaining)) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + } + + if (triesRemaining == 0) { + Log.w(TAG, "Account locked. User out of attempts on KBS.") + onAccountLocked() + } + } + + private fun onAccountLocked() { + val message = if (reRegisterViewModel.isLocalVerification) R.string.ReRegisterWithPinFragment_out_of_guesses_local else R.string.PinRestoreLockedFragment_youve_run_out_of_pin_guesses + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.PinRestoreEntryFragment_incorrect_pin) + .setMessage(message) + .setCancelable(false) + .setPositiveButton(R.string.ReRegisterWithPinFragment_send_sms_code) { _, _ -> onSkipPinEntry() } + .setNegativeButton(R.string.AccountLockedFragment__learn_more) { _, _ -> CommunicationActions.openBrowserLink(requireContext(), getString(R.string.PinRestoreLockedFragment_learn_more_url)) } + .show() + } + + private fun enableAndFocusPinEntry() { + binding.pinRestorePinInput.isEnabled = true + binding.pinRestorePinInput.isFocusable = true + ViewUtil.focusAndShowKeyboard(binding.pinRestorePinInput) + } + + private fun getPinEntryKeyboardType(): PinKeyboardType { + val isNumeric = binding.pinRestorePinInput.inputType and InputType.TYPE_MASK_CLASS == InputType.TYPE_CLASS_NUMBER + return if (isNumeric) PinKeyboardType.NUMERIC else PinKeyboardType.ALPHA_NUMERIC + } + + private fun updateKeyboard(keyboard: PinKeyboardType) { + val isAlphaNumeric = keyboard == PinKeyboardType.ALPHA_NUMERIC + binding.pinRestorePinInput.inputType = if (isAlphaNumeric) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD else InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + binding.pinRestorePinInput.text?.clear() + } + + @StringRes + private fun resolveKeyboardToggleText(keyboard: PinKeyboardType): Int { + return if (keyboard == PinKeyboardType.ALPHA_NUMERIC) { + R.string.RegistrationLockFragment__enter_alphanumeric_pin + } else { + R.string.RegistrationLockFragment__enter_numeric_pin + } + } + + private fun onNeedHelpClicked() { + val message = if (reRegisterViewModel.isLocalVerification) R.string.ReRegisterWithPinFragment_need_help_local else R.string.PinRestoreEntryFragment_your_pin_is_a_d_digit_code + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.PinRestoreEntryFragment_need_help) + .setMessage(getString(message, KbsConstants.MINIMUM_PIN_LENGTH)) + .setPositiveButton(R.string.PinRestoreEntryFragment_skip) { _, _ -> onSkipPinEntry() } + .setNeutralButton(R.string.PinRestoreEntryFragment_contact_support) { _, _ -> + val body = SupportEmailUtil.generateSupportEmailBody(requireContext(), R.string.ReRegisterWithPinFragment_support_email_subject, null, null) + + CommunicationActions.openEmail( + requireContext(), + SupportEmailUtil.getSupportEmailAddress(requireContext()), + getString(R.string.ReRegisterWithPinFragment_support_email_subject), + body + ) + } + .setNegativeButton(R.string.PinRestoreEntryFragment_cancel, null) + .show() + } + + private fun onSkipClicked() { + val message = if (reRegisterViewModel.isLocalVerification) R.string.ReRegisterWithPinFragment_skip_local else R.string.PinRestoreEntryFragment_if_you_cant_remember_your_pin + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.PinRestoreEntryFragment_skip_pin_entry) + .setMessage(message) + .setPositiveButton(R.string.PinRestoreEntryFragment_skip) { _, _ -> onSkipPinEntry() } + .setNegativeButton(R.string.PinRestoreEntryFragment_cancel, null) + .show() + } + + private fun onSkipPinEntry() { + registrationViewModel.setUserSkippedReRegisterFlow(true) + findNavController().safeNavigate(R.id.action_reRegisterWithPinFragment_to_enterPhoneNumberFragment) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java deleted file mode 100644 index f6fee31c25..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.thoughtcrime.securesms.registration.fragments; - -import android.content.Intent; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.ActivityNavigator; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.LoggingFragment; -import org.thoughtcrime.securesms.MainActivity; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob; -import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob; -import org.thoughtcrime.securesms.jobs.ProfileUploadJob; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; -import org.thoughtcrime.securesms.pin.PinRestoreActivity; -import org.thoughtcrime.securesms.profiles.AvatarHelper; -import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.registration.RegistrationUtil; -import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; - -import java.util.Arrays; - -public final class RegistrationCompleteFragment extends LoggingFragment { - - private static final String TAG = Log.tag(RegistrationCompleteFragment.class); - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_registration_blank, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - FragmentActivity activity = requireActivity(); - RegistrationViewModel viewModel = new ViewModelProvider(activity).get(RegistrationViewModel.class); - - if (SignalStore.storageService().needsAccountRestore()) { - Log.i(TAG, "Performing pin restore"); - activity.startActivity(new Intent(activity, PinRestoreActivity.class)); - } else if (!viewModel.isReregister()) { - boolean needsProfile = Recipient.self().getProfileName().isEmpty() || !AvatarHelper.hasAvatar(activity, Recipient.self().getId()); - boolean needsPin = !SignalStore.kbsValues().hasPin(); - - Log.i(TAG, "Pin restore flow not required." + - " profile name: " + Recipient.self().getProfileName().isEmpty() + - " profile avatar: " + !AvatarHelper.hasAvatar(activity, Recipient.self().getId()) + - " needsPin:" + needsPin); - - Intent startIntent = MainActivity.clearTop(activity); - - if (needsPin) { - startIntent = chainIntents(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), startIntent); - } - - if (needsProfile) { - startIntent = chainIntents(EditProfileActivity.getIntentForUserProfile(activity), startIntent); - } - - if (!needsProfile && !needsPin) { - ApplicationDependencies.getJobManager() - .startChain(new ProfileUploadJob()) - .then(Arrays.asList(new MultiDeviceProfileKeyUpdateJob(), new MultiDeviceProfileContentUpdateJob())) - .enqueue(); - - RegistrationUtil.maybeMarkRegistrationComplete(requireContext()); - } - - activity.startActivity(startIntent); - } - - activity.finish(); - ActivityNavigator.applyPopAnimationsToPendingTransition(activity); - } - - private static @NonNull Intent chainIntents(@NonNull Intent sourceIntent, @NonNull Intent nextIntent) { - sourceIntent.putExtra("next_intent", nextIntent); - return sourceIntent; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt new file mode 100644 index 0000000000..8caf9277d7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.kt @@ -0,0 +1,91 @@ +package org.thoughtcrime.securesms.registration.fragments + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.navigation.ActivityNavigator +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.LoggingFragment +import org.thoughtcrime.securesms.MainActivity +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob +import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob +import org.thoughtcrime.securesms.jobs.ProfileUploadJob +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity +import org.thoughtcrime.securesms.pin.PinRestoreActivity +import org.thoughtcrime.securesms.profiles.AvatarHelper +import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.registration.RegistrationUtil +import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel + +/** + * [RegistrationCompleteFragment] is not visible to the user, but functions as basically a redirect towards one of: + * - [PIN Restore flow activity](org.thoughtcrime.securesms.pin.PinRestoreActivity) + * - [Profile](org.thoughtcrime.securesms.profiles.edit.EditProfileActivity) / [PIN creation](org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity) flow activities (this class chains the necessary activities together as an intent) + * - Exit registration flow and progress to conversation list + */ +class RegistrationCompleteFragment : LoggingFragment() { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_registration_blank, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val activity = requireActivity() + val viewModel: RegistrationViewModel by viewModels(ownerProducer = { requireActivity() }) + + if (viewModel.isReregister) { + SignalStore.misc().shouldShowLinkedDevicesReminder = true + } + + if (SignalStore.storageService().needsAccountRestore()) { + Log.i(TAG, "Performing pin restore.") + activity.startActivity(Intent(activity, PinRestoreActivity::class.java)) + } else { + val isProfileNameEmpty = Recipient.self().profileName.isEmpty + val isAvatarEmpty = !AvatarHelper.hasAvatar(activity, Recipient.self().id) + val needsProfile = isProfileNameEmpty || isAvatarEmpty + val needsPin = !SignalStore.kbsValues().hasPin() && !viewModel.isReregister + + Log.i(TAG, "Pin restore flow not required. Profile name: $isProfileNameEmpty | Profile avatar: $isAvatarEmpty | Needs PIN: $needsPin") + + if (!needsProfile && !needsPin) { + ApplicationDependencies.getJobManager() + .startChain(ProfileUploadJob()) + .then(listOf(MultiDeviceProfileKeyUpdateJob(), MultiDeviceProfileContentUpdateJob())) + .enqueue() + RegistrationUtil.maybeMarkRegistrationComplete() + } + + var startIntent = MainActivity.clearTop(activity) + + if (needsPin) { + startIntent = chainIntents(CreateKbsPinActivity.getIntentForPinCreate(activity), startIntent) + } + + if (needsProfile) { + startIntent = chainIntents(EditProfileActivity.getIntentForUserProfile(activity), startIntent) + } + + activity.startActivity(startIntent) + } + + activity.finish() + ActivityNavigator.applyPopAnimationsToPendingTransition(activity) + } + + private fun chainIntents(sourceIntent: Intent, nextIntent: Intent): Intent { + sourceIntent.putExtra("next_intent", nextIntent) + return sourceIntent + } + + companion object { + private val TAG = Log.tag(RegistrationCompleteFragment::class.java) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java index 71a2ac2249..4869c41bae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java @@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob; import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob; import org.thoughtcrime.securesms.jobs.StorageSyncJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -52,7 +53,11 @@ protected void handleSuccessfulPinEntry(@NonNull String pin) { ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN); stopwatch.split("AccountRestore"); - ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10)); + ApplicationDependencies + .getJobManager() + .startChain(new StorageSyncJob()) + .then(new NewRegistrationUsernameSyncJob()) + .enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10)); stopwatch.split("ContactRestore"); try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java index 036bdaa971..fd42f483a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/WelcomeFragment.java @@ -36,7 +36,6 @@ import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.CommunicationActions; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton; diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationNumberInputController.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationNumberInputController.kt index 02d38b323a..8521cd7ae4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationNumberInputController.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationNumberInputController.kt @@ -30,8 +30,9 @@ class RegistrationNumberInputController( private val spinnerView: MaterialAutoCompleteTextView = countryCodeInputLayout.editText as MaterialAutoCompleteTextView private val supportedCountryPrefixes: List = PhoneNumberUtil.getInstance().supportedCallingCodes .map { CountryPrefix(it, PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(it)) } - .sortedBy { it.digits } + .sortedBy { it.digits.toString() } private val spinnerAdapter: ArrayAdapter = ArrayAdapter(context, R.layout.registration_country_code_dropdown_item, supportedCountryPrefixes) + private val countryCodeEntryListener = CountryCodeEntryListener() private var countryFormatter: AsYouTypeFormatter? = null private var isUpdating = true @@ -41,11 +42,13 @@ class RegistrationNumberInputController( spinnerView.threshold = 100 spinnerView.setAdapter(spinnerAdapter) - spinnerView.addTextChangedListener(CountryCodeEntryListener()) + spinnerView.addTextChangedListener(countryCodeEntryListener) } fun prepopulateCountryCode() { - spinnerView.setText(supportedCountryPrefixes[0].toString()) + if (spinnerView.editableText.isBlank()) { + spinnerView.setText(supportedCountryPrefixes[0].toString()) + } } private fun advanceToPhoneNumberInput() { @@ -73,7 +76,21 @@ class RegistrationNumberInputController( } } - fun updateNumber(numberViewState: NumberViewState) { + fun setNumberAndCountryCode(numberViewState: NumberViewState) { + val countryCode = numberViewState.countryCode + + isUpdating = true + phoneNumberInputLayout.setText(numberViewState.nationalNumber) + if (numberViewState.countryCode != 0) { + spinnerView.setText(supportedCountryPrefixes.first { it.digits == numberViewState.countryCode }.toString()) + } + val regionCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(countryCode) + setCountryFormatter(regionCode) + + isUpdating = false + } + + fun updateNumberFormatter(numberViewState: NumberViewState) { val countryCode = numberViewState.countryCode isUpdating = true @@ -108,7 +125,9 @@ class RegistrationNumberInputController( } return if (justDigits.isEmpty()) { null - } else justDigits.toString() + } else { + justDigits.toString() + } } inner class NumberChangedListener : TextWatcher { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java index 89e4db1aa5..3a0922758a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/BaseRegistrationViewModel.java @@ -6,22 +6,28 @@ import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModel; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; + +import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.pin.KbsRepository; import org.thoughtcrime.securesms.pin.TokenData; -import org.thoughtcrime.securesms.registration.RequestVerificationCodeResponseProcessor; +import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor; import org.thoughtcrime.securesms.registration.VerifyAccountRepository; import org.thoughtcrime.securesms.registration.VerifyAccountRepository.Mode; import org.thoughtcrime.securesms.registration.VerifyResponse; import org.thoughtcrime.securesms.registration.VerifyResponseProcessor; import org.thoughtcrime.securesms.registration.VerifyResponseWithFailedKbs; +import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor; import org.thoughtcrime.securesms.registration.VerifyResponseWithSuccessfulKbs; import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs; -import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor; import org.whispersystems.signalservice.internal.ServiceResponse; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; -import java.util.concurrent.TimeUnit; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; @@ -33,18 +39,20 @@ */ public abstract class BaseRegistrationViewModel extends ViewModel { - private static final long FIRST_CALL_AVAILABLE_AFTER_MS = TimeUnit.SECONDS.toMillis(64); - private static final long SUBSEQUENT_CALL_AVAILABLE_AFTER_MS = TimeUnit.SECONDS.toMillis(300); - - private static final String STATE_NUMBER = "NUMBER"; - private static final String STATE_REGISTRATION_SECRET = "REGISTRATION_SECRET"; - private static final String STATE_VERIFICATION_CODE = "TEXT_CODE_ENTERED"; - private static final String STATE_CAPTCHA = "CAPTCHA"; - private static final String STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS = "SUCCESSFUL_CODE_REQUEST_ATTEMPTS"; - private static final String STATE_REQUEST_RATE_LIMITER = "REQUEST_RATE_LIMITER"; - private static final String STATE_KBS_TOKEN = "KBS_TOKEN"; - private static final String STATE_TIME_REMAINING = "TIME_REMAINING"; - private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME"; + private static final String TAG = Log.tag(BaseRegistrationViewModel.class); + + private static final String STATE_NUMBER = "NUMBER"; + private static final String STATE_REGISTRATION_SECRET = "REGISTRATION_SECRET"; + private static final String STATE_VERIFICATION_CODE = "TEXT_CODE_ENTERED"; + private static final String STATE_CAPTCHA = "CAPTCHA"; + private static final String STATE_PUSH_TIMED_OUT = "PUSH_TIMED_OUT"; + private static final String STATE_INCORRECT_CODE_ATTEMPTS = "STATE_INCORRECT_CODE_ATTEMPTS"; + private static final String STATE_REQUEST_RATE_LIMITER = "REQUEST_RATE_LIMITER"; + private static final String STATE_KBS_TOKEN = "KBS_TOKEN"; + private static final String STATE_TIME_REMAINING = "TIME_REMAINING"; + private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME"; + private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME"; + private static final String STATE_RECOVERY_PASSWORD = "RECOVERY_PASSWORD"; protected final SavedStateHandle savedState; protected final VerifyAccountRepository verifyAccountRepository; @@ -63,16 +71,39 @@ public BaseRegistrationViewModel(@NonNull SavedStateHandle savedStateHandle, setInitialDefaultValue(STATE_NUMBER, NumberViewState.INITIAL); setInitialDefaultValue(STATE_REGISTRATION_SECRET, password); setInitialDefaultValue(STATE_VERIFICATION_CODE, ""); - setInitialDefaultValue(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0); + setInitialDefaultValue(STATE_INCORRECT_CODE_ATTEMPTS, 0); setInitialDefaultValue(STATE_REQUEST_RATE_LIMITER, new LocalCodeRequestRateLimiter(60_000)); + setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.kbsValues().getRecoveryPassword()); + setInitialDefaultValue(STATE_PUSH_TIMED_OUT, false); } - protected void setInitialDefaultValue(@NonNull String key, @NonNull T initialValue) { + protected void setInitialDefaultValue(@NonNull String key, @Nullable T initialValue) { if (!savedState.contains(key) || savedState.get(key) == null) { savedState.set(key, initialValue); } } + public @Nullable String getSessionId() { + return SignalStore.registrationValues().getSessionId(); + } + + public void setSessionId(String sessionId) { + SignalStore.registrationValues().setSessionId(sessionId); + } + + public @Nullable String getSessionE164() { + return SignalStore.registrationValues().getSessionE164(); + } + + public void setSessionE164(String sessionE164) { + SignalStore.registrationValues().setSessionE164(sessionE164); + } + + public void resetSession() { + setSessionE164(null); + setSessionId(null); + } + public @NonNull NumberViewState getNumber() { //noinspection ConstantConditions return savedState.get(STATE_NUMBER); @@ -82,10 +113,17 @@ protected void setInitialDefaultValue(@NonNull String key, @NonNull T initia return savedState.getLiveData(STATE_NUMBER); } + public void restorePhoneNumberStateFromE164(String e164) throws NumberParseException { + Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(e164, null); + onCountrySelected(null, phoneNumber.getCountryCode()); + setNationalNumber(String.valueOf(phoneNumber.getNationalNumber())); + } + public void onCountrySelected(@Nullable String selectedCountryName, int countryCode) { setViewState(getNumber().toBuilder() .selectedCountryDisplayName(selectedCountryName) - .countryCode(countryCode).build()); + .countryCode(countryCode) + .build()); } public void setNationalNumber(String number) { @@ -129,22 +167,25 @@ public void onVerificationCodeEntered(String code) { savedState.set(STATE_VERIFICATION_CODE, code); } - public void markASuccessfulAttempt() { + public void incrementIncorrectCodeAttempts() { //noinspection ConstantConditions - savedState.set(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, (Integer) savedState.get(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS) + 1); + savedState.set(STATE_INCORRECT_CODE_ATTEMPTS, (Integer) savedState.get(STATE_INCORRECT_CODE_ATTEMPTS) + 1); } - public LiveData getSuccessfulCodeRequestAttempts() { - return savedState.getLiveData(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0); + public LiveData getIncorrectCodeAttempts() { + return savedState.getLiveData(STATE_INCORRECT_CODE_ATTEMPTS, 0); } - public @NonNull LocalCodeRequestRateLimiter getRequestLimiter() { - //noinspection ConstantConditions - return savedState.get(STATE_REQUEST_RATE_LIMITER); + public void markPushChallengeTimedOut() { + savedState.set(STATE_PUSH_TIMED_OUT, true); } - public void updateLimiter() { - savedState.set(STATE_REQUEST_RATE_LIMITER, savedState.get(STATE_REQUEST_RATE_LIMITER)); + public List getExcludedChallenges() { + ArrayList challengeKeys = new ArrayList<>(); + if (Boolean.TRUE.equals(savedState.get(STATE_PUSH_TIMED_OUT))) { + challengeKeys.add(RegistrationSessionProcessor.PUSH_CHALLENGE_KEY); + } + return challengeKeys; } public @Nullable TokenData getKeyBackupCurrentToken() { @@ -155,6 +196,14 @@ public void setKeyBackupTokenData(@Nullable TokenData tokenData) { savedState.set(STATE_KBS_TOKEN, tokenData); } + public void setRecoveryPassword(@Nullable String recoveryPassword) { + savedState.set(STATE_RECOVERY_PASSWORD, recoveryPassword); + } + + public @Nullable String getRecoveryPassword() { + return savedState.get(STATE_RECOVERY_PASSWORD); + } + public LiveData getLockedTimeRemaining() { return savedState.getLiveData(STATE_TIME_REMAINING, 0L); } @@ -163,43 +212,123 @@ public LiveData getCanCallAtTime() { return savedState.getLiveData(STATE_CAN_CALL_AT_TIME, 0L); } + public LiveData getCanSmsAtTime() { + return savedState.getLiveData(STATE_CAN_SMS_AT_TIME, 0L); + } + public void setLockedTimeRemaining(long lockedTimeRemaining) { savedState.set(STATE_TIME_REMAINING, lockedTimeRemaining); } - public void onStartEnterCode() { - savedState.set(STATE_CAN_CALL_AT_TIME, System.currentTimeMillis() + FIRST_CALL_AVAILABLE_AFTER_MS); + public void setCanCallAtTime(long callingTimestamp) { + savedState.getLiveData(STATE_CAN_CALL_AT_TIME).postValue(callingTimestamp); } - public void onCallRequested() { - savedState.set(STATE_CAN_CALL_AT_TIME, System.currentTimeMillis() + SUBSEQUENT_CALL_AVAILABLE_AFTER_MS); + public void setCanSmsAtTime(long smsTimestamp) { + savedState.getLiveData(STATE_CAN_SMS_AT_TIME).postValue(smsTimestamp); } - public Single requestVerificationCode(@NonNull Mode mode) { - String captcha = getCaptchaToken(); - clearCaptchaResponse(); + public Single requestVerificationCode(@NonNull Mode mode, @Nullable String mcc, @Nullable String mnc) { - if (mode == Mode.PHONE_CALL) { - onCallRequested(); - } else if (!getRequestLimiter().canRequest(mode, getNumber().getE164Number(), System.currentTimeMillis())) { - return Single.just(RequestVerificationCodeResponseProcessor.forLocalRateLimit()); + final String e164 = getNumber().getE164Number(); + + return getValidSession(e164, mcc, mnc) + .flatMap(processor -> { + if (!processor.hasResult()) { + return Single.just(processor); + } + + String sessionId = processor.getSessionId(); + setSessionId(sessionId); + setSessionE164(e164); + + return handleRequiredChallenges(processor, e164); + }) + .flatMap(processor -> { + if (!processor.hasResult()) { + return Single.just(processor); + } + + if (!processor.isAllowedToRequestCode()) { + return Single.just(processor); + } + + String sessionId = processor.getSessionId(); + clearCaptchaResponse(); + return verifyAccountRepository.requestVerificationCode(sessionId, + getNumber().getE164Number(), + getRegistrationSecret(), + mode) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForVerification::new); + }) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess((RegistrationSessionProcessor processor) -> { + if (processor.hasResult() && processor.isAllowedToRequestCode()) { + setCanSmsAtTime(processor.getNextCodeViaSmsAttempt()); + setCanCallAtTime(processor.getNextCodeViaCallAttempt()); + } + }); + } + + public Single validateSession(String e164) { + String storedSessionId = null; + if (e164.equals(getSessionE164())) { + storedSessionId = getSessionId(); } + return verifyAccountRepository.validateSession(storedSessionId, e164, getRegistrationSecret()) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForSession::new); + } - return verifyAccountRepository.requestVerificationCode(getNumber().getE164Number(), - getRegistrationSecret(), - mode, - captcha) - .map(RequestVerificationCodeResponseProcessor::new) - .observeOn(AndroidSchedulers.mainThread()) - .doOnSuccess(processor -> { - if (processor.hasResult()) { - markASuccessfulAttempt(); - getRequestLimiter().onSuccessfulRequest(mode, getNumber().getE164Number(), System.currentTimeMillis()); - } else { - getRequestLimiter().onUnsuccessfulRequest(); - } - updateLimiter(); - }); + public Single getValidSession(String e164, @Nullable String mcc, @Nullable String mnc) { + return validateSession(e164) + .flatMap(processor -> { + if (processor.isInvalidSession()) { + return verifyAccountRepository.requestValidSession(e164, getRegistrationSecret(), mcc, mnc) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForSession::new) + .doOnSuccess(createSessionProcessor -> { + if (createSessionProcessor.pushChallengeTimedOut()) { + markPushChallengeTimedOut(); + } + }); + } else { + return Single.just(processor); + } + }); + } + + public Single handleRequiredChallenges(RegistrationSessionProcessor processor, String e164) { + final String sessionId = processor.getSessionId(); + + if (processor.isAllowedToRequestCode()) { + return Single.just(processor); + } + + if (hasCaptchaToken() && processor.captchaRequired(getExcludedChallenges())) { + Log.d(TAG, "Submitting completed captcha challenge"); + final String captcha = Objects.requireNonNull(getCaptchaToken()); + clearCaptchaResponse(); + return verifyAccountRepository.verifyCaptcha(sessionId, captcha, e164, getRegistrationSecret()) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForSession::new); + } else { + String challenge = processor.getChallenge(getExcludedChallenges()); + Log.d(TAG, "Handling challenge of type " + challenge); + if (challenge != null) { + switch (challenge) { + case RegistrationSessionProcessor.PUSH_CHALLENGE_KEY: + return verifyAccountRepository.requestAndVerifyPushToken(sessionId, + getNumber().getE164Number(), + getRegistrationSecret()) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForSession::new); + + case RegistrationSessionProcessor.CAPTCHA_KEY: + // fall through to passing the processor back so that the eventual subscriber will check captchaRequired() and handle accordingly + default: + break; + } + } + } + + return Single.just(processor); } public Single verifyCodeWithoutRegistrationLock(@NonNull String code) { @@ -261,4 +390,5 @@ public Single verifyCodeAndRegister protected abstract Single onVerifySuccess(@NonNull VerifyResponseProcessor processor); protected abstract Single onVerifySuccessWithRegistrationLock(@NonNull VerifyResponseWithRegistrationLockProcessor processor, String pin); + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt new file mode 100644 index 0000000000..4d91f54722 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/ReRegisterWithPinViewModel.kt @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.registration.viewmodel + +import androidx.lifecycle.ViewModel +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import org.thoughtcrime.securesms.pin.TokenData + +/** + * Used during re-registration flow when pin entry is required to skip SMS verification. Mostly tracks + * guesses remaining in both the local and remote check flows. + */ +class ReRegisterWithPinViewModel : ViewModel() { + var isLocalVerification: Boolean = false + private set + + var hasIncorrectGuess: Boolean = false + + private val _triesRemaining: BehaviorSubject = BehaviorSubject.createDefault(10) + val triesRemaining: Observable = _triesRemaining.observeOn(AndroidSchedulers.mainThread()) + + fun updateTokenData(tokenData: TokenData?) { + if (tokenData == null) { + isLocalVerification = true + if (hasIncorrectGuess) { + _triesRemaining.onNext((_triesRemaining.value!! - 1).coerceAtLeast(0)) + } + } else { + _triesRemaining.onNext(tokenData.triesRemaining) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index 2e254d183d..c2b5ff6d22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -3,33 +3,56 @@ import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import androidx.lifecycle.AbstractSavedStateViewModelFactory; import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModel; import androidx.savedstate.SavedStateRegistryOwner; +import org.signal.core.util.Stopwatch; +import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.NewRegistrationUsernameSyncJob; +import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob; +import org.thoughtcrime.securesms.jobs.StorageSyncJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.lock.PinHashing; import org.thoughtcrime.securesms.pin.KbsRepository; +import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException; import org.thoughtcrime.securesms.pin.TokenData; import org.thoughtcrime.securesms.registration.RegistrationData; import org.thoughtcrime.securesms.registration.RegistrationRepository; -import org.thoughtcrime.securesms.registration.RequestVerificationCodeResponseProcessor; +import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor; import org.thoughtcrime.securesms.registration.VerifyAccountRepository; import org.thoughtcrime.securesms.registration.VerifyResponse; import org.thoughtcrime.securesms.registration.VerifyResponseProcessor; import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor; import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs; +import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.KbsPinData; +import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException; +import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException; import org.whispersystems.signalservice.internal.ServiceResponse; +import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse; +import org.whispersystems.util.Base64; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.schedulers.Schedulers; public final class RegistrationViewModel extends BaseRegistrationViewModel { + private static final String TAG = Log.tag(RegistrationViewModel.class); + private static final String STATE_FCM_TOKEN = "FCM_TOKEN"; private static final String STATE_RESTORE_FLOW_SHOWN = "RESTORE_FLOW_SHOWN"; private static final String STATE_IS_REREGISTER = "IS_REREGISTER"; @@ -37,6 +60,9 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel { private final RegistrationRepository registrationRepository; + private boolean userSkippedReRegisterFlow = false; + private boolean autoShowSmsConfirmDialog = false; + public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle, boolean isReregister, @NonNull VerifyAccountRepository verifyAccountRepository, @@ -66,7 +92,11 @@ public void onNumberDetected(int countryCode, String nationalNumber) { } public @Nullable String getFcmToken() { - return savedState.get(STATE_FCM_TOKEN); + String token = savedState.get(STATE_FCM_TOKEN); + if (token == null || token.isEmpty()) { + return null; + } + return token; } @MainThread @@ -96,54 +126,106 @@ public boolean hasBackupCompleted() { return completed != null ? completed : false; } - @Override - public Single requestVerificationCode(@NonNull VerifyAccountRepository.Mode mode) { - return super.requestVerificationCode(mode) - .doOnSuccess(processor -> { - if (processor.hasResult()) { - setFcmToken(processor.getResult().getFcmToken().orElse(null)); - } - }); + public boolean hasUserSkippedReRegisterFlow() { + return userSkippedReRegisterFlow; + } + + public void setUserSkippedReRegisterFlow(boolean userSkippedReRegisterFlow) { + Log.i(TAG, "User skipped re-register flow."); + this.userSkippedReRegisterFlow = userSkippedReRegisterFlow; + if (userSkippedReRegisterFlow) { + setAutoShowSmsConfirmDialog(true); + } + } + + public boolean shouldAutoShowSmsConfirmDialog() { + return autoShowSmsConfirmDialog; + } + + public void setAutoShowSmsConfirmDialog(boolean autoShowSmsConfirmDialog) { + this.autoShowSmsConfirmDialog = autoShowSmsConfirmDialog; } @Override protected Single> verifyAccountWithoutRegistrationLock() { - return verifyAccountRepository.verifyAccount(getRegistrationData()) - .flatMap(verifyAccountWithoutKbsResponse -> { - VerifyResponseProcessor processor = new VerifyResponseWithoutKbs(verifyAccountWithoutKbsResponse); - String pin = SignalStore.kbsValues().getPin(); - - if (processor.registrationLock() && SignalStore.kbsValues().getRegistrationLockToken() != null && pin != null) { - KbsPinData pinData = new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()); - - return verifyAccountRepository.verifyAccountWithPin(getRegistrationData(), pin, () -> pinData) - .map(verifyAccountWithPinResponse -> { - if (verifyAccountWithPinResponse.getResult().isPresent() && verifyAccountWithPinResponse.getResult().get().getKbsData() != null) { - return verifyAccountWithPinResponse; - } else { - return verifyAccountWithoutKbsResponse; - } - }); - } else { - return Single.just(verifyAccountWithoutKbsResponse); - } - }); + final String sessionId = getSessionId(); + if (sessionId == null) { + throw new IllegalStateException("No valid registration session"); + } + return verifyAccountRepository.verifyAccount(sessionId, getRegistrationData()) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForVerification::new) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess(processor -> { + if (processor.hasResult()) { + setCanSmsAtTime(processor.getNextCodeViaSmsAttempt()); + setCanCallAtTime(processor.getNextCodeViaCallAttempt()); + } + }) + .observeOn(Schedulers.io()) + .flatMap(processor -> { + if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) { + return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), null, null); + } else if (processor.getError() == null) { + return Single.just(ServiceResponse.forApplicationError(new IncorrectCodeException(), 403, null)); + } else { + return Single.just(ServiceResponse.coerceError(processor.getResponse())); + } + }) + .flatMap(verifyAccountWithoutKbsResponse -> { + VerifyResponseProcessor processor = new VerifyResponseWithoutKbs(verifyAccountWithoutKbsResponse); + String pin = SignalStore.kbsValues().getPin(); + + if ((processor.isKbsLocked() || processor.registrationLock()) && SignalStore.kbsValues().getRegistrationLockToken() != null && pin != null) { + KbsPinData pinData = new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()); + + return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> pinData) + .map(verifyAccountWithPinResponse -> { + if (verifyAccountWithPinResponse.getResult().isPresent() && verifyAccountWithPinResponse.getResult().get().getKbsData() != null) { + return verifyAccountWithPinResponse; + } else { + return verifyAccountWithoutKbsResponse; + } + }); + } else { + return Single.just(verifyAccountWithoutKbsResponse); + } + }) + .onErrorReturn(ServiceResponse::forUnknownError); } @Override protected Single> verifyAccountWithRegistrationLock(@NonNull String pin, @NonNull TokenData kbsTokenData) { - return verifyAccountRepository.verifyAccountWithPin(getRegistrationData(), pin, () -> Objects.requireNonNull(KbsRepository.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse()))); + final String sessionId = getSessionId(); + if (sessionId == null) { + throw new IllegalStateException("No valid registration session"); + } + return verifyAccountRepository.verifyAccount(sessionId, getRegistrationData()) + .map(RegistrationSessionProcessor.RegistrationSessionProcessorForVerification::new) + .doOnSuccess(processor -> { + if (processor.hasResult()) { + setCanSmsAtTime(processor.getNextCodeViaSmsAttempt()); + setCanCallAtTime(processor.getNextCodeViaCallAttempt()); + } + }) + .>flatMap(processor -> { + if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) { + return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> Objects.requireNonNull(KbsRepository.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse()))); + } else { + return Single.just(ServiceResponse.coerceError(processor.getResponse())); + } + }) + .onErrorReturn(ServiceResponse::forUnknownError); } @Override protected Single onVerifySuccess(@NonNull VerifyResponseProcessor processor) { - return registrationRepository.registerAccount(getRegistrationData(), processor.getResult()) + return registrationRepository.registerAccount(getRegistrationData(), processor.getResult(), false) .map(VerifyResponseWithoutKbs::new); } @Override protected Single onVerifySuccessWithRegistrationLock(@NonNull VerifyResponseWithRegistrationLockProcessor processor, String pin) { - return registrationRepository.registerAccount(getRegistrationData(), processor.getResult()) + return registrationRepository.registerAccount(getRegistrationData(), processor.getResult(), true) .map(processor::updatedIfRegistrationFailed); } @@ -154,7 +236,216 @@ private RegistrationData getRegistrationData() { registrationRepository.getRegistrationId(), registrationRepository.getProfileKey(getNumber().getE164Number()), getFcmToken(), - registrationRepository.getPniRegistrationId()); + registrationRepository.getPniRegistrationId(), + getSessionId() != null ? null : getRecoveryPassword()); + } + + public @NonNull Single verifyReRegisterWithPin(@NonNull String pin) { + return Single.fromCallable(() -> verifyReRegisterWithPinInternal(pin)) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .flatMap(data -> { + if (data.canProceed) { + return updateFcmTokenValue().subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .onErrorReturnItem("") + .flatMap(s -> verifyReRegisterWithRecoveryPassword(pin, data.pinData)); + } else { + throw new IllegalStateException("Unable to get token or master key"); + } + }) + .onErrorReturn(t -> new VerifyResponseWithRegistrationLockProcessor(ServiceResponse.forUnknownError(t), getKeyBackupCurrentToken())) + .map(p -> { + if (p instanceof VerifyResponseWithRegistrationLockProcessor) { + VerifyResponseWithRegistrationLockProcessor lockProcessor = (VerifyResponseWithRegistrationLockProcessor) p; + if (lockProcessor.wrongPin() && lockProcessor.getTokenData() != null) { + TokenData newToken = TokenData.withResponse(lockProcessor.getTokenData(), lockProcessor.getTokenResponse()); + return new VerifyResponseWithRegistrationLockProcessor(lockProcessor.getResponse(), newToken); + } + } + + return p; + }) + .doOnSuccess(p -> { + if (p.hasResult()) { + restoreFromStorageService(); + } + }) + .observeOn(AndroidSchedulers.mainThread()); + } + + @WorkerThread + private @NonNull ReRegistrationData verifyReRegisterWithPinInternal(@NonNull String pin) + throws KeyBackupSystemWrongPinException, IOException, KeyBackupSystemNoDataException + { + String localPinHash = SignalStore.kbsValues().getLocalPinHash(); + + if (hasRecoveryPassword() && localPinHash != null) { + if (PinHashing.verifyLocalPinHash(localPinHash, pin)) { + Log.i(TAG, "Local pin matches input, attempting registration"); + return ReRegistrationData.canProceed(new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse())); + } else { + throw new KeyBackupSystemWrongPinException(new TokenResponse(null, null, 0)); + } + } else { + TokenData data = getKeyBackupCurrentToken(); + if (data == null) { + Log.w(TAG, "No token data, abort skip flow"); + return ReRegistrationData.cannotProceed(); + } + + KbsPinData kbsPinData = KbsRepository.restoreMasterKey(pin, data.getEnclave(), data.getBasicAuth(), data.getTokenResponse()); + if (kbsPinData == null || kbsPinData.getMasterKey() == null) { + Log.w(TAG, "No kbs data, abort skip flow"); + return ReRegistrationData.cannotProceed(); + } + + setRecoveryPassword(kbsPinData.getMasterKey().deriveRegistrationRecoveryPassword()); + setKeyBackupTokenData(data); + return ReRegistrationData.canProceed(kbsPinData); + } + } + + private Single verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull KbsPinData pinData) { + RegistrationData registrationData = getRegistrationData(); + if (registrationData.getRecoveryPassword() == null) { + throw new IllegalStateException("No valid recovery password"); + } + + return verifyAccountRepository.registerAccount(null, registrationData, null, null) + .observeOn(Schedulers.io()) + .onErrorReturn(ServiceResponse::forUnknownError) + .map(VerifyResponseWithoutKbs::new) + .flatMap(processor -> { + if (processor.registrationLock()) { + return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> pinData) + .onErrorReturn(ServiceResponse::forUnknownError) + .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken())); + } else { + return Single.just(processor); + } + }) + .flatMap(processor -> { + if (processor.hasResult()) { + VerifyResponse verifyResponse = processor.getResult(); + boolean setRegistrationLockEnabled = verifyResponse.getKbsData() != null; + + if (!setRegistrationLockEnabled) { + verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin); + } + + return registrationRepository.registerAccount(registrationData, verifyResponse, setRegistrationLockEnabled) + .map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken())); + } else { + return Single.just(processor); + } + }); + } + + public @NonNull Single canEnterSkipSmsFlow() { + if (userSkippedReRegisterFlow) { + return Single.just(false); + } + + return Single.just(hasRecoveryPassword()) + .flatMap(hasRecoveryPassword -> { + Log.i(TAG, "Checking if user has existing recovery password: " + hasRecoveryPassword); + if (hasRecoveryPassword) { + return Single.just(true); + } else { + return checkForValidKbsAuthCredentials(); + } + }); + } + + private Single checkForValidKbsAuthCredentials() { + final List kbsAuthTokenList = SignalStore.kbsValues().getKbsAuthTokenList(); + List usernamePasswords = kbsAuthTokenList + .stream() + .limit(10) + .map(t -> { + try { + return new String(Base64.decode(t.replace("Basic ", "").trim()), StandardCharsets.ISO_8859_1); + } catch (IOException e) { + return null; + } + }) + .collect(Collectors.toList()); + + if (usernamePasswords.isEmpty()) { + return Single.just(false); + } + + return registrationRepository.getKbsAuthCredential(getRegistrationData(), usernamePasswords) + .flatMap(p -> { + if (p.getValid() != null) { + return kbsRepository.getToken(p.getValid()) + .flatMap(r -> { + if (r.getResult().isPresent()) { + TokenData tokenData = r.getResult().get(); + setKeyBackupTokenData(tokenData); + return Single.just(tokenData.getTriesRemaining() > 0); + } else { + return Single.just(false); + } + }); + } else { + return Single.just(false); + } + }) + .onErrorReturnItem(false) + .observeOn(AndroidSchedulers.mainThread()); + } + + public Single updateFcmTokenValue() { + return verifyAccountRepository.getFcmToken().observeOn(AndroidSchedulers.mainThread()).doOnSuccess(this::setFcmToken); + } + + private void restoreFromStorageService() { + SignalStore.onboarding().clearAll(); + + Stopwatch stopwatch = new Stopwatch("ReRegisterRestore"); + + ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN); + stopwatch.split("AccountRestore"); + + ApplicationDependencies + .getJobManager() + .startChain(new StorageSyncJob()) + .then(new NewRegistrationUsernameSyncJob()) + .enqueueAndBlockUntilCompletion(TimeUnit.SECONDS.toMillis(10)); + stopwatch.split("ContactRestore"); + + try { + FeatureFlags.refreshSync(); + } catch (IOException e) { + Log.w(TAG, "Failed to refresh flags.", e); + } + stopwatch.split("FeatureFlags"); + + stopwatch.stop(TAG); + } + + private boolean hasRecoveryPassword() { + return getRecoveryPassword() != null && Objects.equals(getRegistrationData().getE164(), SignalStore.account().getE164()); + } + + private static class ReRegistrationData { + public boolean canProceed; + public KbsPinData pinData; + + private ReRegistrationData(boolean canProceed, @Nullable KbsPinData pinData) { + this.canProceed = canProceed; + this.pinData = pinData; + } + + public static ReRegistrationData cannotProceed() { + return new ReRegistrationData(false, null); + } + + public static ReRegistrationData canProceed(@NonNull KbsPinData pinData) { + return new ReRegistrationData(true, pinData); + } } public static final class Factory extends AbstractSavedStateViewModelFactory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt b/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt index bdb489a438..6449b59040 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt @@ -33,7 +33,6 @@ object ReleaseChannel { messageRanges: BodyRangeList? = null, storyType: StoryType = StoryType.NONE ): MessageTable.InsertResult? { - val attachments: Optional> = if (media != null) { val attachment = SignalServiceAttachmentPointer( CDN_NUMBER, diff --git a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java index 34ea5b6e31..944f63adea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java @@ -68,7 +68,7 @@ protected long getDelayForEvent(@NonNull ViewOnceExpirationInfo event) { @AnyThread @Override - protected void scheduleAlarm(@NonNull Application application, long delay) { + protected void scheduleAlarm(@NonNull Application application, ViewOnceExpirationInfo event, long delay) { setAlarm(application, delay, ViewOnceAlarm.class); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java index 30f67b0f3b..0551653103 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java @@ -204,7 +204,6 @@ public void onCameraSwitchError(String errorMessage) { cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount())); } - @TargetApi(21) private static class FilteredCamera2Enumerator extends Camera2Enumerator { private static final String TAG = Log.tag(Camera2Enumerator.class); diff --git a/app/src/main/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheet.kt index e965937819..ef48643057 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheet.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.safety import android.content.Context import android.os.Bundle import androidx.fragment.app.FragmentManager +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog import org.thoughtcrime.securesms.database.model.IdentityRecord @@ -126,7 +127,7 @@ object SafetyNumberBottomSheet { * @throws IllegalArgumentException if the bundle does not contain the correct parcelized arguments. */ fun getArgsFromBundle(bundle: Bundle): SafetyNumberBottomSheetArgs { - val args = bundle.getParcelable(ARGS) + val args: SafetyNumberBottomSheetArgs? = bundle.getParcelableCompat(ARGS, SafetyNumberBottomSheetArgs::class.java) Preconditions.checkArgument(args != null) return args!! } diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/TextEntryDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/scribbles/TextEntryDialogFragment.kt index b37e7f7044..e6635763b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/TextEntryDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/TextEntryDialogFragment.kt @@ -12,6 +12,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.AppCompatSeekBar import androidx.fragment.app.FragmentManager import com.airbnb.lottie.SimpleColorFilter +import org.signal.core.util.getParcelableCompat import org.signal.imageeditor.core.HiddenEditText import org.signal.imageeditor.core.model.EditorElement import org.signal.imageeditor.core.renderers.MultiLineTextRenderer @@ -42,7 +43,7 @@ class TextEntryDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_im dismissAllowingStateLoss() } - val element: EditorElement = requireNotNull(requireArguments().getParcelable("element")) + val element: EditorElement = requireNotNull(requireArguments().getParcelableCompat("element", EditorElement::class.java)) val incognito = requireArguments().getBoolean("incognito") val selectAll = requireArguments().getBoolean("selectAll") diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringStoriesManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringStoriesManager.kt index adb9904a3a..69ac4754cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringStoriesManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringStoriesManager.kt @@ -51,7 +51,7 @@ class ExpiringStoriesManager( override fun getDelayForEvent(event: Event): Long = event.delay @WorkerThread - override fun scheduleAlarm(application: Application, delay: Long) { + override fun scheduleAlarm(application: Application, event: Event, delay: Long) { setAlarm(application, delay, ExpireStoriesAlarm::class.java) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java index 74bd48c4eb..418840ac4d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -30,7 +30,7 @@ protected long getNextScheduledExecutionTime(Context context) { @Override protected long onAlarm(Context context, long scheduledTime) { if (SignalStore.settings().isBackupEnabled()) { - LocalBackupJob.enqueue(shouldScheduleExact()); + LocalBackupJob.enqueue(false); } return setNextBackupTimeToIntervalFromNow(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/PendingRetryReceiptManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/PendingRetryReceiptManager.java index 9fa38afe4f..eae2976c60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/PendingRetryReceiptManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/PendingRetryReceiptManager.java @@ -70,7 +70,7 @@ protected long getDelayForEvent(@NonNull PendingRetryReceiptModel event) { @AnyThread @Override - protected void scheduleAlarm(@NonNull Application application, long delay) { + protected void scheduleAlarm(@NonNull Application application, PendingRetryReceiptModel event, long delay) { setAlarm(application, delay, PendingRetryReceiptAlarm.class); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ScheduledMessageManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/ScheduledMessageManager.kt index d7722f59ac..bd019c5b1d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ScheduledMessageManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ScheduledMessageManager.kt @@ -1,14 +1,19 @@ package org.thoughtcrime.securesms.service import android.app.Application +import android.app.PendingIntent import android.content.Context import android.content.Intent import androidx.annotation.WorkerThread +import org.signal.core.util.PendingIntentFlags import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.conversation.ConversationIntents import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.IndividualSendJob import org.thoughtcrime.securesms.jobs.PushGroupSendJob +import org.thoughtcrime.securesms.recipients.RecipientId import kotlin.time.Duration.Companion.seconds /** @@ -31,19 +36,25 @@ class ScheduledMessageManager( @Suppress("UsePropertyAccessSyntax") @WorkerThread override fun getNextClosestEvent(): Event? { - val oldestTimestamp = messagesTable.getOldestScheduledSendTimestamp() ?: return null + val oldestMessage: MediaMmsMessageRecord? = messagesTable.getOldestScheduledSendTimestamp() as? MediaMmsMessageRecord - val delay = (oldestTimestamp - System.currentTimeMillis()).coerceAtLeast(0) + if (oldestMessage == null) { + cancelAlarm(application, ScheduledMessagesAlarm::class.java) + return null + } + + val delay = (oldestMessage.scheduledDate - System.currentTimeMillis()).coerceAtLeast(0) Log.i(TAG, "The next scheduled message needs to be sent in $delay ms.") - return Event(delay) + return Event(delay, oldestMessage.recipient.id, oldestMessage.threadId) } @WorkerThread override fun executeEvent(event: Event) { val scheduledMessagesToSend = messagesTable.getScheduledMessagesBefore(System.currentTimeMillis()) for (record in scheduledMessagesToSend) { - if (messagesTable.clearScheduledStatus(record.threadId, record.id, record.recipient.expiresInSeconds.seconds.inWholeMilliseconds)) { + val expiresIn = SignalDatabase.recipients.getExpiresInSeconds(record.recipient.id) + if (messagesTable.clearScheduledStatus(record.threadId, record.id, expiresIn.seconds.inWholeMilliseconds)) { if (record.recipient.isPushGroup) { PushGroupSendJob.enqueue(application, ApplicationDependencies.getJobManager(), record.id, record.recipient.id, emptySet(), true) } else { @@ -59,11 +70,18 @@ class ScheduledMessageManager( override fun getDelayForEvent(event: Event): Long = event.delay @WorkerThread - override fun scheduleAlarm(application: Application, delay: Long) { - trySetExactAlarm(application, System.currentTimeMillis() + delay, ScheduledMessagesAlarm::class.java) + override fun scheduleAlarm(application: Application, event: Event, delay: Long) { + val conversationIntent = ConversationIntents.createBuilder(application, event.recipientId, event.threadId).build() + + trySetExactAlarm( + application, + System.currentTimeMillis() + delay, + ScheduledMessagesAlarm::class.java, + PendingIntent.getActivity(application, 0, conversationIntent, PendingIntentFlags.mutable()) + ) } - data class Event(val delay: Long) + data class Event(val delay: Long, val recipientId: RecipientId, val threadId: Long) class ScheduledMessagesAlarm : ExportedBroadcastReceiver() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/TimedEventManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/TimedEventManager.java index 87b878a28a..3ed28ed8eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/TimedEventManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/TimedEventManager.java @@ -60,7 +60,7 @@ public void scheduleIfNecessary() { scheduleIfNecessary(); }, delay); - scheduleAlarm(application, delay); + scheduleAlarm(application, event, delay); } }); } @@ -88,7 +88,7 @@ public void scheduleIfNecessary() { * use {@link #setAlarm(Context, long, Class)} as a helper method. */ @AnyThread - protected abstract void scheduleAlarm(@NonNull Application application, long delay); + protected abstract void scheduleAlarm(@NonNull Application application, E event, long delay); /** * Helper method to set an alarm. @@ -102,7 +102,7 @@ protected static void setAlarm(@NonNull Context context, long delay, @NonNull Cl alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, pendingIntent); } - protected static void trySetExactAlarm(@NonNull Context context, long timestamp, @NonNull Class alarmClass) { + protected static void trySetExactAlarm(@NonNull Context context, long timestamp, @NonNull Class alarmClass, @NonNull PendingIntent showIntent) { Intent intent = new Intent(context, alarmClass); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable()); AlarmManager alarmManager = ServiceUtil.getAlarmManager(context); @@ -112,11 +112,7 @@ protected static void trySetExactAlarm(@NonNull Context context, long timestamp, boolean hasManagerPermission = Build.VERSION.SDK_INT < 31 || alarmManager.canScheduleExactAlarms(); if (hasManagerPermission) { try { - if (Build.VERSION.SDK_INT >= 23) { - alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timestamp, pendingIntent); - } else { - alarmManager.setExact(AlarmManager.RTC_WAKEUP, timestamp, pendingIntent); - } + alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(timestamp, showIntent), pendingIntent); return; } catch (Exception e) { Log.w(TAG, e); @@ -126,4 +122,16 @@ protected static void trySetExactAlarm(@NonNull Context context, long timestamp, Log.w(TAG, "Unable to schedule exact alarm, falling back to inexact alarm, scheduling alarm for: " + timestamp); alarmManager.set(AlarmManager.RTC_WAKEUP, timestamp, pendingIntent); } + + protected static void cancelAlarm(@NonNull Context context, @NonNull Class alarmClass) { + Intent intent = new Intent(context, alarmClass); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable()); + + try { + pendingIntent.cancel(); + ServiceUtil.getAlarmManager(context).cancel(pendingIntent); + } catch (SecurityException e) { + Log.i(TAG, "Unable to cancel alarm because we don't have permission"); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java index 53cbce96a4..8d73e03a7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java @@ -74,7 +74,7 @@ protected long getDelayForEvent(@NonNull TrimEvent event) { } @Override - protected void scheduleAlarm(@NonNull Application application, long delay) { + protected void scheduleAlarm(@NonNull Application application, TrimEvent event, long delay) { setAlarm(application, delay, TrimThreadsByDateAlarm.class); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt index 80b130cdaa..b17f8e1645 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt @@ -146,7 +146,7 @@ object AndroidTelecomUtil { AndroidCallConnectionService.KEY_RECIPIENT_ID to recipientId.serialize(), AndroidCallConnectionService.KEY_CALL_ID to callId, AndroidCallConnectionService.KEY_VIDEO_CALL to isVideoCall - ), + ) ) try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.kt index 68b4b6b1aa..05300c71f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.kt @@ -17,7 +17,7 @@ data class LocalDeviceState constructor( var activeDevice: SignalAudioManager.AudioDevice = SignalAudioManager.AudioDevice.NONE, var availableDevices: Set = emptySet(), var bluetoothPermissionDenied: Boolean = false, - var networkConnectionType: PeerConnection.AdapterType = PeerConnection.AdapterType.UNKNOWN, + var networkConnectionType: PeerConnection.AdapterType = PeerConnection.AdapterType.UNKNOWN ) { fun duplicate(): LocalDeviceState { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcEphemeralState.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcEphemeralState.kt index de598abcef..a0a9da3f13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcEphemeralState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcEphemeralState.kt @@ -8,5 +8,5 @@ import org.thoughtcrime.securesms.events.CallParticipantId */ data class WebRtcEphemeralState( val localAudioLevel: CallParticipant.AudioLevel = CallParticipant.AudioLevel.LOWEST, - val remoteAudioLevels: Map = emptyMap(), + val remoteAudioLevels: Map = emptyMap() ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt index 9060d44310..c9f0ab57aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/v2/ShareActivity.kt @@ -14,6 +14,9 @@ import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import org.signal.core.util.Result +import org.signal.core.util.getParcelableArrayListCompat +import org.signal.core.util.getParcelableArrayListExtraCompat +import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.MainActivity import org.thoughtcrime.securesms.PassphraseRequiredActivity @@ -135,7 +138,7 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C throw AssertionError("Expected a recipient selection!") } - val contactSearchKeys: List = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION)!! + val contactSearchKeys: List = bundle.getParcelableArrayListCompat(MultiselectForwardFragment.RESULT_SELECTION, ContactSearchKey.RecipientSearchKey::class.java)!! viewModel.onContactSelectionConfirmed(contactSearchKeys) } @@ -161,12 +164,12 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C } ?: Result.failure(IntentError.SEND_MULTIPLE_TEXT) } intent.action == Intent.ACTION_SEND_MULTIPLE && intent.hasExtra(Intent.EXTRA_STREAM) -> { - intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { + intent.getParcelableArrayListExtraCompat(Intent.EXTRA_STREAM, Uri::class.java)?.let { Result.success(UnresolvedShareData.ExternalMultiShare(it)) } ?: Result.failure(IntentError.SEND_MULTIPLE_STREAM) } intent.action == Intent.ACTION_SEND && intent.hasExtra(Intent.EXTRA_STREAM) -> { - intent.getParcelableExtra(Intent.EXTRA_STREAM)?.let { + intent.getParcelableExtraCompat(Intent.EXTRA_STREAM, Uri::class.java)?.let { Result.success(UnresolvedShareData.ExternalSingleShare(it, intent.type)) } ?: extractSingleExtraTextFromIntent(IntentError.SEND_STREAM) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java index 5113e1f6d7..37016b9fbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/AccountRecordProcessor.java @@ -125,8 +125,9 @@ boolean isInvalid(@NonNull SignalAccountRecord remote) { boolean storiesDisabled = remote.isStoriesDisabled(); boolean hasReadOnboardingStory = remote.hasReadOnboardingStory() || remote.hasViewedOnboardingStory() || local.hasReadOnboardingStory() || local.hasViewedOnboardingStory() ; boolean hasSeenGroupStoryEducation = remote.hasSeenGroupStoryEducationSheet() || local.hasSeenGroupStoryEducationSheet(); - boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled, storyViewReceiptsState, hasReadOnboardingStory); - boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled, storyViewReceiptsState, hasReadOnboardingStory); + String username = remote.getUsername(); + boolean matchesRemote = doParamsMatch(remote, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled, storyViewReceiptsState, hasReadOnboardingStory, username); + boolean matchesLocal = doParamsMatch(local, unknownFields, givenName, familyName, avatarUrlPath, profileKey, noteToSelfArchived, noteToSelfForcedUnread, readReceipts, typingIndicators, sealedSenderIndicators, linkPreviews, phoneNumberSharingMode, unlisted, pinnedConversations, preferContactAvatars, payments, universalExpireTimer, primarySendsSms, e164, defaultReactions, subscriber, displayBadgesOnProfile, subscriptionManuallyCancelled, keepMutedChatsArchived, hasSetMyStoriesPrivacy, hasViewedOnboardingStory, storiesDisabled, storyViewReceiptsState, hasReadOnboardingStory, username); if (matchesRemote) { return remote; @@ -163,6 +164,7 @@ boolean isInvalid(@NonNull SignalAccountRecord remote) { .setStoriesDisabled(storiesDisabled) .setHasReadOnboardingStory(hasReadOnboardingStory) .setHasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducation) + .setUsername(username) .build(); } } @@ -211,7 +213,8 @@ private static boolean doParamsMatch(@NonNull SignalAccountRecord contact, boolean hasViewedOnboardingStory, boolean storiesDisabled, @NonNull OptionalBool storyViewReceiptsState, - boolean hasReadOnboardingStory) + boolean hasReadOnboardingStory, + @Nullable String username) { return Arrays.equals(contact.serializeUnknownFields(), unknownFields) && Objects.equals(contact.getGivenName().orElse(""), givenName) && @@ -241,6 +244,7 @@ private static boolean doParamsMatch(@NonNull SignalAccountRecord contact, contact.hasViewedOnboardingStory() == hasViewedOnboardingStory && contact.isStoriesDisabled() == storiesDisabled && contact.getStoryViewReceiptsState().equals(storyViewReceiptsState) && - contact.hasReadOnboardingStory() == hasReadOnboardingStory; + contact.hasReadOnboardingStory() == hasReadOnboardingStory && + Objects.equals(contact.getUsername(), username); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java index c02c7647e9..18a37b3f1b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/ContactRecordProcessor.java @@ -19,14 +19,23 @@ import org.whispersystems.signalservice.api.util.OptionalUtil; import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState; +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.regex.Pattern; public class ContactRecordProcessor extends DefaultStorageRecordProcessor { private static final String TAG = Log.tag(ContactRecordProcessor.class); + private static final Pattern E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{0,18}$"); + private final RecipientTable recipientTable; private final ACI selfAci; @@ -47,6 +56,69 @@ public ContactRecordProcessor() { this.selfE164 = selfE164; } + /** + * For contact records specifically, we have some extra work that needs to be done before we process all of the records. + * + * We have to look and see if there is an unregistered ACI-only record and another E164/PNI-only record that points to the + * same local contact row. + * + * If so, we actually want to mimic the split and turn them into two separate contact rows locally. The reasons are nuanced, + * but the TL;DR is that we want to split unregistered users into separate rows so that a user could re-register and get a + * different ACI. + */ + @Override + public void process(@NonNull Collection remoteRecords, @NonNull StorageKeyGenerator keyGenerator) throws IOException { + if (!FeatureFlags.phoneNumberPrivacy()) { + super.process(remoteRecords, keyGenerator); + return; + } + + List unregisteredAciOnly = new ArrayList<>(); + List pniE164Only = new ArrayList<>(); + + for (SignalContactRecord remoteRecord : remoteRecords) { + if (isInvalid(remoteRecord)) { + continue; + } + + if (remoteRecord.getUnregisteredTimestamp() > 0 && remoteRecord.getServiceId() != null && !remoteRecord.getPni().isPresent() && !remoteRecord.getNumber().isPresent()) { + unregisteredAciOnly.add(remoteRecord); + } else if (remoteRecord.getServiceId() != null && remoteRecord.getServiceId().equals(remoteRecord.getPni().orElse(null))) { + pniE164Only.add(remoteRecord); + } + } + + if (unregisteredAciOnly.isEmpty() || pniE164Only.isEmpty()) { + super.process(remoteRecords, keyGenerator); + return; + } + + Log.i(TAG, "We have some unregistered ACI-only contacts as well as some PNI-only contacts. Need to do an intersection to detect any possible required splits."); + + TreeSet localMatches = new TreeSet<>(this); + + for (SignalContactRecord aciOnly : unregisteredAciOnly) { + Optional localMatch = getMatching(aciOnly, keyGenerator); + + if (localMatch.isPresent()) { + localMatches.add(localMatch.get()); + } + } + + for (SignalContactRecord pniOnly : pniE164Only) { + Optional localMatch = getMatching(pniOnly, keyGenerator); + + if (localMatch.isPresent() && localMatches.contains(localMatch.get())) { + Log.w(TAG, "Found a situation where we need to split our local record in two in order to match the remote state."); + + SignalDatabase.recipients().splitForStorageSync(localMatch.get().getId().getRaw()); + } + } + + + super.process(remoteRecords, keyGenerator); + } + /** * Error cases: * - You can't have a contact record without an address component. @@ -72,6 +144,9 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { } else if (!FeatureFlags.phoneNumberPrivacy() && remote.getServiceId().equals(remote.getPni().orElse(null))) { Log.w(TAG, "Found a PNI-only ContactRecord when PNP is disabled -- marking as invalid."); return true; + } else if (remote.getNumber().isPresent() && !isValidE164(remote.getNumber().get())) { + Log.w(TAG, "Found a record with an invalid E164. Marking as invalid."); + return true; } else { return false; } @@ -197,8 +272,9 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { boolean hidden = remote.isHidden(); String systemGivenName = SignalStore.account().isPrimaryDevice() ? local.getSystemGivenName().orElse("") : remote.getSystemGivenName().orElse(""); String systemFamilyName = SignalStore.account().isPrimaryDevice() ? local.getSystemFamilyName().orElse("") : remote.getSystemFamilyName().orElse(""); - boolean matchesRemote = doParamsMatch(remote, unknownFields, serviceId, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); - boolean matchesLocal = doParamsMatch(local, unknownFields, serviceId, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); + String systemNickname = remote.getSystemNickname().orElse(""); + boolean matchesRemote = doParamsMatch(remote, unknownFields, serviceId, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); + boolean matchesLocal = doParamsMatch(local, unknownFields, serviceId, pni, e164, profileGivenName, profileFamilyName, systemGivenName, systemFamilyName, systemNickname, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden); if (matchesRemote) { return remote; @@ -212,6 +288,7 @@ boolean isInvalid(@NonNull SignalContactRecord remote) { .setProfileFamilyName(profileFamilyName) .setSystemGivenName(systemGivenName) .setSystemFamilyName(systemFamilyName) + .setSystemNickname(systemNickname) .setProfileKey(profileKey) .setUsername(username) .setIdentityState(identityState) @@ -250,6 +327,10 @@ public int compare(@NonNull SignalContactRecord lhs, @NonNull SignalContactRecor } } + private static boolean isValidE164(String value) { + return E164_PATTERN.matcher(value).matches(); + } + private static boolean doParamsMatch(@NonNull SignalContactRecord contact, @Nullable byte[] unknownFields, @NonNull ServiceId serviceId, @@ -259,6 +340,7 @@ private static boolean doParamsMatch(@NonNull SignalContactRecord contact, @NonNull String profileFamilyName, @NonNull String systemGivenName, @NonNull String systemFamilyName, + @NonNull String systemNickname, @Nullable byte[] profileKey, @NonNull String username, @Nullable IdentityState identityState, @@ -280,6 +362,7 @@ private static boolean doParamsMatch(@NonNull SignalContactRecord contact, Objects.equals(contact.getProfileFamilyName().orElse(""), profileFamilyName) && Objects.equals(contact.getSystemGivenName().orElse(""), systemGivenName) && Objects.equals(contact.getSystemFamilyName().orElse(""), systemFamilyName) && + Objects.equals(contact.getSystemNickname().orElse(""), systemNickname) && Arrays.equals(contact.getProfileKey().orElse(null), profileKey) && Objects.equals(contact.getUsername().orElse(""), username) && Objects.equals(contact.getIdentityState(), identityState) && diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java index 188759f5ae..dedfcc1695 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV2RecordProcessor.java @@ -58,7 +58,7 @@ boolean isInvalid(@NonNull SignalGroupV2Record remote) { return StorageSyncModels.localToRemoteRecord(settings); } else { Log.w(TAG, "No local master key. Assuming it matches remote since the groupIds match. Enqueuing a fetch to fix the bad state."); - groupDatabase.fixMissingMasterKey(null, record.getMasterKeyOrThrow()); + groupDatabase.fixMissingMasterKey(record.getMasterKeyOrThrow()); return StorageSyncModels.localToRemoteRecord(settings, record.getMasterKeyOrThrow()); } }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java index 0d51433954..03138b6202 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java @@ -105,11 +105,11 @@ public static boolean profileKeyChanged(StorageRecordUpdate } public static SignalStorageRecord buildAccountRecord(@NonNull Context context, @NonNull Recipient self) { - RecipientTable recipientTable = SignalDatabase.recipients(); - RecipientRecord record = recipientTable.getRecordForSync(self.getId()); - List pinned = Stream.of(SignalDatabase.threads().getPinnedRecipientIds()) - .map(recipientTable::getRecordForSync) - .toList(); + RecipientTable recipientTable = SignalDatabase.recipients(); + RecipientRecord record = recipientTable.getRecordForSync(self.getId()); + List pinned = Stream.of(SignalDatabase.threads().getPinnedRecipientIds()) + .map(recipientTable::getRecordForSync) + .toList(); final OptionalBool storyViewReceiptsState = SignalStore.storyValues().getViewedReceiptsEnabled() ? OptionalBool.ENABLED : OptionalBool.DISABLED; @@ -153,6 +153,7 @@ record = recipientTable.getRecordForSync(self.getId()); .setStoryViewReceiptsState(storyViewReceiptsState) .setHasReadOnboardingStory(hasReadOnboardingStory) .setHasSeenGroupStoryEducationSheet(SignalStore.storyValues().getUserHasSeenGroupStoryEducationSheet()) + .setUsername(self.getUsername().orElse(null)) .build(); return SignalStorageRecord.forAccount(account); diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java index 0ac1a407b3..09617f48b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java @@ -116,6 +116,7 @@ public static List localToRemotePinnedCo .setProfileFamilyName(recipient.getProfileName().getFamilyName()) .setSystemGivenName(recipient.getSystemProfileName().getGivenName()) .setSystemFamilyName(recipient.getSystemProfileName().getFamilyName()) + .setSystemNickname(recipient.getSyncExtras().getSystemNickname()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing() || recipient.getSystemContactUri() != null) .setIdentityKey(recipient.getSyncExtras().getIdentityKey()) @@ -126,6 +127,7 @@ public static List localToRemotePinnedCo .setHideStory(hideStory) .setUnregisteredTimestamp(recipient.getSyncExtras().getUnregisteredTimestamp()) .setHidden(recipient.isHidden()) + .setUsername(recipient.getUsername()) .build(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java index eddd056853..c58b7d42a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncValidations.java @@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Base64; import org.signal.core.util.SetUtil; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.storage.SignalContactRecord; import org.whispersystems.signalservice.api.storage.SignalStorageManifest; import org.whispersystems.signalservice.api.storage.SignalStorageRecord; @@ -103,6 +102,19 @@ public static void validateForcePush(@NonNull SignalStorageManifest manifest, @N } private static void validateManifestAndInserts(@NonNull SignalStorageManifest manifest, @NonNull List inserts, @NonNull Recipient self) { + int accountCount = 0; + for (StorageId id : manifest.getStorageIds()) { + accountCount += id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE ? 1 : 0; + } + + if (accountCount > 1) { + throw new MultipleAccountError(); + } + + if (accountCount == 0) { + throw new MissingAccountError(); + } + Set allSet = new HashSet<>(manifest.getStorageIds()); Set insertSet = new HashSet<>(Stream.of(inserts).map(SignalStorageRecord::getId).toList()); Set rawIdSet = Stream.of(allSet).map(id -> ByteBuffer.wrap(id.getRaw())).collect(Collectors.toSet()); @@ -112,31 +124,43 @@ private static void validateManifestAndInserts(@NonNull SignalStorageManifest ma } if (rawIdSet.size() != allSet.size()) { - throw new DuplicateRawIdError(); - } + List ids = manifest.getStorageIdsByType().get(ManifestRecord.Identifier.Type.CONTACT_VALUE); + if (ids.size() != new HashSet<>(ids).size()) { + throw new DuplicateContactIdError(); + } - if (inserts.size() > insertSet.size()) { - throw new DuplicateInsertInWriteError(); - } + ids = manifest.getStorageIdsByType().get(ManifestRecord.Identifier.Type.GROUPV1_VALUE); + if (ids.size() != new HashSet<>(ids).size()) { + throw new DuplicateGroupV1IdError(); + } - int accountCount = 0; - for (StorageId id : manifest.getStorageIds()) { - accountCount += id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE ? 1 : 0; - } + ids = manifest.getStorageIdsByType().get(ManifestRecord.Identifier.Type.GROUPV2_VALUE); + if (ids.size() != new HashSet<>(ids).size()) { + throw new DuplicateGroupV2IdError(); + } - if (accountCount > 1) { - throw new MultipleAccountError(); + ids = manifest.getStorageIdsByType().get(ManifestRecord.Identifier.Type.STORY_DISTRIBUTION_LIST_VALUE); + if (ids.size() != new HashSet<>(ids).size()) { + throw new DuplicateDistributionListIdError(); + } + + throw new DuplicateRawIdAcrossTypesError(); } - if (accountCount == 0) { - throw new MissingAccountError(); + if (inserts.size() > insertSet.size()) { + throw new DuplicateInsertInWriteError(); } + for (SignalStorageRecord insert : inserts) { if (!allSet.contains(insert.getId())) { throw new InsertNotPresentInFullIdSetError(); } + if (insert.isUnknown()) { + throw new UnknownInsertError(); + } + if (insert.getContact().isPresent()) { SignalContactRecord contact = insert.getContact().get(); @@ -157,7 +181,19 @@ private static void validateManifestAndInserts(@NonNull SignalStorageManifest ma private static final class DuplicateStorageIdError extends Error { } - private static final class DuplicateRawIdError extends Error { + private static final class DuplicateRawIdAcrossTypesError extends Error { + } + + private static final class DuplicateContactIdError extends Error { + } + + private static final class DuplicateGroupV1IdError extends Error { + } + + private static final class DuplicateGroupV2IdError extends Error { + } + + private static final class DuplicateDistributionListIdError extends Error { } private static final class DuplicateInsertInWriteError extends Error { @@ -169,6 +205,9 @@ private static final class InsertNotPresentInFullIdSetError extends Error { private static final class DeletePresentInFullIdSetError extends Error { } + private static final class UnknownInsertError extends Error { + } + private static final class MultipleAccountError extends Error { } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt index f9af7118bc..35a50f2235 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt @@ -8,6 +8,7 @@ import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.dependencies.ApplicationDependencies @@ -135,7 +136,7 @@ class StorySlateView @JvmOverloads constructor( override fun onRestoreInstanceState(state: Parcelable?) { if (state is Bundle) { - val rootState: Parcelable? = state.getParcelable("ROOT") + val rootState: Parcelable? = state.getParcelableCompat("ROOT", Parcelable::class.java) this.state = State.fromCode(state.getInt("STATE", State.HIDDEN.code)) this.postId = state.getLong("ID") super.onRestoreInstanceState(rootState) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostModel.kt index 8696a57c10..eb570126f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostModel.kt @@ -16,6 +16,7 @@ import com.bumptech.glide.load.ResourceDecoder import com.bumptech.glide.load.engine.Resource import com.bumptech.glide.load.resource.SimpleResource import org.signal.core.util.concurrent.safeBlockingGet +import org.signal.core.util.readParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.database.SignalDatabase @@ -80,7 +81,7 @@ data class StoryTextPostModel( return StoryTextPostModel( storyTextPost = StoryTextPost.parseFrom(ParcelUtil.readByteArray(parcel)), storySentAtMillis = parcel.readLong(), - storyAuthor = parcel.readParcelable(RecipientId::class.java.classLoader)!!, + storyAuthor = parcel.readParcelableCompat(RecipientId::class.java)!!, bodyRanges = ParcelUtil.readByteArray(parcel)?.let { BodyRangeList.parseFrom(it) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt index 81f607b6a0..22fb81694e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt @@ -56,13 +56,13 @@ object StoryDialogs { } fun resendStory(context: Context, onDismiss: () -> Unit = {}, resend: () -> Unit) { - MaterialAlertDialogBuilder(context) - .setMessage(R.string.StoryDialogs__story_could_not_be_sent) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.StoryDialogs__send) { _, _ -> resend() } - .setOnDismissListener { onDismiss() } - .show() -} + MaterialAlertDialogBuilder(context) + .setMessage(R.string.StoryDialogs__story_could_not_be_sent) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.StoryDialogs__send) { _, _ -> resend() } + .setOnDismissListener { onDismiss() } + .show() + } fun displayStoryOrProfileImage( context: Context, @@ -92,7 +92,7 @@ object StoryDialogs { context: Context, recipientName: String, onCancelled: () -> Unit = {}, - onHideStoryConfirmed: () -> Unit, + onHideStoryConfirmed: () -> Unit ) { MaterialAlertDialogBuilder(context, R.style.ThemeOverlay_Signal_MaterialAlertDialog) .setTitle(R.string.StoriesLandingFragment__hide_story) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt index 48de725bfe..c98784ed30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt @@ -135,7 +135,6 @@ object StoriesLandingItem { private val addToStoriesView: View = itemView.findViewById(R.id.add_to_story) override fun bind(model: Model) { - presentDateOrStatus(model) setUpClickListeners(model) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt index 7844ece1f8..acbf57d1e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt @@ -122,9 +122,13 @@ class StoriesLandingRepository(context: Context) { hasRepliesFromSelf = messageRecords.any { SignalDatabase.messages.hasSelfReplyInStory(it.id) }, isHidden = sender.shouldHideStory(), primaryStory = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, messageRecords[primaryIndex]), - secondaryStory = if (sender.isMyStory) messageRecords.drop(1).firstOrNull()?.let { - ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it) - } else null, + secondaryStory = if (sender.isMyStory) { + messageRecords.drop(1).firstOrNull()?.let { + ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it) + } + } else { + null + }, sendingCount = sendingCount, failureCount = failureCount ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/connections/ViewAllSignalConnectionsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/connections/ViewAllSignalConnectionsFragment.kt index ba53a4c19b..1afbfa8228 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/connections/ViewAllSignalConnectionsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/connections/ViewAllSignalConnectionsFragment.kt @@ -29,7 +29,7 @@ class ViewAllSignalConnectionsFragment : Fragment(R.layout.view_all_signal_conne selectionLimits = SelectionLimits(0, 0), displayCheckBox = false, displaySmsTag = ContactSearchAdapter.DisplaySmsTag.IF_NOT_REGISTERED, - displayPhoneNumber = ContactSearchAdapter.DisplayPhoneNumber.NEVER, + displaySecondaryInformation = ContactSearchAdapter.DisplaySecondaryInformation.NEVER, mapStateToConfiguration = { getConfiguration() }, performSafetyNumberChecks = false ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/create/CreateStoryNameFieldItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/create/CreateStoryNameFieldItem.kt index d002630801..8ba4dc6c10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/create/CreateStoryNameFieldItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/create/CreateStoryNameFieldItem.kt @@ -22,7 +22,7 @@ object CreateStoryNameFieldItem { class Model( val body: CharSequence, - val error: CharSequence?, + val error: CharSequence? ) : PreferenceModel() { override fun areItemsTheSame(newItem: Model): Boolean = true override fun areContentsTheSame(newItem: Model): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt index f78e9c26a9..0f39f3f6db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/select/BaseStoryRecipientSelectionFragment.kt @@ -131,7 +131,7 @@ abstract class BaseStoryRecipientSelectionFragment : Fragment(R.layout.stories_b override fun getHeaderAction(): HeaderAction { return HeaderAction( - R.string.BaseStoryRecipientSelectionFragment__select_all, + R.string.BaseStoryRecipientSelectionFragment__select_all ) { viewModel.toggleSelectAll() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsState.kt index d77b38a813..9d3b58132c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsState.kt @@ -7,5 +7,5 @@ data class StoriesPrivacySettingsState( val areViewReceiptsEnabled: Boolean, val isUpdatingEnabledState: Boolean = false, val storyContactItems: List = emptyList(), - val userHasStories: Boolean = false, + val userHasStories: Boolean = false ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsViewModel.kt index 0937d8c4af..40ccfc834c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsViewModel.kt @@ -48,7 +48,7 @@ class StoriesPrivacySettingsViewModel( val configuration = ContactSearchConfiguration.build { addSection( ContactSearchConfiguration.Section.Stories( - includeHeader = false, + includeHeader = false ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/AddToGroupStoryDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/AddToGroupStoryDelegate.kt index 573bdcf900..0602fac459 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/AddToGroupStoryDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/AddToGroupStoryDelegate.kt @@ -118,7 +118,7 @@ class AddToGroupStoryDelegate( MessageSender.sendStories( ApplicationDependencies.getApplication(), secureMessages, - null, + null ) { Log.d(TAG, "Sent.") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt index 2412cc9479..d20b860300 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt @@ -15,6 +15,8 @@ import androidx.window.layout.WindowMetricsCalculator import com.bumptech.glide.Glide import com.bumptech.glide.MemoryCategory import org.signal.core.util.dp +import org.signal.core.util.getParcelableCompat +import org.signal.core.util.getParcelableExtraCompat import org.signal.core.util.sp import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R @@ -45,7 +47,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { if (savedInstanceState != null) { - val cache: StoryViewStateCache? = savedInstanceState.getParcelable(DATA_CACHE) + val cache: StoryViewStateCache? = savedInstanceState.getParcelableCompat(DATA_CACHE, StoryViewStateCache::class.java) if (cache != null) { storyViewStateViewModel.storyViewStateCache.putAll(cache) } @@ -125,7 +127,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll supportFragmentManager.beginTransaction() .replace( R.id.fragment_container, - StoryViewerFragment.create(intent.getParcelableExtra(ARGS)!!) + StoryViewerFragment.create(intent.getParcelableExtraCompat(ARGS, StoryViewerArgs::class.java)!!) ) .commit() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt index 21c65ea238..417fb9e266 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt @@ -11,6 +11,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.viewpager2.widget.ViewPager2 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import org.signal.core.util.getParcelableArrayListCompat +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient @@ -42,7 +44,7 @@ class StoryViewerFragment : private val lifecycleDisposable = LifecycleDisposable() - private val storyViewerArgs: StoryViewerArgs by lazy { requireArguments().getParcelable(ARGS)!! } + private val storyViewerArgs: StoryViewerArgs by lazy { requireArguments().getParcelableCompat(ARGS, StoryViewerArgs::class.java)!! } private lateinit var storyCrossfader: StoriesSharedElementCrossFaderView @@ -120,7 +122,7 @@ class StoryViewerFragment : } if (savedInstanceState != null && savedInstanceState.containsKey(HIDDEN)) { - val ids: List = savedInstanceState.getParcelableArrayList(HIDDEN)!! + val ids: List = savedInstanceState.getParcelableArrayListCompat(HIDDEN, RecipientId::class.java)!! viewModel.addHiddenAndRefresh(ids.toSet()) } else { viewModel.refresh() diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt index 0389075d27..f52e4db0c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt @@ -20,7 +20,7 @@ import kotlin.math.max class StoryViewerViewModel( private val storyViewerArgs: StoryViewerArgs, - private val repository: StoryViewerRepository, + private val repository: StoryViewerRepository ) : ViewModel() { private val store = RxStore( diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryDisplay.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryDisplay.kt index d8bfa6d5db..bda528efad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryDisplay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryDisplay.kt @@ -10,10 +10,12 @@ enum class StoryDisplay { * View/Reply is underneath story content, corners are rounded, content is not cropped */ LARGE, + /** * View/Reply overlays story content, corners are rounded, content is not cropped */ MEDIUM, + /** * View/Reply is overlays story content, corners are not rounded, content is cropped */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index bece0807f4..5814944126 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -18,7 +18,6 @@ import android.view.View import android.view.animation.Interpolator import android.widget.FrameLayout import android.widget.TextView -import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat @@ -30,12 +29,14 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView import com.google.android.material.progressindicator.CircularProgressIndicatorSpec import com.google.android.material.progressindicator.IndeterminateDrawable import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import org.signal.core.util.DimensionUnit import org.signal.core.util.dp +import org.signal.core.util.getParcelableCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.animation.AnimationCompleteListener @@ -153,7 +154,9 @@ class StoryViewerPageFragment : private var sendingProgressDrawable: IndeterminateDrawable? = null - private val storyViewerPageArgs: StoryViewerPageArgs by lazy(LazyThreadSafetyMode.NONE) { requireArguments().getParcelable(ARGS)!! } + private val storyViewerPageArgs: StoryViewerPageArgs by lazy(LazyThreadSafetyMode.NONE) { + requireArguments().getParcelableCompat(ARGS, StoryViewerPageArgs::class.java)!! + } @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -173,7 +176,7 @@ class StoryViewerPageFragment : val moreButton: View = view.findViewById(R.id.more) val distributionList: TextView = view.findViewById(R.id.distribution_list) val cardWrapper: TouchInterceptingFrameLayout = view.findViewById(R.id.story_content_card_touch_interceptor) - val card: CardView = view.findViewById(R.id.story_content_card) + val card: MaterialCardView = view.findViewById(R.id.story_content_card) val caption: TextView = view.findViewById(R.id.story_caption) val largeCaption: TextView = view.findViewById(R.id.story_large_caption) val largeCaptionOverlay: View = view.findViewById(R.id.story_large_caption_overlay) @@ -228,7 +231,7 @@ class StoryViewerPageFragment : val singleTapHandler = SingleTapHandler( cardWrapper, viewModel::goToNextPost, - viewModel::goToPreviousPost, + viewModel::goToPreviousPost ) val gestureDetector = GestureDetectorCompat( @@ -245,7 +248,9 @@ class StoryViewerPageFragment : gestureDetector.setOnDoubleTapListener(null) val scaleListener = StoryScaleListener( - viewModel, sharedViewModel, card + viewModel, + sharedViewModel, + card ) val scaleDetector = ScaleGestureDetector( @@ -605,7 +610,7 @@ class StoryViewerPageFragment : private fun adjustConstraintsForScreenDimensions( viewsAndReplies: View, cardWrapper: View, - card: CardView + card: MaterialCardView ) { val constraintSet = ConstraintSet() constraintSet.clone(storyPageContainer) @@ -1062,7 +1067,7 @@ class StoryViewerPageFragment : viewModel.setIsDisplayingForwardDialog(true) MultiselectForwardFragmentArgs.create( requireContext(), - storyPost.conversationMessage.multiselectCollection.toSet(), + storyPost.conversationMessage.multiselectCollection.toSet() ) { MultiselectForwardFragment.showBottomSheet(childFragmentManager, it) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt index 701df9a796..74a9b52f69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt @@ -6,11 +6,11 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.util.AttributeSet import android.widget.ImageView -import androidx.cardview.widget.CardView import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target +import com.google.android.material.card.MaterialCardView import org.signal.core.util.DimensionUnit import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition @@ -24,7 +24,7 @@ import kotlin.reflect.KProperty class StoriesSharedElementCrossFaderView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null -) : CardView(context, attrs), CrossfaderTransition.Crossfadeable { +) : MaterialCardView(context, attrs), CrossfaderTransition.Crossfadeable { companion object { val CORNER_RADIUS_START = DimensionUnit.DP.toPixels(12f) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReactionBar.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReactionBar.kt index b8148ef9ee..a8203ddb08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReactionBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReactionBar.kt @@ -63,7 +63,6 @@ class StoryReactionBar @JvmOverloads constructor( animatorSet?.cancel() animatorSet = AnimatorSet().apply { - playTogether( emojiViews.flatMap { listOf(ObjectAnimator.ofFloat(it, View.ALPHA, 1f), ObjectAnimator.ofFloat(it, View.TRANSLATION_Y, 0f)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt index 0637d22f44..c9d57dedf9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/composer/StoryReplyComposer.kt @@ -178,14 +178,15 @@ class StoryReplyComposer @JvmOverloads constructor( } private fun getReactionEmojis(): List> { - val reactionEmoji = SignalStore.emojiValues().reactions - val recentEmoji = RecentEmojiPageModel(context, ReactWithAnyEmojiBottomSheetDialogFragment.REACTION_STORAGE_KEY).emoji - val emoji = (reactionEmoji + recentEmoji).distinct() - val displayEmoji: List = emoji + val reactionDisplayEmoji: List = SignalStore.emojiValues().reactions.map { Emoji(it) } + val canonicalReactionEmoji: List = reactionDisplayEmoji.map { EmojiSource.latest.variationsToCanonical[it.value] ?: it.value } + val canonicalRecentReactionEmoji: Set = LinkedHashSet(RecentEmojiPageModel(context, ReactWithAnyEmojiBottomSheetDialogFragment.REACTION_STORAGE_KEY).emoji) - canonicalReactionEmoji.toSet() + + val recentDisplayEmoji: List = canonicalRecentReactionEmoji .mapNotNull { canonical -> EmojiSource.latest.canonicalToVariations[canonical] } .map { Emoji(it) } - return EmojiReactionsPageModel(emoji, displayEmoji).toMappingModels() + return EmojiReactionsPageModel(canonicalReactionEmoji + canonicalRecentReactionEmoji, reactionDisplayEmoji + recentDisplayEmoji).toMappingModels() } private fun onEmojiToggleClicked() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt index b019e22356..766d47fa7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt @@ -9,6 +9,7 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.setFragmentResult import androidx.fragment.app.viewModels import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.KeyboardEntryDialogFragment import org.thoughtcrime.securesms.components.emoji.EmojiEventListener @@ -60,7 +61,7 @@ class StoryDirectReplyDialogFragment : get() = requireArguments().getLong(ARG_STORY_ID) private val recipientId: RecipientId? - get() = requireArguments().getParcelable(ARG_RECIPIENT_ID) + get() = requireArguments().getParcelableCompat(ARG_RECIPIENT_ID, RecipientId::class.java) override val withDim: Boolean = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt index 260788c14b..892a11c656 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt @@ -11,6 +11,7 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.recipients.RecipientId @@ -34,7 +35,7 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi get() = requireArguments().getLong(ARG_STORY_ID) private val groupRecipientId: RecipientId - get() = requireArguments().getParcelable(ARG_GROUP_RECIPIENT_ID)!! + get() = requireArguments().getParcelableCompat(ARG_GROUP_RECIPIENT_ID, RecipientId::class.java)!! private val isFromNotification: Boolean get() = requireArguments().getBoolean(ARG_IS_FROM_NOTIFICATION, false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt index ffb0e73f68..0948ba27f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt @@ -18,6 +18,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.kotlin.subscribeBy import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.getParcelableCompat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment @@ -132,7 +133,7 @@ class StoryGroupReplyFragment : get() = requireArguments().getLong(ARG_STORY_ID) private val groupRecipientId: RecipientId - get() = requireArguments().getParcelable(ARG_GROUP_RECIPIENT_ID)!! + get() = requireArguments().getParcelableCompat(ARG_GROUP_RECIPIENT_ID, RecipientId::class.java)!! private val isFromNotification: Boolean get() = requireArguments().getBoolean(ARG_IS_FROM_NOTIFICATION, false) @@ -481,7 +482,6 @@ class StoryGroupReplyFragment : if (!recipient.isPushV2Group) { annotations } else { - val validRecipientIds: Set = recipient.participantIds .map { id -> MentionAnnotation.idToMentionAnnotationValue(id) } .toSet() diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt index 2e9784c353..bc31658fd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt @@ -45,7 +45,7 @@ class OnReactionSentView @JvmOverloads constructor( R.id.emoji_8, R.id.emoji_9, R.id.emoji_10, - R.id.emoji_11, + R.id.emoji_11 ).forEach { findViewById(it).setImageEmoji(emoji) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt index 1112709722..7d3492a55c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt @@ -15,6 +15,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.recipients.RecipientId @@ -41,7 +42,7 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr get() = requireArguments().getLong(ARG_STORY_ID) private val groupRecipientId: RecipientId - get() = requireArguments().getParcelable(ARG_GROUP_RECIPIENT_ID)!! + get() = requireArguments().getParcelableCompat(ARG_GROUP_RECIPIENT_ID, RecipientId::class.java)!! private val startPageIndex: Int get() = requireArguments().getInt(ARG_START_PAGE) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index 2ecd6a9c55..31aef7e885 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -275,7 +275,7 @@ public static void handlePotentialSignalMeUrl(@NonNull FragmentActivity activity } } } else { - Optional serviceId = UsernameUtil.fetchAciForUsername(username); + Optional serviceId = UsernameUtil.fetchAciForUsernameHash(username); if (serviceId.isPresent()) { recipient = Recipient.externalUsername(serviceId.get(), username); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 033b22108c..f95ab3932b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -15,8 +15,6 @@ import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.SelectionLimits; -import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; -import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob; import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver; @@ -87,7 +85,7 @@ public final class FeatureFlags { private static final String USE_AEC3 = "android.calling.useAec3"; private static final String PAYMENTS_COUNTRY_BLOCKLIST = "global.payments.disabledRegions"; public static final String PHONE_NUMBER_PRIVACY = "android.pnp"; - private static final String USE_FCM_FOREGROUND_SERVICE = "android.useFcmForegroundService.3"; + private static final String USE_FCM_FOREGROUND_SERVICE = "android.useFcmForegroundService.4"; private static final String STORIES_AUTO_DOWNLOAD_MAXIMUM = "android.stories.autoDownloadMaximum"; private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList"; private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList"; @@ -105,6 +103,7 @@ public final class FeatureFlags { private static final String PAYPAL_ONE_TIME_DONATIONS = "android.oneTimePayPalDonations.2"; private static final String PAYPAL_RECURRING_DONATIONS = "android.recurringPayPalDonations.3"; private static final String TEXT_FORMATTING = "android.textFormatting"; + private static final String ANY_ADDRESS_PORTS_KILL_SWITCH = "android.calling.fieldTrial.anyAddressPortsKillSwitch"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -159,7 +158,8 @@ public final class FeatureFlags { CHAT_FILTERS, PAYPAL_ONE_TIME_DONATIONS, PAYPAL_RECURRING_DONATIONS, - TEXT_FORMATTING + TEXT_FORMATTING, + ANY_ADDRESS_PORTS_KILL_SWITCH ); @VisibleForTesting @@ -490,6 +490,7 @@ public static boolean useAec3() { return getBoolean(USE_AEC3, true); } + /** Whether or not we show a foreground service on every high-priority FCM push. */ public static boolean useFcmForegroundService() { return getBoolean(USE_FCM_FOREGROUND_SERVICE, false); } @@ -537,6 +538,13 @@ public static boolean textFormatting() { return getBoolean(TEXT_FORMATTING, false); } + /** + * Enable/disable RingRTC field trial for "AnyAddressPortsKillSwitch" + */ + public static boolean callingFieldTrialAnyAddressPortsKillSwitch() { + return getBoolean(ANY_ADDRESS_PORTS_KILL_SWITCH, false); + } + /** Only for rendering debug info. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FullscreenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/util/FullscreenHelper.java index 9c3bbb4bc0..33454e2e8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FullscreenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FullscreenHelper.java @@ -1,12 +1,7 @@ package org.thoughtcrime.securesms.util; -import android.annotation.SuppressLint; import android.app.Activity; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.content.res.Resources; import android.os.Build; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -17,6 +12,9 @@ import androidx.core.view.DisplayCutoutCompat; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; + +import org.signal.core.util.logging.Log; /** * Encapsulates logic to properly show/hide system UI/chrome in a full screen setting. Also @@ -27,13 +25,24 @@ public final class FullscreenHelper { @NonNull private final Activity activity; public FullscreenHelper(@NonNull Activity activity) { + this(activity, false); + } + + /** + * @param activity The activity we are controlling + * @param suppressShowSystemUI Suppresses the initial 'show system ui' call, which can cause the status and navbar to flash + * during some animations. + */ + public FullscreenHelper(@NonNull Activity activity, boolean suppressShowSystemUI) { this.activity = activity; if (Build.VERSION.SDK_INT >= 28) { activity.getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } - showSystemUI(); + if (!suppressShowSystemUI) { + showSystemUI(); + } } public void configureToolbarLayout(@NonNull View spacer, @NonNull View toolbar) { @@ -71,29 +80,6 @@ private static void setSpacerHeight(@NonNull View spacer, int height) { spacer.setVisibility(View.VISIBLE); } - @SuppressLint("SwitchIntDef") - private static int[] makePaddingValuesForAPI19(@NonNull Activity activity) { - int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); - if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - return new int[]{0, 0}; - } - - Resources resources = activity.getResources(); - int statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android"); - int navBarHeightId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); - int statusBarHeight = resources.getDimensionPixelSize(statusBarHeightId); - int navBarHeight = resources.getDimensionPixelSize(navBarHeightId); - - switch (rotation) { - case Surface.ROTATION_90: - return new int[]{statusBarHeight, navBarHeight}; - case Surface.ROTATION_270: - return new int[]{navBarHeight, statusBarHeight}; - default: - return new int[]{0, 0}; - } - } - private static int[] makePaddingValues(WindowInsetsCompat insets) { Insets tappable = insets.getTappableElementInsets(); DisplayCutoutCompat cutout = insets.getDisplayCutout(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index 40b0004b4c..99815398db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -126,8 +126,7 @@ public static void markIdentityVerified(Context context, Recipient recipient, bo } catch (MmsException e) { throw new AssertionError(); } - boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient.isMuted(); - SignalDatabase.threads().update(threadId, !keepThreadArchived); + SignalDatabase.threads().update(threadId, true); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt index 6cdbdb85b5..67496a3b9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt @@ -105,6 +105,6 @@ fun Locale.orderOfDaysInWeek(): List { firstDayOfWeek.plus(3), firstDayOfWeek.plus(4), firstDayOfWeek.plus(5), - firstDayOfWeek.plus(6), + firstDayOfWeek.plus(6) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java b/app/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java index 4a3cf0d7e3..ad05e0fde7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java @@ -11,6 +11,8 @@ import java.io.InputStream; import java.io.Reader; +import javax.annotation.Nullable; + public class JsonUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -53,7 +55,7 @@ public SaneJSONObject(JSONObject delegate) { this.delegate = delegate; } - public String getString(String name) throws JSONException { + public @Nullable String getString(String name) throws JSONException { if (delegate.isNull(name)) return null; else return delegate.getString(name); } @@ -62,6 +64,10 @@ public long getLong(String name) throws JSONException { return delegate.getLong(name); } + public boolean getBoolean(String name) throws JSONException { + return delegate.getBoolean(name); + } + public boolean isNull(String name) { return delegate.isNull(name); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java b/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java index 7d637cb1a4..432ee1b036 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java @@ -16,20 +16,22 @@ import org.thoughtcrime.securesms.R; +import java.lang.ref.WeakReference; + public class LongClickMovementMethod extends LinkMovementMethod { @SuppressLint("StaticFieldLeak") private static LongClickMovementMethod sInstance; - private final GestureDetector gestureDetector; - private View widget; - private LongClickCopySpan currentSpan; + private final GestureDetector gestureDetector; + private WeakReference widget; + private LongClickCopySpan currentSpan; private LongClickMovementMethod(final Context context) { gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public void onLongPress(MotionEvent e) { - if (currentSpan != null && widget != null) { - currentSpan.onLongClick(widget); + if (currentSpan != null && widget != null && widget.get() != null) { + currentSpan.onLongClick(widget.get()); widget = null; currentSpan = null; } @@ -37,8 +39,8 @@ public void onLongPress(MotionEvent e) { @Override public boolean onSingleTapUp(MotionEvent e) { - if (currentSpan != null && widget != null) { - currentSpan.onClick(widget); + if (currentSpan != null && widget != null && widget.get() != null) { + currentSpan.onClick(widget.get()); widget = null; currentSpan = null; } @@ -80,7 +82,7 @@ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event } this.currentSpan = aSingleSpan; - this.widget = widget; + this.widget = new WeakReference<>(widget); return gestureDetector.onTouchEvent(event); } else if (action == MotionEvent.ACTION_UP && Selection.getSelectionEnd(buffer) > 0){ Selection.setSelection(buffer, 0); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SystemWindowInsetsSetter.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SystemWindowInsetsSetter.kt index b17d464cd4..f310b4a554 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SystemWindowInsetsSetter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SystemWindowInsetsSetter.kt @@ -18,12 +18,14 @@ object SystemWindowInsetsSetter { val insets: Insets? = ViewCompat.getRootWindowInsets(view)?.getInsets(insetType) if (Build.VERSION.SDK_INT > 29 && insets != null && !insets.isEmpty()) { - view.setPadding( - insets.left, - insets.top, - insets.right, - insets.bottom - ) + view.post { + view.setPadding( + insets.left, + insets.top, + insets.right, + insets.bottom + ) + } } else { val top = if (insetType and WindowInsetsCompat.Type.statusBars() != 0) { ViewUtil.getStatusBarHeight(view) @@ -37,12 +39,14 @@ object SystemWindowInsetsSetter { 0 } - view.setPadding( - 0, - top, - 0, - bottom - ) + view.post { + view.setPadding( + 0, + top, + 0, + bottom + ) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 17ef6cf180..31f16a310b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -19,7 +19,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.backup.BackupProtos; +import org.thoughtcrime.securesms.backup.proto.SharedPreference; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -273,70 +273,70 @@ public static long getPreferencesToSaveToBackupCount(@NonNull Context context) { return count; } - public static List getPreferencesToSaveToBackup(@NonNull Context context) { - SharedPreferences preferences = getSharedPreferences(context); - List backupProtos = new ArrayList<>(); - String defaultFile = BuildConfig.SIGNAL_PACKAGE_NAME + "_preferences"; + public static List getPreferencesToSaveToBackup(@NonNull Context context) { + SharedPreferences preferences = getSharedPreferences(context); + List backupProtos = new ArrayList<>(); + String defaultFile = BuildConfig.SIGNAL_PACKAGE_NAME + "_preferences"; for (String booleanPreference : booleanPreferencesToBackup) { if (preferences.contains(booleanPreference)) { - backupProtos.add(BackupProtos.SharedPreference.newBuilder() - .setFile(defaultFile) - .setKey(booleanPreference) - .setBooleanValue(preferences.getBoolean(booleanPreference, false)) - .build()); + backupProtos.add(new SharedPreference.Builder() + .file_(defaultFile) + .key(booleanPreference) + .booleanValue(preferences.getBoolean(booleanPreference, false)) + .build()); } } for (String stringPreference : stringPreferencesToBackup) { if (preferences.contains(stringPreference)) { - backupProtos.add(BackupProtos.SharedPreference.newBuilder() - .setFile(defaultFile) - .setKey(stringPreference) - .setValue(preferences.getString(stringPreference, null)) - .build()); + backupProtos.add(new SharedPreference.Builder() + .file_(defaultFile) + .key(stringPreference) + .value_(preferences.getString(stringPreference, null)) + .build()); } } for (String stringSetPreference : stringSetPreferencesToBackup) { if (preferences.contains(stringSetPreference)) { - backupProtos.add(BackupProtos.SharedPreference.newBuilder() - .setFile(defaultFile) - .setKey(stringSetPreference) - .setIsStringSetValue(true) - .addAllStringSetValue(preferences.getStringSet(stringSetPreference, Collections.emptySet())) - .build()); + backupProtos.add(new SharedPreference.Builder() + .file_(defaultFile) + .key(stringSetPreference) + .isStringSetValue(true) + .stringSetValue(new ArrayList<>(preferences.getStringSet(stringSetPreference, Collections.emptySet()))) + .build()); } } for (String booleanPreference : booleanPreferencesToBackupMolly) { if (preferences.contains(booleanPreference)) { - backupProtos.add(BackupProtos.SharedPreference.newBuilder() - .setFile(SecurePreferenceManager.getSecurePreferencesName()) - .setKey(booleanPreference) - .setBooleanValue(preferences.getBoolean(booleanPreference, false)) - .build()); + backupProtos.add(new SharedPreference.Builder() + .file_(SecurePreferenceManager.getSecurePreferencesName()) + .key(booleanPreference) + .booleanValue(preferences.getBoolean(booleanPreference, false)) + .build()); } } for (String stringSetPreference : stringSetPreferencesToBackupMolly) { if (preferences.contains(stringSetPreference)) { - backupProtos.add(BackupProtos.SharedPreference.newBuilder() - .setFile(SecurePreferenceManager.getSecurePreferencesName()) - .setKey(stringSetPreference) - .setIsStringSetValue(true) - .addAllStringSetValue(preferences.getStringSet(stringSetPreference, Collections.emptySet())) - .build()); + backupProtos.add(new SharedPreference.Builder() + .file_(SecurePreferenceManager.getSecurePreferencesName()) + .key(stringSetPreference) + .isStringSetValue(true) + .stringSetValue(new ArrayList<>(preferences.getStringSet(stringSetPreference, Collections.emptySet()))) + .build()); } } for (String integerPreference : integerPreferencesToBackupMolly) { if (preferences.contains(integerPreference)) { - backupProtos.add(BackupProtos.SharedPreference.newBuilder() - .setFile(SecurePreferenceManager.getSecurePreferencesName()) - .setKey(integerPreference) - .setIntegerValue(preferences.getInt(integerPreference, 0)) - .build()); + backupProtos.add(new SharedPreference.Builder() + .file_(SecurePreferenceManager.getSecurePreferencesName()) + .key(integerPreference) + .integerValue(preferences.getInt(integerPreference, 0)) + .build()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java index 71f38ca50e..c5ea443867 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.java @@ -7,13 +7,15 @@ import androidx.annotation.WorkerThread; import org.signal.core.util.logging.Log; +import org.signal.libsignal.usernames.BaseUsernameException; +import org.signal.libsignal.usernames.Username; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.util.Base64UrlSafe; import java.io.IOException; import java.util.Locale; @@ -24,10 +26,10 @@ public class UsernameUtil { private static final String TAG = Log.tag(UsernameUtil.class); - public static final int MIN_LENGTH = 4; - public static final int MAX_LENGTH = 26; + public static final int MIN_LENGTH = 3; + public static final int MAX_LENGTH = 32; - private static final Pattern FULL_PATTERN = Pattern.compile("^[a-z_][a-z0-9_]{3,25}$", Pattern.CASE_INSENSITIVE); + private static final Pattern FULL_PATTERN = Pattern.compile(String.format(Locale.US, "^[a-zA-Z_][a-zA-Z0-9_]{%d,%d}$", MIN_LENGTH - 1, MAX_LENGTH - 1), Pattern.CASE_INSENSITIVE); private static final Pattern DIGIT_START_PATTERN = Pattern.compile("^[0-9].*$"); public static boolean isValidUsernameForSearch(@Nullable String value) { @@ -66,9 +68,19 @@ public static Optional checkUsername(@Nullable String value) { } } + Log.d(TAG, "No local user with this username. Searching remotely."); try { - Log.d(TAG, "No local user with this username. Searching remotely."); - ACI aci = ApplicationDependencies.getSignalServiceAccountManager().getAciByUsername(username); + return fetchAciForUsernameHash(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))); + } catch (BaseUsernameException e) { + return Optional.empty(); + } + } + + @WorkerThread + public static @NonNull Optional fetchAciForUsernameHash(@NonNull String base64UrlSafeEncodedUsernameHash) { + try { + ACI aci = ApplicationDependencies.getSignalServiceAccountManager() + .getAciByUsernameHash(base64UrlSafeEncodedUsernameHash); return Optional.ofNullable(aci); } catch (IOException e) { return Optional.empty(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index ab08876cde..60030a0bc6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ComposeText; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -67,7 +68,7 @@ public class Util { private static final String TAG = Log.tag(Util.class); - private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90); + private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(1000); public static List asList(T... elements) { List result = new LinkedList<>(); @@ -372,8 +373,25 @@ public static byte[] getSecretBytes(@NonNull SecureRandom secureRandom, int size return secret; } + /** + * @return The amount of time (in ms) until this build of Signal will be considered 'expired'. + * Takes into account both the build age as well as any remote deprecation values. + */ public static long getTimeUntilBuildExpiry() { - return TimeUnit.DAYS.toMillis(1000); + if (SignalStore.misc().isClientDeprecated()) { + return 0; + } + + long buildAge = System.currentTimeMillis() - BuildConfig.BUILD_TIMESTAMP; + long timeUntilBuildDeprecation = BUILD_LIFESPAN - buildAge; + long timeUntilRemoteDeprecation = RemoteDeprecation.getTimeUntilDeprecation(); + + if (timeUntilRemoteDeprecation != -1) { + long timeUntilDeprecation = Math.min(timeUntilBuildDeprecation, timeUntilRemoteDeprecation); + return Math.max(timeUntilDeprecation, 0); + } else { + return Math.max(timeUntilBuildDeprecation, 0); + } } public static T getRandomElement(T[] elements) { @@ -397,12 +415,10 @@ public static int hashCode(@Nullable Object... objects) { else return Uri.parse(uri); } - @TargetApi(VERSION_CODES.KITKAT) public static boolean isLowMemory(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice()) || - activityManager.getLargeMemoryClass() <= 64; + return activityManager.isLowRamDevice() || activityManager.getLargeMemoryClass() <= 64; } public static long getAvailMemory(@NonNull Context context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/VersionTracker.java b/app/src/main/java/org/thoughtcrime/securesms/util/VersionTracker.java deleted file mode 100644 index 099f26d40f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/VersionTracker.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import android.content.Context; -import android.content.pm.PackageManager; - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob; -import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob; -import org.thoughtcrime.securesms.keyvalue.SignalStore; - -import java.util.concurrent.TimeUnit; - -public class VersionTracker { - - private static final String TAG = Log.tag(VersionTracker.class); - - public static int getLastSeenVersion(@NonNull Context context) { - return TextSecurePreferences.getSignalLastVersionCode(context); - } - - public static void updateLastSeenVersion(@NonNull Context context) { - int currentVersionCode = Util.getSignalCanonicalVersionCode(); - int lastVersionCode = TextSecurePreferences.getSignalLastVersionCode(context); - - if (currentVersionCode != lastVersionCode) { - Log.i(TAG, "Upgraded from " + lastVersionCode + " to " + currentVersionCode); - SignalStore.misc().clearClientDeprecated(); - ApplicationDependencies.getJobManager().add(new RemoteConfigRefreshJob()); - RetrieveRemoteAnnouncementsJob.enqueue(true); - LocalMetrics.getInstance().clear(); - } - - TextSecurePreferences.setSignalLastVersionCode(context, currentVersionCode); - } - - public static long getDaysSinceFirstInstalled(Context context) { - try { - long installTimestamp = context.getPackageManager() - .getPackageInfo(context.getPackageName(), 0) - .firstInstallTime; - - return TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - installTimestamp); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, e); - return 0; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/VersionTracker.kt b/app/src/main/java/org/thoughtcrime/securesms/util/VersionTracker.kt new file mode 100644 index 0000000000..d3fad938b2 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/VersionTracker.kt @@ -0,0 +1,48 @@ +package org.thoughtcrime.securesms.util + +import android.content.Context +import android.content.pm.PackageManager +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobs.RefreshAttributesJob +import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob +import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob +import org.thoughtcrime.securesms.keyvalue.SignalStore +import java.time.Duration + +object VersionTracker { + private val TAG = Log.tag(VersionTracker::class.java) + + @JvmStatic + fun getLastSeenVersion(context: Context): Int { + return TextSecurePreferences.getSignalLastVersionCode(context) + } + + @JvmStatic + fun updateLastSeenVersion(context: Context) { + val currentVersionCode = Util.getSignalCanonicalVersionCode() + val lastVersionCode = TextSecurePreferences.getSignalLastVersionCode(context) + + if (currentVersionCode != lastVersionCode) { + Log.i(TAG, "Upgraded from $lastVersionCode to $currentVersionCode") + SignalStore.misc().clearClientDeprecated() + val jobChain = listOf(RemoteConfigRefreshJob(), RefreshAttributesJob()) + ApplicationDependencies.getJobManager().startChain(jobChain).enqueue() + RetrieveRemoteAnnouncementsJob.enqueue(true) + LocalMetrics.getInstance().clear() + } + + TextSecurePreferences.setSignalLastVersionCode(context, currentVersionCode) + } + + @JvmStatic + fun getDaysSinceFirstInstalled(context: Context): Long { + return try { + val installTimestamp = context.packageManager.getPackageInfo(context.packageName, 0).firstInstallTime + Duration.ofMillis(System.currentTimeMillis() - installTimestamp).toDays() + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, e) + 0 + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/MccMncProducer.kt b/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/MccMncProducer.kt new file mode 100644 index 0000000000..f4e484154b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/dualsim/MccMncProducer.kt @@ -0,0 +1,28 @@ +package org.thoughtcrime.securesms.util.dualsim + +import android.content.Context +import android.content.pm.PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS +import android.telephony.TelephonyManager +import androidx.core.content.ContextCompat + +/** + * The mobile country code consists of three decimal digits and the mobile network code consists of two or three decimal digits. + */ +class MccMncProducer(context: Context) { + var mcc: String? = null + private set + var mnc: String? = null + private set + + init { + if (context.packageManager.hasSystemFeature(FEATURE_TELEPHONY_RADIO_ACCESS)) { + val tel = ContextCompat.getSystemService(context, TelephonyManager::class.java) + val networkOperator = tel?.networkOperator + + if (networkOperator?.isNotBlank() == true && networkOperator.length >= 5) { + mcc = networkOperator.substring(0, 3) + mnc = networkOperator.substring(3) + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/views/CircularProgressMaterialButton.kt b/app/src/main/java/org/thoughtcrime/securesms/util/views/CircularProgressMaterialButton.kt index a20287e0d5..006a729dad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/views/CircularProgressMaterialButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/views/CircularProgressMaterialButton.kt @@ -14,6 +14,7 @@ import androidx.core.content.withStyledAttributes import com.google.android.material.button.MaterialButton import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.theme.overlay.MaterialThemeOverlay +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.util.visible import kotlin.math.max @@ -90,7 +91,7 @@ class CircularProgressMaterialButton @JvmOverloads constructor( override fun onRestoreInstanceState(state: Parcelable) { val stateBundle = state as Bundle - val superState: Parcelable? = stateBundle.getParcelable(SUPER_STATE) + val superState: Parcelable? = stateBundle.getParcelableCompat(SUPER_STATE, Parcelable::class.java) super.onRestoreInstanceState(superState) currentState = if (materialButton.visibility == INVISIBLE) State.PROGRESS else State.BUTTON diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java index d62ab30ae6..553eab8c35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java @@ -284,15 +284,14 @@ public void onCreateContextMenu(ContextMenu menu, View view, public boolean onContextItemSelected(MenuItem item) { if (fingerprint == null) return super.onContextItemSelected(item); - switch (item.getItemId()) { - case R.id.menu_copy: - handleCopyToClipboard(fingerprint, codes.length); - return true; - case R.id.menu_compare: - handleCompareWithClipboard(fingerprint); - return true; - default: - return super.onContextItemSelected(item); + if (item.getItemId() == R.id.menu_copy) { + handleCopyToClipboard(fingerprint, codes.length); + return true; + } else if (item.getItemId() == R.id.menu_compare) { + handleCompareWithClipboard(fingerprint); + return true; + } else { + return super.onContextItemSelected(item); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt index eff57957c7..7b38adeff1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyIdentityFragment.kt @@ -7,6 +7,7 @@ import android.widget.Toast import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import org.signal.core.util.ThreadUtil +import org.signal.core.util.getParcelableCompat import org.signal.qr.kitkat.ScanListener import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.WrapperDialogFragment @@ -84,10 +85,10 @@ class VerifyIdentityFragment : Fragment(R.layout.fragment_container), ScanListen } private val recipientId: RecipientId - get() = requireArguments().getParcelable(EXTRA_RECIPIENT)!! + get() = requireArguments().getParcelableCompat(EXTRA_RECIPIENT, RecipientId::class.java)!! private val remoteIdentity: IdentityKeyParcelable - get() = requireArguments().getParcelable(EXTRA_IDENTITY)!! + get() = requireArguments().getParcelableCompat(EXTRA_IDENTITY, IdentityKeyParcelable::class.java)!! private val isVerified: Boolean get() = requireArguments().getBoolean(EXTRA_VERIFIED) diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt index 3ef0023aaf..eb99ac8949 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/video/exo/SimpleExoPlayerPool.kt @@ -82,7 +82,7 @@ class SimpleExoPlayerPool(context: Context) : ExoPlayerPool(MAXIMUM_R * players will be returned first when a player is requested via require. */ abstract class ExoPlayerPool( - private val maximumReservedPlayers: Int, + private val maximumReservedPlayers: Int ) : AppForegroundObserver.Listener { companion object { @@ -130,7 +130,7 @@ abstract class ExoPlayerPool( @MainThread private fun get(allowReserved: Boolean, tag: String): T? { val player = findAvailablePlayer(allowReserved) - return if (player == null && pool.size < getMaximumAllowed(allowReserved)) { + val toReturn = if (player == null && pool.size < getMaximumAllowed(allowReserved)) { val newPlayer = createPlayer() val poolState = createPoolStateForNewEntry(allowReserved, tag) pool[newPlayer] = poolState @@ -142,7 +142,9 @@ abstract class ExoPlayerPool( } else { Log.d(TAG, "Failed to get an ExoPlayer instance for tag: $tag :: ${poolStats()}") null - }?.apply { + } + + return toReturn?.apply { configureForVideoPlayback() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCommand.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCommand.kt index bb828303a1..5584ce6697 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCommand.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/AudioManagerCommand.kt @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.webrtc.audio import android.net.Uri import android.os.Parcel import android.os.Parcelable +import org.signal.core.util.readParcelableCompat +import org.signal.core.util.readSerializableCompat import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.ParcelUtil @@ -36,7 +38,7 @@ sealed class AudioManagerCommand : Parcelable { @JvmField val CREATOR: Parcelable.Creator = ParcelCheat { parcel -> StartIncomingRinger( - ringtoneUri = parcel.readParcelable(Uri::class.java.classLoader)!!, + ringtoneUri = parcel.readParcelableCompat(Uri::class.java)!!, vibrate = ParcelUtil.readBoolean(parcel) ) } @@ -83,7 +85,12 @@ sealed class AudioManagerCommand : Parcelable { companion object { @JvmField - val CREATOR: Parcelable.Creator = ParcelCheat { SetUserDevice(it.readParcelable(RecipientId::class.java.classLoader), it.readSerializable() as SignalAudioManager.AudioDevice) } + val CREATOR: Parcelable.Creator = ParcelCheat { + SetUserDevice( + it.readParcelableCompat(RecipientId::class.java), + it.readSerializableCompat(SignalAudioManager.AudioDevice::class.java)!! + ) + } } } @@ -98,8 +105,8 @@ sealed class AudioManagerCommand : Parcelable { @JvmField val CREATOR: Parcelable.Creator = ParcelCheat { parcel -> SetDefaultDevice( - recipientId = parcel.readParcelable(RecipientId::class.java.classLoader), - device = parcel.readSerializable() as SignalAudioManager.AudioDevice, + recipientId = parcel.readParcelableCompat(RecipientId::class.java), + device = parcel.readSerializableCompat(SignalAudioManager.AudioDevice::class.java)!!, clearUserEarpieceSelection = ParcelUtil.readBoolean(parcel) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/FullSignalAudioManagerApi31.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/FullSignalAudioManagerApi31.kt index 3122d1ba57..67e90a34ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/FullSignalAudioManagerApi31.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/FullSignalAudioManagerApi31.kt @@ -229,7 +229,7 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener else -> AudioDevice.SPEAKER_PHONE } - if (deviceToSet != currentAudioDevice) + if (deviceToSet != currentAudioDevice) { try { val chosenDevice: AudioDeviceInfo = availableCommunicationDevices.first { AudioDeviceMapping.getEquivalentPlatformTypes(deviceToSet).contains(it.type) } val result = androidAudioManager.setCommunicationDevice(chosenDevice) @@ -243,5 +243,6 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener } catch (e: NoSuchElementException) { androidAudioManager.clearCommunicationDevice() } + } } } diff --git a/app/src/main/proto/Backups.proto b/app/src/main/protowire/Backups.proto similarity index 94% rename from app/src/main/proto/Backups.proto rename to app/src/main/protowire/Backups.proto index b96f752fd9..3a83201644 100644 --- a/app/src/main/proto/Backups.proto +++ b/app/src/main/protowire/Backups.proto @@ -8,8 +8,7 @@ syntax = "proto2"; package signal; -option java_package = "org.thoughtcrime.securesms.backup"; -option java_outer_classname = "BackupProtos"; +option java_package = "org.thoughtcrime.securesms.backup.proto"; message SqlStatement { message SqlParameter { diff --git a/app/src/main/res/drawable/ic_megaphone_invite_friends.xml b/app/src/main/res/drawable/ic_megaphone_invite_friends.xml deleted file mode 100644 index d66dc4e88b..0000000000 --- a/app/src/main/res/drawable/ic_megaphone_invite_friends.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_megaphone_start_group.xml b/app/src/main/res/drawable/ic_megaphone_start_group.xml deleted file mode 100644 index 79fb0ad965..0000000000 --- a/app/src/main/res/drawable/ic_megaphone_start_group.xml +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_megaphone_use_sms.xml b/app/src/main/res/drawable/ic_megaphone_use_sms.xml deleted file mode 100644 index 27226ac5b5..0000000000 --- a/app/src/main/res/drawable/ic_megaphone_use_sms.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_signal_add_photo.xml b/app/src/main/res/drawable/ic_signal_add_photo.xml deleted file mode 100644 index 3e799a701d..0000000000 --- a/app/src/main/res/drawable/ic_signal_add_photo.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_signal_appearance.xml b/app/src/main/res/drawable/ic_signal_appearance.xml deleted file mode 100644 index 3cd4118798..0000000000 --- a/app/src/main/res/drawable/ic_signal_appearance.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_mediapreview_v2.xml b/app/src/main/res/layout/activity_mediapreview_v2.xml index fa93ff97d4..ef315d2f67 100644 --- a/app/src/main/res/layout/activity_mediapreview_v2.xml +++ b/app/src/main/res/layout/activity_mediapreview_v2.xml @@ -1,7 +1,21 @@ - \ No newline at end of file + android:layout_height="match_parent"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/alert_view.xml b/app/src/main/res/layout/alert_view.xml index 648f2141d2..02297292c7 100644 --- a/app/src/main/res/layout/alert_view.xml +++ b/app/src/main/res/layout/alert_view.xml @@ -1,7 +1,7 @@ - tools:viewBindingIgnore="true" + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> + app:layout_constraintTop_toBottomOf="@+id/edit_kbs_pin_description" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> diff --git a/app/src/main/res/layout/camera_fragment.xml b/app/src/main/res/layout/camera_fragment.xml index fb1603914e..ec0787af70 100644 --- a/app/src/main/res/layout/camera_fragment.xml +++ b/app/src/main/res/layout/camera_fragment.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + - - + diff --git a/app/src/main/res/layout/conversation_item_received_text_only.xml b/app/src/main/res/layout/conversation_item_received_text_only.xml index 70fa9d874f..16711258c1 100644 --- a/app/src/main/res/layout/conversation_item_received_text_only.xml +++ b/app/src/main/res/layout/conversation_item_received_text_only.xml @@ -30,7 +30,7 @@ android:layout_height="@dimen/conversation_item_reply_size" android:layout_gravity="center" android:padding="9dp" - android:tint="@color/signal_icon_tint_primary" + android:tint="@color/signal_icon_tint_secondary" app:srcCompat="@drawable/symbol_reply_24" /> diff --git a/app/src/main/res/layout/conversation_item_received_thumbnail.xml b/app/src/main/res/layout/conversation_item_received_thumbnail.xml index cced3718d8..2b0701b0ef 100644 --- a/app/src/main/res/layout/conversation_item_received_thumbnail.xml +++ b/app/src/main/res/layout/conversation_item_received_thumbnail.xml @@ -1,21 +1,20 @@ - + tools:visibility="gone" /> diff --git a/app/src/main/res/layout/conversation_item_sent_multimedia.xml b/app/src/main/res/layout/conversation_item_sent_multimedia.xml index c5cba9bb18..8c7765ac28 100644 --- a/app/src/main/res/layout/conversation_item_sent_multimedia.xml +++ b/app/src/main/res/layout/conversation_item_sent_multimedia.xml @@ -29,7 +29,7 @@ android:layout_height="@dimen/conversation_item_reply_size" android:layout_gravity="center" android:padding="9dp" - android:tint="@color/signal_icon_tint_primary" + android:tint="@color/signal_icon_tint_secondary" app:srcCompat="@drawable/symbol_reply_24" /> diff --git a/app/src/main/res/layout/conversation_item_sent_text_only.xml b/app/src/main/res/layout/conversation_item_sent_text_only.xml index 1f5f78e1c8..1a57523995 100644 --- a/app/src/main/res/layout/conversation_item_sent_text_only.xml +++ b/app/src/main/res/layout/conversation_item_sent_text_only.xml @@ -28,7 +28,7 @@ android:layout_width="@dimen/conversation_item_reply_size" android:layout_height="@dimen/conversation_item_reply_size" android:padding="9dp" - android:tint="@color/signal_icon_tint_primary" + android:tint="@color/signal_icon_tint_secondary" android:layout_gravity="center" app:srcCompat="@drawable/symbol_reply_24" /> diff --git a/app/src/main/res/layout/conversation_item_thumbnail.xml b/app/src/main/res/layout/conversation_item_thumbnail.xml index 577302bff0..9d51bee8b1 100644 --- a/app/src/main/res/layout/conversation_item_thumbnail.xml +++ b/app/src/main/res/layout/conversation_item_thumbnail.xml @@ -2,30 +2,19 @@ - + android:layout="@layout/conversation_item_thumbnail_thumbnail_view_stub" /> - + android:layout="@layout/conversation_item_thumbnail_album_thumbnail_view_stub" /> - + android:layout="@layout/conversation_item_thumbnail_footer_view_stub" /> diff --git a/app/src/main/res/layout/conversation_item_thumbnail_album_thumbnail_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_album_thumbnail_view_stub.xml new file mode 100644 index 0000000000..ebf493abc9 --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thumbnail_album_thumbnail_view_stub.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_thumbnail_footer_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_footer_view_stub.xml new file mode 100644 index 0000000000..6ba700929d --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thumbnail_footer_view_stub.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml b/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml new file mode 100644 index 0000000000..39d2c99e77 --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thumbnail_thumbnail_view_stub.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_list_fragment.xml b/app/src/main/res/layout/conversation_list_fragment.xml index d05fba7925..6d3369a919 100644 --- a/app/src/main/res/layout/conversation_list_fragment.xml +++ b/app/src/main/res/layout/conversation_list_fragment.xml @@ -122,6 +122,7 @@ app:backgroundTint="@color/signal_colorSurfaceVariant" app:srcCompat="@drawable/symbol_camera_24" app:tint="@color/signal_colorOnSurface" + app:shapeAppearanceOverlay="@style/Signal.ShapeOverlay.Rounded.Fab" tools:visibility="visible" /> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/copy_button.xml b/app/src/main/res/layout/copy_button.xml index 529ee49feb..41ed1db634 100644 --- a/app/src/main/res/layout/copy_button.xml +++ b/app/src/main/res/layout/copy_button.xml @@ -7,6 +7,8 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/dsl_settings_gutter" android:layout_marginEnd="@dimen/dsl_settings_gutter" + android:ellipsize="end" + android:lines="1" android:minHeight="56dp" android:paddingHorizontal="16dp" android:textAlignment="viewStart" diff --git a/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml b/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml index 7c0bc3d743..0a95d36686 100644 --- a/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml +++ b/app/src/main/res/layout/custom_chat_color_creator_fragment_page.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + - tools:viewBindingIgnore="true" + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> - - + diff --git a/app/src/main/res/layout/fragment_media_preview_v2.xml b/app/src/main/res/layout/fragment_media_preview_v2.xml index 9a926e0a88..196da61edf 100644 --- a/app/src/main/res/layout/fragment_media_preview_v2.xml +++ b/app/src/main/res/layout/fragment_media_preview_v2.xml @@ -66,8 +66,9 @@ diff --git a/app/src/main/res/layout/fragment_registration_captcha.xml b/app/src/main/res/layout/fragment_registration_captcha.xml index 6e4cbf78ea..26cffc8a82 100644 --- a/app/src/main/res/layout/fragment_registration_captcha.xml +++ b/app/src/main/res/layout/fragment_registration_captcha.xml @@ -8,6 +8,7 @@ android:fillViewport="true"> @@ -21,6 +22,7 @@ android:layout_marginEnd="24dp" android:gravity="center" android:text="@string/RegistrationActivity_we_need_to_verify_that_youre_human" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/fragment_registration_enter_code.xml b/app/src/main/res/layout/fragment_registration_enter_code.xml index 972cbe6b74..2e0bf4e387 100644 --- a/app/src/main/res/layout/fragment_registration_enter_code.xml +++ b/app/src/main/res/layout/fragment_registration_enter_code.xml @@ -101,6 +101,20 @@ app:layout_constraintVertical_bias="1.0" tools:text="@string/RegistrationActivity_resend_code" /> + + + @@ -51,8 +52,6 @@ android:maxLines="1" android:padding="0dp" android:singleLine="true" - android:textColor="@color/signal_colorOnSurfaceVariant" - android:textColorHint="@color/signal_colorOnSurfaceVariant" tools:text="+1" /> @@ -63,14 +62,15 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:hint="@string/RegistrationActivity_phone_number_description"> + android:hint="@string/RegistrationActivity_phone_number_description" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + android:inputType="phone"> @@ -101,7 +101,7 @@ android:layout_marginTop="16dp" android:layout_marginEnd="32dp" android:text="@string/RegistrationActivity_you_will_receive_a_verification_code" - android:textColor="@color/core_grey_60" + android:textColor="@color/signal_colorOnSurfaceVariant" app:layout_constraintTop_toBottomOf="@+id/verify_header" tools:layout_editor_absoluteX="0dp" /> @@ -113,13 +113,11 @@ android:layout_marginTop="16dp" android:layout_marginBottom="16dp" android:text="@android:string/cancel" - android:textColor="@color/core_grey_60" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/registerButton" - app:layout_constraintVertical_bias="0" /> + tools:visibility="visible" /> diff --git a/app/src/main/res/layout/fragment_registration_lock.xml b/app/src/main/res/layout/fragment_registration_lock.xml index 94e651fb33..e3be5f4bb7 100644 --- a/app/src/main/res/layout/fragment_registration_lock.xml +++ b/app/src/main/res/layout/fragment_registration_lock.xml @@ -4,8 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:fillViewport="true" - tools:viewBindingIgnore="true"> + android:fillViewport="true"> diff --git a/app/src/main/res/layout/fragment_transfer_restore.xml b/app/src/main/res/layout/fragment_transfer_restore.xml index a3e930640d..5fd2874571 100644 --- a/app/src/main/res/layout/fragment_transfer_restore.xml +++ b/app/src/main/res/layout/fragment_transfer_restore.xml @@ -1,11 +1,11 @@ + android:fillViewport="true" + tools:viewBindingIgnore="true"> + android:textAppearance="@style/Signal.Text.HeadlineMedium" /> + android:textAppearance="@style/Signal.Text.BodyLarge" + android:textColor="@color/signal_colorOnSurfaceVariant" /> + app:tint="@color/signal_colorPrimary" /> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/group_pending_member_invites_fragment.xml b/app/src/main/res/layout/group_pending_member_invites_fragment.xml index 322018957b..e87843f8a2 100644 --- a/app/src/main/res/layout/group_pending_member_invites_fragment.xml +++ b/app/src/main/res/layout/group_pending_member_invites_fragment.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + - - + diff --git a/app/src/main/res/layout/group_requesting_member_fragment.xml b/app/src/main/res/layout/group_requesting_member_fragment.xml index fd86744cf2..c157047623 100644 --- a/app/src/main/res/layout/group_requesting_member_fragment.xml +++ b/app/src/main/res/layout/group_requesting_member_fragment.xml @@ -14,7 +14,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + diff --git a/app/src/main/res/layout/link_preview.xml b/app/src/main/res/layout/link_preview.xml index b82d90d3e7..c4ff4b6cfe 100644 --- a/app/src/main/res/layout/link_preview.xml +++ b/app/src/main/res/layout/link_preview.xml @@ -1,8 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> - + app:layout_constraintTop_toTopOf="@+id/linkpreview_title" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/manage_profile_fragment.xml b/app/src/main/res/layout/manage_profile_fragment.xml index 98773e6d81..6531fb9e6e 100644 --- a/app/src/main/res/layout/manage_profile_fragment.xml +++ b/app/src/main/res/layout/manage_profile_fragment.xml @@ -186,6 +186,8 @@ style="@style/Signal.Text.Preview" android:layout_width="0dp" android:layout_height="wrap_content" + android:ellipsize="end" + android:lines="1" android:text="@string/ManageProfileFragment_your_username" android:textColor="@color/signal_text_secondary" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/media_preview_image_fragment.xml b/app/src/main/res/layout/media_preview_image_fragment.xml index 99aa3d21da..641267e4b8 100644 --- a/app/src/main/res/layout/media_preview_image_fragment.xml +++ b/app/src/main/res/layout/media_preview_image_fragment.xml @@ -1,8 +1,16 @@ - \ No newline at end of file + tools:viewBindingIgnore="true"> + + + + diff --git a/app/src/main/res/layout/media_preview_video_fragment.xml b/app/src/main/res/layout/media_preview_video_fragment.xml index 6e8796adb1..74b33233e6 100644 --- a/app/src/main/res/layout/media_preview_video_fragment.xml +++ b/app/src/main/res/layout/media_preview_video_fragment.xml @@ -1,14 +1,15 @@ + android:orientation="vertical" + tools:viewBindingIgnore="true"> + android:layout_height="match_parent" + android:layout_gravity="center" /> \ No newline at end of file diff --git a/app/src/main/res/layout/message_details_recipient.xml b/app/src/main/res/layout/message_details_recipient.xml index d44fcfbda5..a4e855ac07 100644 --- a/app/src/main/res/layout/message_details_recipient.xml +++ b/app/src/main/res/layout/message_details_recipient.xml @@ -1,14 +1,14 @@ - + app:cardElevation="0dp" + tools:viewBindingIgnore="true"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/message_details_recipient_header.xml b/app/src/main/res/layout/message_details_recipient_header.xml index 9c0234af01..3f5d2c2d25 100644 --- a/app/src/main/res/layout/message_details_recipient_header.xml +++ b/app/src/main/res/layout/message_details_recipient_header.xml @@ -1,14 +1,14 @@ - + app:cardElevation="0dp" + tools:viewBindingIgnore="true"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/numeric_keyboard_view.xml b/app/src/main/res/layout/numeric_keyboard_view.xml index 26f093fe9b..ddbd3a5536 100644 --- a/app/src/main/res/layout/numeric_keyboard_view.xml +++ b/app/src/main/res/layout/numeric_keyboard_view.xml @@ -15,7 +15,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__1" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_4" app:layout_constraintEnd_toStartOf="@id/numeric_keyboard_2" app:layout_constraintStart_toStartOf="parent" @@ -29,7 +29,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__2" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_4" app:layout_constraintEnd_toStartOf="@id/numeric_keyboard_3" app:layout_constraintStart_toEndOf="@id/numeric_keyboard_1" @@ -43,7 +43,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__3" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/numeric_keyboard_2" @@ -57,7 +57,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__4" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_7" app:layout_constraintEnd_toStartOf="@id/numeric_keyboard_5" app:layout_constraintStart_toStartOf="parent" @@ -71,7 +71,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__5" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_7" app:layout_constraintEnd_toStartOf="@id/numeric_keyboard_6" app:layout_constraintStart_toEndOf="@id/numeric_keyboard_4" @@ -85,7 +85,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__6" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_7" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/numeric_keyboard_5" @@ -99,7 +99,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__7" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_0" app:layout_constraintEnd_toStartOf="@id/numeric_keyboard_8" app:layout_constraintStart_toStartOf="parent" @@ -113,7 +113,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__8" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_0" app:layout_constraintEnd_toStartOf="@id/numeric_keyboard_9" app:layout_constraintStart_toEndOf="@id/numeric_keyboard_7" @@ -127,7 +127,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__9" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toTopOf="@id/numeric_keyboard_0" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/numeric_keyboard_8" @@ -141,7 +141,7 @@ android:gravity="center" android:text="@string/NumericKeyboardView__0" android:textAppearance="@style/Signal.Text.TitleLarge" - android:textColor="@color/signal_light_colorOnSurface" + android:textColor="@color/signal_colorOnSurface" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/numeric_keyboard_8" app:layout_constraintStart_toStartOf="@id/numeric_keyboard_8" @@ -159,6 +159,6 @@ app:layout_constraintStart_toStartOf="@id/numeric_keyboard_9" app:layout_constraintTop_toBottomOf="@id/numeric_keyboard_9" app:srcCompat="@drawable/ic_backspace_24" - app:tint="@color/signal_light_colorOnSurface" /> + app:tint="@color/signal_colorOnSurface" /> \ No newline at end of file diff --git a/app/src/main/res/layout/old_device_transfer_locked_dialog_fragment.xml b/app/src/main/res/layout/old_device_transfer_locked_dialog_fragment.xml index 4225e7acda..38eb0751ec 100644 --- a/app/src/main/res/layout/old_device_transfer_locked_dialog_fragment.xml +++ b/app/src/main/res/layout/old_device_transfer_locked_dialog_fragment.xml @@ -1,32 +1,40 @@ - + android:fillViewport="true" + android:scrollbarAlwaysDrawVerticalTrack="true" + tools:viewBindingIgnore="true"> - - - + android:layout_gravity="center_horizontal" + android:orientation="vertical" + android:padding="?attr/dialogPreferredPadding"> - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/onboarding_megaphone.xml b/app/src/main/res/layout/onboarding_megaphone.xml index 20a1793205..30fb87c5ce 100644 --- a/app/src/main/res/layout/onboarding_megaphone.xml +++ b/app/src/main/res/layout/onboarding_megaphone.xml @@ -1,22 +1,21 @@ - + android:paddingBottom="10dp" + tools:parentTag="android.widget.FrameLayout" + tools:viewBindingIgnore="true"> + android:orientation="vertical"> + android:clipToPadding="false" + android:paddingBottom="12dp"> @@ -49,7 +48,10 @@ android:layout_marginTop="10dp" android:clipChildren="false" android:clipToPadding="false" - app:layout_constraintTop_toBottomOf="@id/onboarding_megaphone_title"/> + android:orientation="horizontal" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintTop_toBottomOf="@id/onboarding_megaphone_title" + tools:listitem="@layout/onboarding_megaphone_card" /> diff --git a/app/src/main/res/layout/onboarding_megaphone_card.xml b/app/src/main/res/layout/onboarding_megaphone_card.xml new file mode 100644 index 0000000000..7ec739f218 --- /dev/null +++ b/app/src/main/res/layout/onboarding_megaphone_card.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/onboarding_megaphone_list_item.xml b/app/src/main/res/layout/onboarding_megaphone_list_item.xml deleted file mode 100644 index 969b238bed..0000000000 --- a/app/src/main/res/layout/onboarding_megaphone_list_item.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/pin_restore_entry_fragment.xml b/app/src/main/res/layout/pin_restore_entry_fragment.xml index e4a4c20d5b..7f7f139131 100644 --- a/app/src/main/res/layout/pin_restore_entry_fragment.xml +++ b/app/src/main/res/layout/pin_restore_entry_fragment.xml @@ -4,8 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:fillViewport="true" - tools:viewBindingIgnore="true"> + android:fillViewport="true"> @@ -53,14 +52,18 @@ android:minWidth="210dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/pin_restore_pin_description"> + app:layout_constraintTop_toBottomOf="@+id/pin_restore_pin_description" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + android:inputType="numberPassword" + tools:text="1234567890" /> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/processing_payment_dialog.xml b/app/src/main/res/layout/processing_payment_dialog.xml index b358c306c0..caacf2131d 100644 --- a/app/src/main/res/layout/processing_payment_dialog.xml +++ b/app/src/main/res/layout/processing_payment_dialog.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/profile_create_fragment.xml b/app/src/main/res/layout/profile_create_fragment.xml index 8f25565383..d6790ff7cc 100644 --- a/app/src/main/res/layout/profile_create_fragment.xml +++ b/app/src/main/res/layout/profile_create_fragment.xml @@ -60,7 +60,7 @@ android:layout_marginEnd="@dimen/dsl_settings_gutter" android:layout_marginBottom="16dp" android:text="@string/ProfileCreateFragment__profiles_are_visible_to_contacts_and_people_you_message" - android:textColor="@color/core_grey_60" + android:textColor="@color/signal_colorOnSurfaceVariant" app:layout_constraintTop_toBottomOf="@id/title" /> + android:hint="@string/CreateProfileActivity_first_name_required" + android:theme="@style/Signal.ThemeOverlay.TextInputLayout"> + android:hint="@string/CreateProfileActivity_last_name_optional" + android:theme="@style/Signal.ThemeOverlay.TextInputLayout"> - - xmlns:tools="http://schemas.android.com/tools" - tools:viewBindingIgnore="true" + + - tools:viewBindingIgnore="true" + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> - - + diff --git a/app/src/main/res/layout/schedule_message_time_picker_bottom_sheet.xml b/app/src/main/res/layout/schedule_message_time_picker_bottom_sheet.xml index 930fbd97c6..1bdcc811c7 100644 --- a/app/src/main/res/layout/schedule_message_time_picker_bottom_sheet.xml +++ b/app/src/main/res/layout/schedule_message_time_picker_bottom_sheet.xml @@ -1,9 +1,9 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + app:layout_constraintTop_toTopOf="parent" /> + android:gravity="center" + android:text="@string/ScheduleMessageTimePickerBottomSheet__dialog_title" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/anchor" /> + android:textColor="@color/signal_colorOnSurfaceVariant_60" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/title" + tools:text="All times in (UTC -5:00) Eastern Time (US and Canada)" /> @@ -57,16 +57,16 @@ android:id="@+id/date_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - tools:text="Today" android:drawablePadding="8dp" - android:textAppearance="@style/Signal.Text.BodyLarge" /> + android:textAppearance="@style/Signal.Text.BodyLarge" + tools:text="Today" /> + android:background="?selectableItemBackgroundBorderless" + android:src="@drawable/ic_expand_down_24" /> @@ -74,9 +74,9 @@ android:id="@+id/time_selector" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingHorizontal="6dp" - android:layout_marginEnd="18dp" android:layout_marginTop="16dp" + android:layout_marginEnd="18dp" + android:paddingHorizontal="6dp" android:paddingVertical="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/timezone_disclaimer"> @@ -85,16 +85,16 @@ android:id="@+id/time_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - tools:text="1:00 p.m." android:drawablePadding="8dp" - android:textAppearance="@style/Signal.Text.BodyLarge" /> + android:textAppearance="@style/Signal.Text.BodyLarge" + tools:text="1:00 p.m." /> + android:background="?selectableItemBackgroundBorderless" + android:src="@drawable/ic_expand_down_24" /> @@ -103,11 +103,12 @@ style="@style/Signal.Widget.Button.Medium.Tonal" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="18dp" android:layout_marginTop="30dp" - app:layout_constraintTop_toBottomOf="@id/day_selector" - app:layout_constraintEnd_toEndOf="parent" + android:layout_marginEnd="18dp" android:layout_marginBottom="24dp" - android:text="@string/ScheduleMessageTimePickerBottomSheet__schedule_send"/> + android:text="@string/ScheduleMessageTimePickerBottomSheet__schedule_send" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/day_selector" /> \ No newline at end of file diff --git a/app/src/main/res/layout/signal_map_view.xml b/app/src/main/res/layout/signal_map_view.xml index cebb8bb231..911fb606d7 100644 --- a/app/src/main/res/layout/signal_map_view.xml +++ b/app/src/main/res/layout/signal_map_view.xml @@ -1,7 +1,7 @@ - tools:viewBindingIgnore="true" + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> diff --git a/app/src/main/res/layout/stories_link_popup.xml b/app/src/main/res/layout/stories_link_popup.xml index 5ab58ed1ac..6038696f9e 100644 --- a/app/src/main/res/layout/stories_link_popup.xml +++ b/app/src/main/res/layout/stories_link_popup.xml @@ -21,7 +21,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/bubble" /> - + tools:parentTag="com.google.android.material.card.MaterialCardView"> - - + - - + \ No newline at end of file diff --git a/app/src/main/res/layout/stories_viewer_fragment_page.xml b/app/src/main/res/layout/stories_viewer_fragment_page.xml index 2611a9cd13..f8bf5b3701 100644 --- a/app/src/main/res/layout/stories_viewer_fragment_page.xml +++ b/app/src/main/res/layout/stories_viewer_fragment_page.xml @@ -23,7 +23,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0"> - - + - tools:viewBindingIgnore="true" + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> + + android:scaleType="centerCrop" /> + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/verification_code_view.xml b/app/src/main/res/layout/verification_code_view.xml index fd71e865f7..97298c516b 100644 --- a/app/src/main/res/layout/verification_code_view.xml +++ b/app/src/main/res/layout/verification_code_view.xml @@ -19,11 +19,12 @@ app:layout_constraintEnd_toStartOf="@+id/container_one" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toTopOf="parent" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + app:layout_constraintTop_toTopOf="parent" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + app:layout_constraintTop_toTopOf="parent" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + app:layout_constraintTop_toTopOf="parent" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + app:layout_constraintTop_toTopOf="parent" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> + app:layout_constraintTop_toTopOf="parent" + app:materialThemeOverlay="@style/Signal.ThemeOverlay.TextInputLayout"> - tools:viewBindingIgnore="true" + xmlns:tools="http://schemas.android.com/tools" + tools:viewBindingIgnore="true"> - - + diff --git a/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml b/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml index ac9c34fece..dfda467441 100644 --- a/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml +++ b/app/src/main/res/layout/webrtc_call_participant_recycler_item.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/webrtc_call_view.xml b/app/src/main/res/layout/webrtc_call_view.xml index 565a47fe2e..37f636e272 100644 --- a/app/src/main/res/layout/webrtc_call_view.xml +++ b/app/src/main/res/layout/webrtc_call_view.xml @@ -176,7 +176,7 @@ app:layout_constraintTop_toTopOf="parent" tools:visibility="gone"> - - + \ No newline at end of file diff --git a/app/src/main/res/navigation/app_settings.xml b/app/src/main/res/navigation/app_settings.xml index c13f94eed9..a196a28a74 100644 --- a/app/src/main/res/navigation/app_settings.xml +++ b/app/src/main/res/navigation/app_settings.xml @@ -432,6 +432,16 @@ app:popUpTo="@id/app_settings" app:popUpToInclusive="true" /> + + diff --git a/app/src/main/res/navigation/app_settings_change_number.xml b/app/src/main/res/navigation/app_settings_change_number.xml index 8b71578221..cf70228145 100644 --- a/app/src/main/res/navigation/app_settings_change_number.xml +++ b/app/src/main/res/navigation/app_settings_change_number.xml @@ -73,6 +73,10 @@ android:name="org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberVerifyFragment" tools:layout="@layout/fragment_change_phone_number_verify"> + + + @@ -48,6 +55,11 @@ android:name="org.thoughtcrime.securesms.components.settings.app.privacy.advanced.AdvancedPrivacySettingsFragment" android:label="advanced_privacy_settings_fragment" /> + + diff --git a/app/src/main/res/navigation/registration.xml b/app/src/main/res/navigation/registration.xml index f1238ee584..a154ce5500 100644 --- a/app/src/main/res/navigation/registration.xml +++ b/app/src/main/res/navigation/registration.xml @@ -110,13 +110,23 @@ app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> + + + tools:layout="@layout/fragment_registration_country_picker" /> + + + + + + + + + tools:layout="@layout/fragment_registration_captcha" /> https://support.signal.org/hc/articles/360007321171 --> + Ja Nee @@ -147,7 +148,7 @@ Julle sal in staat wees om boodskappe en oproepe uit te ruil, en jou naam en foto sal met hulle gedeel word. Julle sal boodskappe vir mekaar kan stuur. - Versperde mense kan nie bel of vir jou boodskappe stuur nie. + Versperde mense kan jou nie bel of vir jou boodskappe stuur nie. Versperde mense sal nie vir jou boodskappe kan stuur nie. Versper ontvangs van Signal-bywerkings en -nuus. @@ -480,7 +481,7 @@ %1$d gesprekke na inkassie geskuif - Lees + Gelees Gelees @@ -493,17 +494,17 @@ Verwyder speld - Verwyder speld + Verwyder spelde - Skakel klank af + Demp Demp - Skakel klank aan + Ontdemp Ontdemp - Kies + Selekteer Argiveer Argiveer @@ -542,6 +543,15 @@ +%1$d + + Herkoppel jou toestelle + + Die toestelle wat jy bygevoeg het, is ontkoppel toe jou toestel gederegistreer is. Gaan na Instellings om enige toestelle te herkoppel. + + Maak instellings oop + + Later + Selekteer lede @@ -953,6 +963,16 @@ Gebruikersnaam geskep Gebruikersnaam gekopieer + + Kon nie gebruikersnaam skrap nie. Probeer later weer. + + Gebruikersnaam geskrap + + + + Iets het verkeerd geloop met jou gebruikernaam, dit is nie meer aan jou rekening toegewys nie. Jy kan probeer om dit weer op te stel of \'n nuwe een kies. + + Stel nou reg @@ -1156,8 +1176,8 @@ Nuwe groep Nooi vriende uit Gebruik SMS - Voorkoms - Voeg foto toe + Kletskleure + Voeg ’n profielfoto by Antwoorde @@ -1584,6 +1604,17 @@ Skep nuwe PIN + + Stuur SMS-kode + + Signal-registrasie - Hulp nodig met registreer van PIN vir Android + + Jou PIN is \'n %1$d+-syferkode wat jy geskep het wat numeries of alfanumeries kan wees.\n\nAs jy nie jou PIN kan onthou nie, kan jy \'n nuwe een skep. + + As jy nie jou PIN kan onthou nie, kan jy \'n nuwe een skep. + + Jy het jou PIN-raaiskote opgebruik, maar jy kan steeds toegang tot jou Signal-rekening verkry deur \'n nuwe PIN te skep. + Waarskuwing Indien jy die PIN deaktiveer, sal jy alle data verloor wanneer jy Signal herregistreer, tensy jy handmatig \'n rugsteun maak en dit herwin. Jy kan nie Registrasiesluiting aanskakel terwyl die PIN gedeaktiveer is nie. @@ -1731,7 +1762,7 @@ Julle sal nie mekaar se oudio of video ontvang nie. Kan nie oudio & video van %1$s ontvang nie Kan nie oudio en video van %1$s ontvang nie - Dit kan wees omdat hulle die verandering in jou veiligheidsnommer nog nie geverifieer het nie, hulle toestel gee probleme, of hulle het jou versper. + Dit kan wees omdat hulle die verandering in jou veiligheidsnommer nog nie geverifieer het nie, of hulle toestel gee probleme, of hulle het jou versper. Vee om skermdeling te bekyk @@ -1768,11 +1799,18 @@ Signal benodig kontakte- en mediatoestemmings om jou te help om met vriende te verbind en boodskappe te stuur. Jou kontakte word opgelaai deur middel van Signal se privaatkontak-ontdekking, wat beteken dat hulle end-tot-end geënkripteer is en nooit vir die Signal-diens sigbaar is nie. Signal benodig kontakte- en mediatoestemmings om jou te help om met vriende te verbind. Jou kontakte word opgelaai deur middel van Signal se privaatkontak-ontdekking, wat beteken dat hulle end-tot-end geënkripteer is en nooit vir die Signal-diens sigbaar is nie. Jy het te veel keer probeer om hierdie nommer te registreer. Probeer gerus later weer. + + Jy het te veel keer probeer om hierdie nommer te registreer. Probeer asseblief weer oor %1$s. Kan nie aan diens koppel nie. Kontroleer netwerkverbinding en probeer weer. Niestandaard-nommerformaat Die nommer wat jy ingesleutel het (%1$s), lyk of dit in \'n niestandaard-formaat is. \n\nHet jy %2$s bedoel? Molly Android - Foonnommerformaat + Oproep versoek + + SMS aangevra + + Verifiëringskode aangevra Jy moet nog net %1$d stap doen voor jy ’n ontfoutlog indien. Jy moet nog net %1$d stappe doen voor jy ’n ontfoutlog indien. @@ -1792,6 +1830,16 @@ Bel Bevestigingskode Stuur kode weer + + Probleme met registrasie? + + • Maak seker dat jou foon \'n selsein het om jou SMS of oproep te ontvang\n • Bevestig dat jy \'n foonoproep na die nommer kan ontvang\n • Kontroleer dat jy jou foonnommer korrek ingesleutel het. + + Vir meer inligting, volg asseblief hierdie foutopsporingstappe of kontak Steundiens + + hierdie foutopsporingstappe + + Kontak Steundiens Skakel registrasieslot aan? @@ -1951,13 +1999,17 @@ Jy het \'n wapen gebruik - %1$s Het op jou storie gereageer + %1$s het op jou storie gereageer - %1$s Het op hulle storie gereageer + %1$s het op hulle storie gereageer Betaling Geskeduleerde boodskap + + Jou boodskapgeskiedenis is saamgevoeg + + %1$s behoort aan %2$s Molly-bywerking @@ -2088,13 +2140,15 @@ %1$s %2$s Kontak Het %1$s gereageer op: \"%2$s\". - Het %1$s gereageer op jou video + Het %1$s gereageer op jou video. Het %1$s gereageer op jou foto. - Het %1$s gereageer op jou GIF + Het %1$s gereageer op jou GIF. Het %1$s gereageer op jou lêer. Het %1$s gereageer op jou oudio. Het %1$s op jou eenkeerkyk-media gereageer. - Het %1$s op jou plakker gereageer + + Het %1$s op jou betaling gereageer. + Het %1$s op jou plakker gereageer. Hierdie boodskap is geskrap. Skakel kennisgewings af dat kontakte by Signal aangesluit het? Jy kan dit weer aanskakel in Signal > Instellings > Kennisgewings. @@ -2675,9 +2729,9 @@ Gebruik adresboekfoto\'s Vertoon foto\'s van jou adresboek indien beskikbaar - Keep Muted Chats Archived + Hou gedempte kletse geargiveer - Muted chats that are archived will remain archived when a new message arrives. + Gedempte kletse wat geargiveer is, sal geargiveer bly wanneer \'n nuwe boodskap aankom. Genereer skakelvoorskoue Haal skakelvoorskoue direk van die webwerwe af vir boodskappe wat jy stuur. Verander wagwoordfrase @@ -3295,6 +3349,8 @@ Voer jou PIN in Voer die PIN in wat jy vir jou rekening geskep het. Dit is anders as jou SMS-verifikasiekode. + + Sleutel die PIN in wat jy vir jou rekening geskep het. Voer alfanumeriese PIN in Voer numeriese PIN in PIN verkeerd. Probeer weer. @@ -3398,7 +3454,10 @@ Jou rugsteun bevat \'n baie groot lêer wat nie gerugsteun kan word nie. Skrap dit asb. en skep \'n nuwe rugsteun. Tik om rugsteunkopieë te bestuur. Verkeerde nommer? + Bel my (%1$02d:%2$02d) + + Stuur weer Kode (%1$02d:%2$02d) Kontak Signal-steundiens Signal-registrasie - verifikasiekode vir Android Verkeerde kode @@ -3406,6 +3465,18 @@ Onbekend Sien my telefoonnommer Vind my volgens my foonnommer + + Telefoonnommer + + Kies wie jou telefoonnommer kan sien en wie jou daarmee op Molly kan kontak. + + Wie my nommer kan sien + + Niemand sal jou telefoonnommer in Molly kan sien nie + + Wie my volgens my nommer kan vind + + Jou telefoonnommer sal sigbaar wees vir mense en groepe aan wie jy boodskappe stuur. Mense wat jou nommer in hulle telefoonkontakte het, sal dit ook in Molly kan sien. Enigiemand My kontakte Niemand @@ -3615,7 +3686,7 @@ %1$s/%2$s - “%1$s” is versper + “%1$s” is versper. Kon nie “%1$s” versper nie “%1$s” is ontsper. @@ -3670,14 +3741,14 @@ Die skrap van jou rekening sal: Voer jou foonnommer in Skrap rekening - Skrap jou rekeninginligting en profielprent - Skrap al jou boodskappe + Jou rekeninginligting en profielfoto skrap + Al jou boodskappe skrap Skrap %1$s in jou betalingsrekening Geen landkode aangedui nie Geen nommer aangedui nie Die foonnommer wat jy ingevoer het stem nie met dié van jou rekening ooreen nie. Is jy seker jy wil jou rekening skrap? - Dit sal jou Signal-rekening skrap en die toepassing terugstel. Die toep sal toemaak nadat die proses voltooi is. + Dit sal jou Signal-rekening skrap en die toepassing terugstel. Die toepassing sal toemaak nadat die proses voltooi is. Kon nie lokale data skrap nie. Jy kan dit handmatig in die stelsel-toepassingsinstellings skrap. Lanseer toepassingsinstellings @@ -3784,12 +3855,12 @@ Deaktiveer beursie Jou balans - Ons beveel aan dat jy jou fondse na ’n ander beursie-adres oordrag voor jy betalings deaktiveer. Indien jy kies om nie nou jou fondse oor te dra nie, sal hulle in die beursie wat aan Molly gekoppel is, bly as jy betalings heraktiveer. + Ons beveel aan dat jy jou fondse na ’n ander beursie-adres oordra voor jy betalings deaktiveer. Indien jy kies om nie nou jou fondse oor te dra nie, sal dit in jou beursie bly wat aan Molly gekoppel is indien jy betalings heraktiveer. Dra oorblywende balans oor Deaktiveer sonder oordrag Deaktiveer Deaktiveer sonder oordrag? - Jou balans sal in jou Molly-gekoppelde beursie bly indien jy kies om betalings te heraktiveer. + Jou saldo sal in jou beursie bly wat aan Molly gekoppel is indien jy kies om betalings te heraktiveer. Fout met beursie-deaktivering. @@ -4008,7 +4079,7 @@ Boodskappe Verdwynboodskappe Toepassingveiligheid - Versper skermgrepe in die onlangs-lys en in die toepassing + Versper skermgrepe in die onlangs-lys en binne-in die toepassing Signal-boodskappe en oproepe, herlei oproepe altyd, en verseëlde afsender Verstek-tydhouer vir nuwe kletse Stel ’n verstek-verdwynboodskap-tydhouer in vir alle kletse wat jy begin. @@ -4106,7 +4177,7 @@ Nie nou nie - Pas reaksies aan + Pasmaking van reaksies Tik om ’n emoji te vervang Stel terug Stoor @@ -4508,7 +4579,7 @@ Jou geskenk kon nie gestuur word nie as gevolg van \'n netwerkfout. Kontroleer jou verbinding en probeer weer. - Skenking aan %1$s + Skenking namens %1$s %1$s het namens jou aan Signal geskenk @@ -4907,7 +4978,7 @@ Antwoorde & reaksies - Laat antwoorde &amp toe; reaksies + Laat antwoorde & reaksies toe Laat toe dat mense wat na jou storie kan kyk, daarop reageer en antwoord @@ -5601,5 +5672,15 @@ Skrap gebruikersnaam + + + h + + m + + Stel + + Minimum tyd voordat skermslot in werking tree, is 1 minuut. + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4d089f2bca..98809b4b7b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -14,6 +14,7 @@ + نعم لا @@ -97,10 +98,10 @@ المستخدمون المحظورون - أضف مستخدما محظورا + أضف مستخدمًا محظورًا لن يتمكن المستخدم المحظور من الاتصال بك ولا إرسال الرسائل إليك. لا أحد محظور - حظر المستخدم ؟ + حظر المستخدم؟ لن يتمكن \"%1$s\" من الاتصال بك ولا إرسال الرسائل إليك. حظر @@ -153,10 +154,10 @@ حظر الحصول على الجديد والتحديثات من Signal. استئناف الحصول على الجديد والتحديثات من Signal. - رفع الحظر عن %1$s ؟ + رفع الحظر عن %1$s؟ حظر حظر ثم المغادرة - إبلاغ عن بريد عشوائي ثم حظره + إبلاغ عن بريد مزعج ثم حظره اليوم @@ -471,7 +472,7 @@ %1$s مشغّل - حظر الطلب ؟ + حظر الطلب؟ لن يتمكن المستخدم %1$s من الانضمام إلى هذه المجموعة أو حتى إرسال طلب للانضمام إليها من خلال رابط المجموعة. لكن يمكن إضافته يدويًا للمجموعة. @@ -520,28 +521,28 @@ تم نقل %1$d رسالة إلى الوارد - اقرأ - اقرأ - اقرأ - اقرأ - اقرأ - اقرأ + مقروءة + مقروء + مقروءة + مقروءة + مقروءة + مقروءة - غير مقروء + غير مقروءة غير مقروء - غير مقروءه - غير مقروءه - غير مقروء - غير مقروء + غير مقروءة + غير مقروءة + غير مقروءة + غير مقروءة تثبيت تثبيت تثبيت - ثبت + تثبيت تثبيت - ثبت + تثبيت إلغاء التثبيت @@ -552,37 +553,37 @@ إلغاء التثبيت - كتم الصوت - كتم الصوت - كتم الصوت - كتم الصوت - كتم الصوت - كتم الصوت + كتم الدردشات + كتم الدردشة + كتم الدردشات + كتم الدردشات + كتم الدردشات + كتم الدردشات - إعادة الصوت - إعادة الصوت - إعادة الصوت - إعادة الصوت - إعادة الصوت - إعادة الصوت + إلغاء كتم الدردشات + إلغاء كتم الدردشة + إلغاء كتم الدردشات + إلغاء كتم الدردشات + إلغاء كتم الدردشات + إلغاء كتم الدردشات - حَدِّد + تحديد - أرشيف - أرشيف - أرشيف - أرشيف - أرشيف - أرشيف + أرشفة + أرشفة + أرشفة + أرشفة + أرشفة + أرشفة - أخرج من الأرشيف - أخرج من الأرشيف - أخرج من الأرشيف - أخرج من الأرشيف - أخرج من الأرشيف - أخرج من الأرشيف + إخراج من الأرشيف + إخراج من الأرشيف + إخراج من الأرشيف + إخراج من الأرشيف + إخراج من الأرشيف + إخراج من الأرشيف حذف @@ -592,7 +593,7 @@ حذف حذف - اختيار الجميع + تحديد الكل تم تحديد %1$d تم تحديد %1$d @@ -622,6 +623,15 @@ +%1$d + + أعد ربط أجهزتك + + تم إلغاء ربط الأجهزة التي أضفتها عند إلغاء تسجيل جهازك. انتقل إلى الإعدادات لتعيد ربط أي أجهزة. + + فتح الإعدادات + + لاحقًا + حدِّد الأعضاء @@ -1087,7 +1097,7 @@ نبّهني عندما يذكر أحدهم اسمي - تلقي الإشعارات عندما يُذكَر اسمك في المحادثات الصامتة ؟ + تلقِّي الإشعارات عندما يُذكَر اسمك في المحادثات الصامتة؟ نبّهني دوماً لا تنبّهني @@ -1105,6 +1115,16 @@ تم إنشاء اسم المستخدم تم نسخ اسم المستخدم + + تعذّر حذف اسم المستخدم. يُرجى المحاولة مرة أخرى لاحقًا. + + تم حذف اسم المستخدم + + + + حدثت مشكلة مع اسم المستخدم الخاص بك ولم يعد مخصصًا لحسابك. يمكنك محاولة تعيينه من جديد أو اختيار اسم مستخدم جديد. + + إصلاح الآن @@ -1348,8 +1368,8 @@ مجموعة جديدة دعوة الأصدقاء استخدام رسالة قصيرة - المظهر - إضافة صورة + لون الدردشة + إضافة صورة للحساب الشخصي الإجابات @@ -1704,10 +1724,10 @@ رفع الحظر هل تود السماح لـ%1$s بمراسلتك، بالإضافة إلى مشاركة اسمك وصورتك معه؟ لن يعرف أنك قرأت رسالته إلى أن تسمح له بذلك. - هل تود السماح لـ %1$s بمراسلتك ،بالإضافة إلى مشاركة اسمك وصورتك معه ؟ لن تصله أي رسالة حتى تزيل الحظر عنه. + هل تود السماح لـ %1$s بمراسلتك ومشاركة اسمك وصورتك معه؟ لن تصله أي رسالة حتى تزيل الحظر عنه. هل تود السماح لـ %1$s بإرسال رسالة لك؟ لن تتلقى أي رسالة منه حتى تُلغي حظرك له. - الحصول على الجديد والتحديثات من %1$s ؟ لن تصلك أي تحديثات حتى يُرفَع حظرك عنها. + الحصول على الجديد والتحديثات من %1$s؟ لن تصلك أي تحديثات حتى يُرفَع حظرك عنها. أأنت بحاجة إلى متابعة محادثتك مع هذه المجموعة ومشاركة اسمك وصورتك مع أعضائها ؟ يجب ترقية هذه المجموعة لتفعيل ميزات جديدة مثل ‎@mention والمشرفين عليها. ستتم دعوة اﻷعضاء الذين لم يشاركوا أسمائهم أو صورهم في هذه المجموعة للانضمام إليها. لا يمكن استخدام هذه المجموعة القديمة الطراز لأنها كبيرة جدا. الحد الأقصى للمجموعة هو %1$d. @@ -1715,7 +1735,7 @@ الانضمام إلى هذه المجموعة ومشاركة اسمك وصورتك مع أعضائها؟ لن يعرفوا أنك قرأت رسالتهم حتى توافق. هل تود الانضمام إلى هذه المجموعة ومشاركة اسمك وصورتك مع أعضائها؟ لن تظهر لك رسائلهم حتى تسمح بذلك. هل تود الانضمام إلى هذه المجموعة؟ لن يعرفوا أنك قرأت رسائلهم إلا إذا وافقت. - إزالة الحظر عن هذه المجموعة ومشاركة اسمك وصورتك مع أعضائها؟ لن تتلقى أية رسائل حتى تزيل الحظر. + إزالة الحظر عن هذه المجموعة ومشاركة اسمك وصورتك مع أعضائها؟ لن تتلقى أية رسائل حتى تُزيل الحظر. إظهار عضو في %1$s @@ -1840,9 +1860,20 @@ إنشاء رقم تعريف شخصي جديد + + أرسل رسالة قصيرة + + التسجيل في Signal - بحاجة إلى مساعدة مع الرقم التعريفي الشخصي في أندرويد + + إنَّ الـPIN الخاص بك عِبارة عَن رمز مكون من %1$d+ خانات أو أكثر والتي يُمكن أن تكون إما أرقامًا أو حُروفًا.\n\nإذا لم تستطع تَذكُر الـPIN الخاص بك، يمكنك إنشاء رقم جديد. + + إذا لم تستطع تَذكُر الـPIN الخاص بك، يمكنك إنشاء رقم جديد. + + لم تعد تتوفر على تخمينات الرقم التعريفي الشخصي، لكن يمكنك الوصول إلى حساب Signal الخاص بك عبر إنشاء رقم جديد. + تحذير - إذا قمت بتعطيل الرمز التعريفي الشخصي، ستضيع جميع بياناتك عند قبامك بإعادة التسجيل إلى Signal إلا إذا قمت باسترجاعها بواسطة النسخ الاحتياطي اليدوي. لا يمكنك تفعيل قفل التسجيل أثناء تعطيل الرمز التعريفي الشخصي. + إذا قمت بتعطيل الرمز التعريفي الشخصي، ستضيع جميع بياناتك عند قيامك بإعادة التسجيل إلى Signal إلا إذا قمت باسترجاعها بواسطة النسخ الاحتياطي اليدوي. لا يمكنك تفعيل قفل التسجيل أثناء تعطيل الرمز التعريفي الشخصي. إلغاء الرقم التعريفي الشخصي @@ -1987,7 +2018,7 @@ الكاميرا - إعادة الصوت + إلغاء كتم الدردشات كتم @@ -2011,7 +2042,7 @@ كلاكما لن يتلقى صوت الآخر ولا صورته. لا يمكن تلقي الصوت والصورة من %1$s لا يمكن تلقي الصوت والصورة من %1$s - ربما يكون ذلك بسبب عدم تحقق الشخص من تغيير رقم أمانك، أو بسبب وجود مشكلة في جهازه، أو قيامه بحظرك. + ربما يكون ذلك بسبب عدم تحقّق الشخص من تغيير رقم أمانك أو بسبب وجود مشكلة في جهازه أو قيامه بحظرك. سحب لعرض الشاشة المُشارَكة @@ -2048,11 +2079,18 @@ يحتاج Signal إلى أذونات جهات الاتصال والوسائط للمساعدة في التواصل مع أصدقائك وإرسال الرسائل. يتم تحميل جهات اتصالك باستخدام نظام اكتشاف جهة الاتصال الخاص في Signal، مما يعني أنها مشفرة من الطرفين وغير مرئية على الإطلاق لخدمة Signal. يحتاج Signal إلى أذونات جهات الاتصال للمساعدة في التواصل مع أصدقائك. يتم تحميل جهات اتصالك باستخدام نظام اكتشاف جهة الاتصال الخاص في Signal، مما يعني أنها مشفرة من الطرفين وغير مرئية على الإطلاق لخدمة Signal. لقد قمت بمحاولات كثيرة للتسجيل بهذا الرقم. الرجاء المحاولة لاحقاً. + + لقد قمت بمحاولات كثيرة للتسجيل بهذا الرقم. يُرجى المحاولة لاحقاً %1$s. لا يمكن الاتصال بالخدمة. الرجاء التأكد من الإتصال بالشبكة والمحاولة مرة أخرى. نسق العدد غير المعياري ‫يظهر أن العدد الذي قمت بإدخاله (%1$s) ليس بالنسق المعياري.\n\nأ قصدك هو %2$s ؟ Molly Android - نسق رقم الهاتف + طُلبَت مكالمة + + تم طلب رسالة قصيرة + + تم طلب رقم التحقق أنت الآن على بعد %1$d خطوة من إرسال سجل التصحيح. أنت الآن على بعد %1$d خطوة من إرسال سجل التصحيح. @@ -2076,6 +2114,16 @@ اتصال رمز التحقق أعِد إرسال الرمز + + هل تواجه مشكلة في التسجيل؟ + + • تأكد من أن هاتفك يتوفر على إشارة خلوية للتوصل بالرسالة القصيرة أو الاتصال\n • قم بتأكيد إمكانية استقبالك اتصال على الرقم\n • تحقق من أنك أدخلت رقم هاتفك بشكل صحيح. + + لِمزيد من المعلومات، يُرجى اتباع خطوات المُساعفة هذه أو الاتصال بالدعم + + خطوات المُساعفة هذه + + الاتصال بالدعم هل تريد تشغيل قفل التسجيل ؟ @@ -2242,6 +2290,10 @@ عمليات الدفع رسالة مُجدولة + + تم دمج سِجل رسائلك + + يعود الرقم %1$s إلى %2$s تحديث Molly @@ -2376,13 +2428,15 @@ %1$s%2$s جهة اتصال تفاعل %1$s مع: \"%2$s\". - تفاعل %1$s مع الفيديو الخاص بك. - تفاعل %1$s مع الصورة الخاصة بك. - تفاعل %1$s على صورة GIF الخاصة بك. - تفاعل %1$s مع الملف الخاص بك. - تفاعل %1$s مع التسجيل الصوتي الخاص بك. - تفاعل %1$s مع وسائطك التي تُعرَض مرة واحدة - تم تفاعل %1$s مع الملصق الخاص بك. + تفاعل %1$s مع فيديوهاتك. + تفاعل %1$s مع صورتك. + تفاعل %1$s مع صورتك الـ GIF. + تفاعل %1$s مع ملفك الخاص. + تفاعل %1$s مع تسجيلك الصوتي. + تفاعل %1$s مع وسائطك التي تُعرَض مرة واحدة. + + تفاعل %1$s مع عملية الدفع الخاصة بك. + تم تفاعل %1$s مع ملصقك الخاص. تم حذف هذه الرسالة. تعطيل إشعارات انضمام شخص إلى Signal ؟ تمكنك تفعيلها مجددا في Signal > الإعدادات > الإشعارات. @@ -2819,8 +2873,8 @@ مشكل في التوصيل - تعذر توصيل رسالة – نصية، ملصقة، رد فعل، وصل القراءة، أو وسيط – من %1$s. يُحتمَل أنها أُرسلَت إليك مباشرة أو أنها قد أُرسلَت في مجموعة. - لم يتمكن %1$s من توصيل رسالة أو ملصقا أو رد فعل أو وصل القراءة إليك. + لم يتمكن %1$s من توصيل رسالة نصية أو ملصقة أو رد فعل أو وصل القراءة إليك. يُحتمَل أنها أُرسلَت إليك مباشرة أو أنها قد أُرسلَت في مجموعة. + لم يتمكن %1$s من توصيل رسالة أو ملصق أو رد فعل أو وصل القراءة إليك. الاسم الأول (مطلوب) @@ -2968,7 +3022,7 @@ تخصيص كتم لمدة ساعة - اصمت لـ 8 ساعات + كتم لمدة 8 ساعات كتم لمدة يوم كتم لمدة سبعة أيام دائمًا @@ -3468,7 +3522,7 @@ - إلغاء الكتم. + إلغاء كتم الدردشات كتم الإشعارات @@ -3647,6 +3701,8 @@ أدخل الرقم التعريفي الشخصي عليك بإدخال الرقم التعريفي الشخصي الذي قمت بإنشائه لحسابك. إنه مختلف عن رمز التحقق الذي وصلك عبر رسالة قصيرة. + + أدخل رقم التعريف الشخصي الذي أنشأته لحسابك. أدخل الرقم التعريفي الشخصي بالأحرف والأرقام أدخل الرقم التعريفي الشخصي بالأرقام الرقم التعريفي الشخصي غير صحيح. يرجى إعادة المحاولة. @@ -3770,7 +3826,10 @@ تحتوي نسختك الاحتياطية على ملف كبير جدًا لا يُمكن نسخه احتياطيًا. يُرجى حذفه وإنشاء نسخة احتياطية جديدة. يُرجى اللمس لإدارة النسخ الاحتياطية. رقم غير صحيح؟ + اتصل بي (%1$02d:%2$02d) + + إعادة إرسال الرمز (%1$02d:%2$02d) الاتصال بخدمة دعم Signal تسجيل Signal - رمز التّحقق لِ Android رمز غير صحيح @@ -3778,6 +3837,18 @@ مجهول رؤية رقم هاتفي اعثروا عليّ برقم الهاتف + + رقم الهاتف + + اختر من يُمكنه رؤية رقم هاتفك ومن يمكنه الاتصال بك على Molly باستخدامه. + + من يُمكنه رؤية رقمي + + لن يرى أي أحد رقم هاتفك في Molly. + + مَن يُمكنه العثور عليَّ باستخدام رقمي + + سَيكون رقم هاتفك مرئيًا للأشخاص والمجموعات التي تراسلها. كما أن الأشخاص الذين لديهم رقمك في جهات اتصال هواتفهم سيرونه أيضًا على Molly. الكل جهات اتصالي لا أحد @@ -4047,7 +4118,7 @@ شبكة Wi-Fi ضعيفة. تم التبديل إلى شبكة خلوية. - سيؤدي حذف حسابك إلى : + سيؤدي حذف حسابك إلى: يُرجى إدخال رقم هاتفك حذف الحساب حذف معلومات حسابك وصورة حسابك الشخصي @@ -4168,12 +4239,12 @@ تعطيل المحفظة رصيدك - يٌوصَى بإرسال مبالغك إلى عنوان محفظة أخرى قبل تعطيل الدفوعات. إذا اخترت عدم إرسال مبالغك الآن، سوف تبقى في محفظتك مرتبطة بـ Molly إذا أعدت تفعيل الدفوعات. + يُوصَى بإرسال رصيدك المالي إلى عنوان محفظة أخرى قبل تعطيل عمليات الدفع. إذا اخترت عدم إرسال رصيدك المالي الآن، ستبقى في محفظتك مرتبطة بـ Molly إذا أعدت تفعيل الدفوعات. إرسال الرصيد المتبقي تعطيل بدون أي إرسال تعطيل تعطيل بدون أي إرسال ؟ - سوف يبقى رصيدك في محفظتك المرتبطة بـ Molly إذا اخترت إعادة تفعيل الدفوعات. + سيبقى رصيدك في محفظتك المرتبطة بـ Molly إذا اخترت إعادة تفعيل عمليات الدفع. حدث خطأ خلال تعطيل المحفظة. @@ -4396,7 +4467,7 @@ التراسُل الرسائل المختفية أمان التطبيق - منع لقطات الشاشة داخل التطبيق وفي قائمة الأحدث + منع لقطات الشاشة داخل التطبيق وفي قائمة الأحدَث محادثات ومكالمات Signal، مناوبة الاتصالات دوما، والمُرسِل المختوم المهلة الافتراضية للمحادثات الجديدة تحديد مدة افتراضية لمؤقت اختفاء الرسائل، لجميع المحادثات الجديدة التي قمت ببدئها. @@ -4563,7 +4634,7 @@ كتم - صامت + تم كتم الدردشات البحث الرسائل المختفية @@ -4582,7 +4653,7 @@ الطلبات و الدعوات وصلة المجموعة الإضافة كجهة اتصال - شغِّل الصوت + إلغاء كتم الدردشات كُتمَت المحادثة حتى %1$s كُتمَت المحادثة للأبد لقد نُسخ رقم الهاتف إلى الحافظة. @@ -4920,7 +4991,7 @@ تعذّر إرسال هديتك بسبب خطأ في الشبكة. تحقّق من اتصالك بالشبكة ثم حاول مجددًا. - تقديم تبرّع لـ %1$s + تبرّع نيابة عن %1$s تبرّع %1$s لـ Signal نيابة عنك @@ -5499,9 +5570,9 @@ لقد تفاعلت مع قصة %1$s - تفاعل مع قصتك + تفاعَلَ مع قصتك - تفاعل مع قصة + تفاعَلَ مع قصة @@ -6101,5 +6172,15 @@ حذف اِسمُ المُستخدِم + + + ساعة + + دقيقة + + تعيين + + الحد الأدنى من الوقت قبل تطبيق قفل الشاشة هو 1 دقيقة. + diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 888a6353ff..02c77cd300 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -14,6 +14,7 @@ + Bəli Xeyr @@ -96,13 +97,13 @@ Mesajlar yoxlanılır… - Əngəllənmiş istifadəçilər - İstifadəçi əlavə et - Əngəllənmiş istifadəçilər, sizə mesaj göndərə və ya zəng edə bilməz. - Heç bir istifadəçi əngəllənməyib - İstifadəçi əngəllənsin? + Bloklanmış istifadəçilər + Bloklanmış istifadəçini əlavə et + Bloklanmış istifadəçilər, sizə mesaj göndərə və ya zəng edə bilməz. + Bloklanmış istifadəçi yoxdur + İstifadəçi bloklansın? \"%1$s\" sizə zəng edə və ya mesaj göndərə bilməyəcək. - Əngəllə + Blokla @@ -138,8 +139,8 @@ Davam et - %1$s qrupunu əngəlləyərək tərk edirsiniz? - %1$s əngəllənsin? + %1$s qrupunu bloklayaraq tərk edirsiniz? + %1$s bloklansın? Artıq bu qrupdan mesaj və ya yeniləmələr almayacaqsınız və üzvlər sizi bu qrupa yenidən əlavə edə bilməyəcək. Qrup üzvləri, sizi yenidən bu qrupa əlavə edə bilməyəcək. Qrup üzvləri, sizi bu qrupa yenidən əlavə edə biləcək. @@ -147,16 +148,16 @@ Bir-birinizə mesaj göndərə, zəng edə biləcəksiniz, foto və adınız onlarla paylaşılacaq. Bir-birinizə mesaj göndərə biləcəksiniz. - Əngəllənmiş şəxslər sizə zəng edə və ya mesaj göndərə bilməyəcək. - Əngəllənən şəxslər sizə mesaj göndərə bilməyəcək. + Bloklanmış şəxslər sizə zəng edə və ya mesajlar göndərə bilməyəcək. + Bloklanmış şəxslər sizə mesajlar göndərə bilməyəcək. - Signal yeniliklərini və xəbərlərini almağı əngəlləyin. + Signal yeniliklərini və xəbərlərini bloklayın. Signal yeniliklərini və xəbərlərini almağı davam etdirin. - %1$s əngəldən çıxarılsın? - Əngəllə - Əngəllə və tərk et - Spam bildir və əngəllə + %1$s Blokdan çıxarılsın? + Blokla + Blokla və çıx + Spamla olaraq bildir və blokla Bu gün @@ -318,7 +319,7 @@ Signal mesajı Gəlin Molly-a keçək: %1$s Zəhmət olmasa bir əlaqə seçin - Əngəldən çıxart + Blokdan çıxart Qoşma, göndərdiyiniz mesaj növü üçün həcm limitləri aşır. Səs yazıla bilmir! Üzv olmadığınız üçün bu qrupa mesaj göndərə bilməzsiniz. @@ -366,7 +367,7 @@ Mesaj göndərmə xətası - Spam olaraq bildirildi və əngəlləndi. + Spam olaraq bildirildi və bloklandı. SMS mesajlaşması hazırda qeyri-aktivdir. Mesajlarınızı telefonunuzdakı başqa bir tətbiqə ixrac edə bilərsiniz. @@ -447,15 +448,15 @@ %1$s açıqdır - Tələb əngəllənsin? + Tələb bloklansın? %1$s, qrup bağlantısı vasitəsilə bu qrupa qoşula və ya qoşulma tələbi göndərə bilməz. Yenə də qrupa manual olaraq əlavə edilə bilər. - Tələbi əngəllə + Tələbi blokla İmtina - Əngəlləndi + Bloklandı Filtri təmizlə @@ -480,12 +481,12 @@ %1$d söhbət gələnlər qutusuna köçürüldü - Oxundu - Oxundu + Oxunmuş + Oxunmuş - Oxunmadı - Oxunmadı + Oxunmamış + Oxunmamış Sancaqla @@ -496,17 +497,17 @@ Sancağı götür - Səssizə al - Səssizə al + Səssiz et + Səssiz et Səsi aç Səsi aç - Seçin + Seç - Arxivlə - Arxivlə + Arxivləşdir + Arxivləşdir Arxivdən çıxart @@ -542,6 +543,15 @@ +%1$d + + Cihazlarınızı yenidən əlaqələndirin + + Cihazınız qeydiyyatdan çıxarıldıqda əlavə etdiyiniz cihazlarla əlaqə kəsildi. İstənilən cihazı yenidən əlaqələndirmək üçün Parametrlərə keçin. + + Parametrləri aç + + Daha sonra + Üzvləri seçin @@ -935,7 +945,7 @@ Adım çəkiləndə bildir - Səssizə alınmış söhbətlərdə adınız çəkiləndə bildiriş alınsın? + Səsi bağlı söhbətlərdə adınız çəkiləndə bildiriş alınsın? Həmişə bildir Bildirmə @@ -953,6 +963,16 @@ İstifadəçi adı yaradıldı İstifadəçi adı köçürüldü + + İstifadəçi adını silmək mümkün olmadı. Daha sonra yenidən cəhd edin. + + İstifadəçi adı silindi + + + + İstifadəçi adınızla bağlı xəta yarandı, o artıq hesabınıza təyin edilməyib. Onu yenidən quraşdırmağa cəhd edin və ya yenisini seçin. + + İndi düzəlt @@ -1156,8 +1176,8 @@ Yeni qrup Dostları dəvət et SMS istifadə et - Görünüş - Foto əlavə et + Çat rəngləri + Bir profil fotosu əlavə et Cavablar @@ -1468,14 +1488,14 @@ Qəbul et Davam et Sil - Əngəllə - Əngəldən çıxart + Blokla + Blokdan çıxart %1$s əlaqəsinin sizə mesaj göndərməsinə icazə verərək adınızı və fotonuzu onunla paylaşırsınız? Qəbul edənə qədər onun mesajlarını gördüyünüzü bilməyəcək. - %1$s əlaqəsinin sizə mesaj göndərməsinə icazə verərək adınızı və fotonuzu onunla paylaşırsınız? Əngəldən çıxardana qədər heç bir mesaj almayacaqsınız. + %1$s adlı istifadəçinin sizə mesaj göndərməsinə icazə verərək adınızı və fotonuzu onunla paylaşırsınız? Blokdan çıxaranadək heç bir mesaj almayacaqsınız. - %1$s sizə mesaj göndərsin? Onu əngəldən çıxarana qədər heç bir mesaj almayacaqsınız. - %1$s üzərindən yeniliklər və xəbərlər alınsın? Əngəli götürənə qədər heç bir yenilik almayacaqsınız. + %1$s sizə mesaj göndərsin? Onu blokdan çıxaranadək heç bir mesaj almayacaqsınız. + %1$s üzərindən yeniliklər və xəbərlər alınsın? Blokdan çıxaranadək heç bir yenilik almayacaqsınız. Bu qrup ilə danışığınıza davam edib adınızı və fotonuzu üzvlər ilə paylaşmaq istəyirsiniz? \@adçəkmə və admin kimi yeni özəllikləri aktivləşdirmək üçün bu qrupu yüksəldin. Bu qrupda adını və fotosunu paylaşmayan üzvlərə qoşulmaq üçün dəvət göndəriləcək. Bu Köhnə Qrup çox böyük olduğu üçün istifadə edilə bilmir. Maksimum qrup ölçüsü %1$d. @@ -1483,7 +1503,7 @@ Bu qrupa qoşularaq adınızı və fotonuzu üzvlərlə paylaşırsınız? Qəbul edənə qədər onların mesajlarını gördüyünüzü bilməyəcəklər. Bu qrupa qoşularaq adınızı və fotonuzu üzvlərlə paylaşırsınız? Qəbul edənə qədər onların mesajlarını görməyəcəksiniz. Bu qrupa qoşulursunuz? Qəbul edənə qədər onların mesajlarını gördüyünüzü bilməyəcəklər. - Bu qrupu əngəldən çıxardaraq adınızı və fotonuzu üzvlərlə paylaşırsınız? Əngəldən çıxardana qədər heç bir mesaj almayacaqsınız. + Bu qrupu blokdan çıxararaq adınızı və fotonuzu üzvlərlə paylaşırsınız? Blokdan çıxaranadək heç bir mesaj almayacaqsınız. Bax %1$s qrupunun üzvü @@ -1584,9 +1604,20 @@ Yeni PIN yarat + + SMS kodu göndər + + Signal Qeydiyyatı - Android üçün PIN kodla bağlı kömək lazımdır + + PIN kodunuz yaratdığınız ədədi və ya alfa nömrəli ola bilən %1$d+ rəqəmli koddur.\n\nPIN kodunuzu xatırlamırsınızsa, yenisini yarada bilərsiniz. + + PIN kodunuzu xatırlamırsınızsa, yenisini yarada bilərsiniz. + + PIN kod təxminləriniz tükəndi, lakin siz hələ də yeni PIN kod yaradaraq Signal hesabınıza daxil ola bilərsiniz. + Xəbərdarlıq - Əgər PIN-i sıradan çıxartsanız, manual olaraq nüsxələmə və geri yükləmə etmədiyiniz müddətcə Signal-da yenidən qeydiyyatdan keçəndə bütün verilənlərinizi itirəcəksiniz. PIN sıradan çıxarılanda, Qeydiyyat Kilidini aça bilməyəcəksiniz. + Əgər PIN-i qeyri-aktiv etsəniz, əllə ehtiyat nüsxəsini çıxarmadığınız və geri yükləmə etmədiyiniz müddətcə Signal-da yenidən qeydiyyatdan keçəndə bütün verilənləri itirəcəksiniz. PIN qeyri-aktiv edildikdə, Qeydiyyat Kilidini aça bilməyəcəksiniz. PIN-i sıradan çıxart @@ -1616,8 +1647,8 @@ Hekayəm - Əngəllə - Əngəldən çıxart + Blokla + Blokdan çıxart @@ -1713,7 +1744,7 @@ Səsi aç - Səssizə al + Səsi bağla Zəng @@ -1726,12 +1757,12 @@ - %1$s əngəlləndi + %1$s bloklandı Daha çox məlumat Onların göndərdiyi səs və ya görüntünü almayacaqsınız və onlar da sizin göndərdiklərinizi almayacaq. %1$s əlaqəsindən səs və ya görüntü alına bilmir %1$s əlaqəsindən səs və ya görüntü alına bilmir - Bu, qarşı tərəf güvənlik nömrəsi dəyişikliyini təsdiqləmədiyi, cihazında bir problem olduğu və ya sizi əngəllədiyi üçün baş verə bilər. + Bu, qarşı tərəf güvənlik nömrəsi dəyişikliyinizi təsdiqləmədiyi, cihazında bir problem olduğu və ya sizi blokladığı üçün baş verə bilər. Ekran paylaşımına baxmaq üçün sürüşdürün @@ -1768,11 +1799,18 @@ Signal-ın dostlarınızla bağlantı qurmağınıza və mesaj göndərməyinizə kömək etməsi üçün əlaqələr və media icazələrinə ehtiyacı var. Əlaqələriniz Signal-ın özəl əlaqə kəşfi istifadə edilərək yüklənir, bu da o deməkdir ki, əlaqələriniz bir ucdan digərinə qədər şifrələnir və heç vaxt Signal xidməti tərəfindən görüntülənə bilmir. Signal-ın dostlarınızla bağlantı qurmağınıza kömək etməsi üçün əlaqələr və media icazələrinə ehtiyacı var. Əlaqələriniz Signal-ın özəl əlaqə kəşfi istifadə edilərək yüklənir, bu da o deməkdir ki, əlaqələriniz bir ucdan digərinə qədər şifrələnir və heç vaxt Signal xidməti tərəfindən görüntülənə bilmir. Bu nömrəni qeydiyyatdan keçirmək üçün həddindən çox cəhd etdiniz. Zəhmət olmasa daha sonra yenidən sınayın. + + Bu nömrəni qeydiyyatdan keçirmək üçün həddindən çox cəhd etdiniz. %1$s dəqiqə sonra yenidən cəhd edin. Xidmətlə bağlantı qurula bilmir. Zəhmət olmasa şəbəkə bağlantınızı yoxlayıb yenidən sınayın. Standart olmayan nömrə formatı Daxil etdiyiniz nömrə (%1$s) standartlara cavab vermir.\n\nBunu nəzərdə tuturdunuz: %2$s? Molly Android - Telefon nömrə formatı + Zəng tələb olundu + + SMS tələb olunur + + Təsdiqləmə kodu tələb olunur Xətanın aradan qaldırılması üçün sorğunu daxil etmək üçün %1$d cəmi addım qaldı. Bir sazlama jurnalını göndərməyinizə %1$d addım qaldı. @@ -1792,6 +1830,16 @@ Zəng et Təsdiqləmə kodu Kodu yenidən göndər + + Qeydiyyatdan keçməkdə çətinlik çəkirsiniz? + + • SMS qəbulu və ya zəng etmək üçün telefonunuzda mobil siqnalın aktivliyindən əmin olun \n • Nömrəyə telefon zəngi qəbul edə biləcəyinizi təsdiqləyin\n• Telefon nömrənizi düzgün daxil edib-etmədiyinizi yoxlayın. + + Əlavə məlumat üçün problemlərin aradan qaldırılması ilə əlaqəli bu addımları yerinə yetirin və ya Dəstək xidmətimizlə əlaqə saxlayın + + buradakı nasazlıqların aradan qaldırılması addımları + + Dəstək xidməti ilə əlaqə Qeydiyyat Kilidi işə salınsın? @@ -1958,6 +2006,10 @@ Ödəniş Planlaşdırılmış mesaj + + Mesaj tarixçəniz birləşdirilib + + %1$s nömrəsi %2$s adlı istifadəçiyə aiddir Molly yeniləməsi @@ -2030,7 +2082,7 @@ Mövcud olmayan seans üçün MMS mesajı şifrələndi - Bildirişləri səssizə al + Bildirişlərin səsini bağla İdxal davam edir @@ -2087,14 +2139,16 @@ Güvənli olmayan SMS %1$s %2$s Əlaqə - \"%2$s\" mesajına reaksiya verdi: %1$s - Videonuza reaksiya verdi: %1$s - Şəklinizə reaksiya verdi: %1$s - GIF-inizə reaksiya verdi: %1$s - Faylınıza reaksiya verdi: %1$s - Səsinizə reaksiya verdi: %1$s - Bir dəfə baxıla bilən medianıza reaksiya verdi: %1$s - Stikerinizə reaksiya verdi: %1$s + \"%2$s\" mesajına reaksiya verdi: %1$s. + Videonuza reaksiya verdi: %1$s. + Şəklinizə reaksiya verdi: %1$s. + GIF-inizə reaksiya verdi: %1$s. + Faylınıza reaksiya verdi: %1$s. + Səsinizə reaksiya verdi: %1$s. + Bir dəfə baxıla bilən media faylınıza reaksiya verdi: %1$s. + + Ödənişinizə %1$s reaksiyası verdi. + Stikerinizə reaksiya verdi: %1$s. Mesaj silindi. Əlaqə Signal-a qoşuldu bildirişləri söndürülsün? Bunu Signal > Tənzimləmələr > Bildirişlər üzərindən təkrar fəallaşdıra bilərsiniz. @@ -2298,7 +2352,7 @@ Zəng bildirişlərini fəallaşdır Əlaqəni yenilə - Tələbi əngəllə + Tələbi blokla Heç bir ortaq qrup yoxdur. Tələbləri diqqətlə nəzərdən keçirin. Bu qrupda heç bir əlaqə yoxdur. Tələbləri diqqətlə nəzərdən keçirin. Bax @@ -2487,8 +2541,8 @@ Çatdırılma problemi - %1$s tərəfindən göndərilən bir mesaj, stiker, reaksiya və ya oxundu bildirişi sizə çatdırıla bilmədi. Sizə birbaşa və ya qrup daxilindən göndərməyə çalışmış ola bilər. - %1$s tərəfindən göndərilən bir mesaj, stiker, reaksiya və ya oxundu bildirişi sizə çatdırıla bilmədi. + %1$s tərəfindən göndərilən bir mesaj, stiker, reaksiya və ya oxunma bildirişi sizə çatdırılmadı. O, bunu sizə birbaşa və ya qrup daxilində göndərməyə çalışmış ola bilər. + %1$s tərəfindən göndərilən mesaj, stiker, reaksiya və ya oxunma bildirişi sizə çatdırılmadı. Ad (tələb olunur) @@ -2635,10 +2689,10 @@ İlkin tənzimləmə istifadə et Özəl tənzimləmə istifadə et - 1 saatlıq səssizə al - 8 saatlıq səssizə al - 1 günlük səssizə al - 7 günlük səssizə al + 1 saatlıq səsi bağla + 8 saatlıq səsi bağla + 1 günlük səsi bağla + 7 günlük səsi bağla Həmişə İlkin tənzimləmələr @@ -2675,9 +2729,9 @@ Ünvan kitabçasındakı fotoları istifadə et Əgər varsa, ünvan kitabçanızdan əlaqənin fotosunu görüntüləyin - Səssizə alınmış söhbətlər arxivlənmiş saxlanılsın + Səsi bağlanmış söhbətlər arxivdə saxlanılsın - Arxivlənmiş və səssizə alınmış söhbətlər, yeni mesaj gələndə arxivlənmiş olaraq qalacaq. + Arxivlənmiş və səsi bağlanmış söhbətlər, yeni mesaj gələndə arxivlənmiş olaraq qalacaq. Bağlantı önbaxışları yarat Göndərdiyiniz mesajlar üçün bağlantı önbaxışlarını birbaşa veb saytlardan alın. Parolu dəyişdir @@ -2971,7 +3025,7 @@ Ödəniş göndərildi Ödəniş alındı Ödəniş tamamlandı %1$s - Blok nömrəsi + Nömrəni blokla Köçürmə @@ -3056,7 +3110,7 @@ Yeni mesaj - İstifadəçini əngəllə + İstifadəçini blokla Qrupa əlavə et @@ -3131,7 +3185,7 @@ Səsi aç - Bildirişləri səssizə al + Bildirişlərin səsini bağla Qrup tənzimləmələri @@ -3295,6 +3349,8 @@ PIN-inizi daxil edin Hesabınız üçün yaratdığınız PIN-i daxil edin. Bu, SMS təsdiqləmə kodundan fərqlidir. + + Hesabınız üçün yaratdığınız PIN kodu daxil edin. Alfa nömrəli PIN daxil edin Rəqəmli PIN yazın Yanlış PIN. Yenidən sınayın. @@ -3398,7 +3454,10 @@ Ehtiyat nüsxənizə çox böyük həcmli bir fayl daxil edildiyi üçün nüsxəni çıxarmaq mümkün olmadı. Həmin faylı silib yeni bir ehtiyat nüsxə yaradın. Nüsxələri idarə etmək üçün toxunun. Şifrə səhvdir? + Mənə zəng et (%1$02d:%2$02d) + + (%1$02d:%2$02d) ərzində kodu yenidən göndər Signal Dəstək komandası ilə əlaqə Signal Qeydiyyatı - Android üçün Təsdiqləmə Kodu Yanlış kod @@ -3406,6 +3465,18 @@ Bilinmir Telefon nömrəmə baxa bilər Məni telefon nömrəmlə tapa bilər + + Telefon nömrəsi + + Telefon nömrənizi görə biləcək və nömrəniz vasitəsilə Molly-da sizinlə əlaqə saxlaya biləcək şəxsləri seçin. + + Nömrəmi kim görə bilər? + + Telefon nömrənizi Molly-da heç kim görməyəcək + + Məni nömrəmlə kim tapa bilər? + + Telefon nömrənizi mesaj göndərdiyiniz şəxslər və qruplar görə bilər. Kontaktlar siyahısında telefon nömrəniz olan şəxslər də Molly-da bunu görə biləcək. Hər kəs Əlaqələrim Heç kim @@ -3562,8 +3633,8 @@ - Əngəllə - Əngəldən çıxart + Blokla + Blokdan çıxart Əlaqələrə əlavə et Əlaqələri açmaq üçün tətbiq tapıla bilmir. @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" əngəlləndi. - \"%1$s\" əngəllənmədi. - \"%1$s\" əngəldən çıxarıldı. + \"%1$s\" bloklanıb. + \"%1$s\" bloklana bilmədi + \"%1$s\" blokdan çıxarıldı. Üzvləri nəzərdən keçir @@ -3644,7 +3715,7 @@ Əlaqəniz Qrupdan çıxart Əlaqəni yenilə - Əngəllə + Blokla Sil %1$s, təzəlikcə profil adını %2$s olaraq dəyişdirdi @@ -3670,8 +3741,8 @@ Hesabınızı silsəniz, aşağıdakılar da silinəcək: Telefon nömrənizi daxil edin Hesabı sil - Hesab məlumatlarınız və profil fotonuz silinəcək - Bütün mesajlarınız silinəcək + Hesab məlumatlarınız və profil fotonuzu silin + Bütün mesajlarınızı silin %1$s ödənişlər hesabınızdan silinəcək Heç bir ölkə kodu göstərilməyib Heç bir nömrə göstərilməyib @@ -3784,12 +3855,12 @@ Pulqabını deaktiv et Balansınız - Ödənişləri deaktiv etməzdən əvvəl pulunuzu başqa bir pulqabı ünvanına köçürmək məsləhət görülür. Pulunuzu indi köçürməməyi seçsəniz, ödənişləri yenidən aktivləşdirdiyiniz təqdirdə, pulunuz Molly-a bağlı pulqabınızda qalacaq. + Ödənişləri qeyri-aktiv etməzdən əvvəl pulunuzu başqa bir pulqabı ünvanına köçürməniz məsləhət görülür. Pulunuzu indi köçürməməyi seçsəniz, ödənişləri yenidən aktivləşdirdiyiniz təqdirdə, pulunuz Molly-la əlaqələndirilmiş pulqabınızda qalacaq. Qalan balansı köçür Köçürmə olmadan deaktiv et Deaktiv et Köçürmə olmadan deaktiv edilsin? - Ödənişləri yenidən aktivləşdirməyi seçsəniz, balansınız Molly-a bağlı pulqabınızda qalacaq. + Ödənişləri yenidən aktivləşdirməyi seçsəniz, balansınız Molly-a əlaqələndirilmiş pulqabınızda qalacaq. Pulqabını deaktiv edərkən xəta baş verdi. @@ -4003,12 +4074,12 @@ Profil yarat - Əngəlləndi + Bloklandı %1$d əlaqə Mesajlaşma Yox olan mesajlar Tətbiq təhlükəsizliyi - Sonuncular siyahısında və tətbiq daxilində ekran şəkillərini əngəllə + Sonuncular siyahısında və tətbiq daxilində ekran şəkillərini blokla Signal mesajları və zəngləri, zəng köçürmə və gizli göndərən Yeni söhbətlər üçün ilkin vaxtölçən Başlatdığınız bütün yeni söhbətlər üçün ilkin bir yox olan mesaj vaxtölçəni tənzimləyin. @@ -4106,7 +4177,7 @@ İndi yox - Reaksiyaları özəlləşdirin + Reaksiyaları fərdiləşdirin Bir ifadəni əvəz etmək üçün toxunun Sıfırla Saxla @@ -4165,7 +4236,7 @@ Zəng et - Səssizə al + Səsi bağla Səssizdə @@ -4175,10 +4246,10 @@ Əlaqə təfsilatları Güvənlik nömrəsinə bax - Əngəllə - Qrupu əngəllə - Əngəldən çıxart - Qrupu əngəldən çıxart + Blokla + Qrupu blokla + Blokdan çıxart + Qrupu blokdan çıxart Bir qrupa əlavə et Hamısına bax Üzv əlavə et @@ -4187,8 +4258,8 @@ Qrup bağlantısı Bir əlaqə kimi əlavə et Səsi aç - Danışıq %1$s tarixinə qədər səssizə alındı - Danışıq həmişəlik səssizə alındı + %1$s tarixinədək söhbətin səsi bağlandı + Söhbətin səsi həmişəlik bağlandı Telefon nömrəsi lövhəyə kopyalandı. Telefon nömrəsi Signal-ı dəstəkləyərək profiliniz üçün nişanlar qazanın. Daha ətraflı məlumat üçün bir nişana toxunun. @@ -4204,7 +4275,7 @@ Kim mesaj göndərə bilər? - Bildirişləri səssizə al + Bildirişlərin səsini bağla Səssizdə deyil Ad çəkmələr Həmişə bildir @@ -4234,7 +4305,7 @@ Çıxart - Əngəllə + Blokla %1$s çıxarılsın? @@ -4242,7 +4313,7 @@ %1$s çıxarılıb - %1$s əngəlləndi + %1$s bloklanıb %1$s çıxarılmadı @@ -4453,7 +4524,7 @@ Aylıq ianə ləğv edildi Stimul nişanınızın müddəti bitdi və artıq profilinizdə görünmür. - Stimul nişanını növbəti 30 gün üçün birdəfəlik ianə edərək yenidən aktivləşdirə bilərsiniz. + Boost nişanını növbəti 30 gün üçün birdəfəlik ianə ilə yenidən aktivləşdirə bilərsiniz. Signal-dan istifadəyə davam edə bilərsiniz, ancaq sizin üçün qurulmuş texnologiyanı dəstəkləmək məqsədilə aylıq ianə verərək davamlı dəstəkçi olmağı da nəzərə ala bilərsiniz. Bir dəstəkçi olun @@ -4465,7 +4536,7 @@ Ödənişinizi emal edə bilmədiyimizə görə təkrarlanan aylıq ianəniz ləğv edildi. Artıq nişanınız profilinizdə görünmür. Təkrarlanan aylıq ianəniz ləğv edildi. %1$s Sizin %2$s nişanınız artıq profilinizdə görünmür. - Signal-dan istifadəyə davam edə bilərsiniz, ancaq tətbiqi dəstəkləmək və nişanınızı təkrar aktivləşdirmək üçün abunəliyi indi yeniləyin. + Signal-dan istifadəyə davam edə bilərsiniz, ancaq tətbiqi dəstəkləmək və nişanınızı təkrar aktivləşdirmək üçün indi yeniləyin. Abunəliyi yenilə Google Play-ə keç @@ -4508,7 +4579,7 @@ Şəbəkə xətasına görə ianənizi göndərmək mümkün olmadı. Bağlantınızı yoxlayıb yenidən cəhd edin. - %1$s üçün ianə + %1$s adından ianə %1$s sizin adınızdan Signal-a ianə verdi @@ -4905,9 +4976,9 @@ Hekayənizə kimin baxa bildiyini seçin. Dəyişikliklər artıq göndərdiyiniz hekayələrə tətbiq olunmayacaq. - Cavablar & reaksiyalar + Cavablar və reaksiyalar - Cavablara & reaksiyalara icazə ver + Cavablara və reaksiyalara icazə ver Hekayələrinizi görən şəxslərin reaksiya və cavab verməsinə icazə verin @@ -5057,11 +5128,11 @@ - %1$s paylaşan hekayəyə reaksiya verdiniz + %1$s adlı istifadəçinin hekayəsinə reaksiya verdiniz Hekayənizə reaksiya verildi - Hekayəyə reaksiya verildi + Bir hekayəyə reaksiya verildi @@ -5322,7 +5393,7 @@ SMS mesajları ixrac olunur - Bu bir qədər çəkə bilər + Bu, bir qədər çəkə bilər %1$d/%2$d ixrac olunur… @@ -5601,5 +5672,15 @@ İstifadəçi adını sil + + + s + + d + + Təyin et + + Ekran kilidi aktivləşmədən əvvəlki minimal vaxt 1 dəqiqədir. + diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index d4818b56bd..4aabfcb6d7 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -14,6 +14,7 @@ + Да Не @@ -98,9 +99,9 @@ Блокирани потребители Добавяне на блокиран потребител - Блокиран потребител не може да ви се обажда или да ви изпраща съобщения. + Блокираните потребители не могат да ви се обаждат, нито да ви изпращат съобщения. Няма блокирани потребители - Блокирай потребител? + Блокиране на потребител? \"%1$s\" няма да може да ви се обажда или да ви изпраща съобщения. Блокиране @@ -138,8 +139,8 @@ Продължи - Блокирай и напусни %1$s? - Блокирай %1$s? + Блокиране и напускане на %1$s? + Блокиране на %1$s? Няма да може да получвате съобщения или известия от тази група и членовете ѝ няма да могат да ви добавят повторно. Членове на групата няма да могат да ви добавят в тази група повторно. Членове на групата ще могат да ви добавят обратно в групата. @@ -147,16 +148,16 @@ Ще можете да изпращате съобщения и да се обаждате един на друг, и името ви и снимката ви ще бъдат споделени с тях. Ще можете да си пишете един на друг. - Блокирани хора няма да могат да ви се обаждат или да ви изпращат съобщения. + Блокираните хора няма да могат да ви се обаждат, нито да ви изпращат съобщения. Блокираните хора няма да могат да ви изпращат съобщения. Блокиране на получаване на актуализации и новини за Signal. Възобновяване на получаване на актуализации и новини за Signal. - Отблокирай %1$s? + Отблокиране на %1$s? Блокиране Блокиране и напускане - Докладвай за спам и блокирай + Докладване за спам и блокиране Днес @@ -318,7 +319,7 @@ Съобщение в Signal Да преминем към Molly %1$s Моля, избери контакт - Отблокирване + Отблокиране Размерът на прикачения файл надминава допустимия лимит за типа съобщение, който изпращате. Не може да бъде записано аудио! Не може да изпращате съобщения на тази група, защото вече не сте неин член. @@ -366,7 +367,7 @@ Грешка при изпращане на медиен файл - Докладван за спам и блокиран. + Потребителят е докладван за спам и блокиран. SMS съобщенията в момента са деактивирани. Можете да експортирате съобщенията си в друго приложение на вашия телефон. @@ -455,7 +456,7 @@ Отказ - Блокиране + Блокирана Изчистване на филтъра @@ -481,11 +482,11 @@ Прочетено - Прочетено + Прочетени - Непрочитане - Непрочитане + Непрочетено + Непрочетени Закачане @@ -496,14 +497,14 @@ Откачане - Заглуши - Заглуши + Заглушаване + Заглушаване - Премахване на заглушаването + Премахни заглушаване Премахни заглушаване - Изберете + Избиране Архивиране Архивиране @@ -516,7 +517,7 @@ Изтриване Изтриване - Избери всичко + Избиране на всичко %1$d избрано %1$d избрани @@ -542,6 +543,15 @@ +%1$d + + Свържете устройствата отново + + Връзката с добавените от вас устройства беше прекъсната, когато дерегистрирахте устройството си. Отидете на „Настройки“, за да свържете устройствата отново. + + Отваряне на настройките + + По-късно + Избери членове @@ -935,7 +945,7 @@ Уведоми ме за споменавания - Получавате ли известия, когато ви споменат в заглушени чатове? + Да получавате ли известия, когато ви споменат в заглушени чатове? Винаги ме известявай Не ме известявай @@ -953,6 +963,16 @@ Потребителското име е създадено Потребителското име е копирано + + Потребителското име не можа да бъде изтрито. Опитайте пак по-късно. + + Потребителското име е изтрито + + + + Нещо се обърка с вашето потребителско име, то вече не е свързано с акаунта ви. Можете да опитате да го зададете отново или да си изберете друго. + + Коригиране сега @@ -1156,8 +1176,8 @@ Нова група Покани приятели Ползвай SMS - Облик - Добави снимка + Цветове на чата + Добавяне на профилна снимка Отговори @@ -1472,10 +1492,10 @@ Отблокиране Позволете на %1$s да ви изпратят съобщение и да споделите вашето име и снимка с тях? Те няма да разберат, че сте видели съобщението им, докато не приемете. - Позволете на %1$s да ви изпратят съобщение и да споделите вашето име и снимка с тях? Няма да получавате съобщения, докато не ги отблокирате. + Позволете на %1$s да ви праща съобщения и споделете вашето име и снимка с тях? Няма да получавате съобщения, докато не ги отблокирате. - Позволявате ли на %1$s да ви изпраща съобщения? Няма да получавате съобщения, преди да разблокирате потребителя. - Получаване на актуализации и новини от %1$s? Няма да получавате актуализации, преди да разблокирате потребителя. + Позволявате ли на %1$s да ви изпраща съобщения? Няма да получавате съобщения, преди да отблокирате потребителя. + Получаване на актуализации и новини от %1$s? Няма да получавате актуализации, преди да отблокирате потребителя. Продължете разговора си с тази група и споделете името и снимката си с нейните членове? Надстройте тази група, за да активирате нови функции като @споменавания и администратори. Членовете, които не са споделили своето име или снимка в тази група, ще бъдат поканени да се присъединят. Тази група от стар вид вече не може да се използва, защото е твърде голяма. Максималният размер на групата е %1$d. @@ -1483,7 +1503,7 @@ Присъединете се към тази група и споделете вашето име и снимка с нейните членове? Те няма да разберат, че сте видели съобщенията им, докато не приемете. Присъединяване към тази група и споделяне на името и снимката ви с нейните членове? Няма да виждате техните съобщения, преди да приемете. Присъединете се към тази група? Те няма да разберат, че сте виждали съобщенията им, докато не приемете. - Разблокирайте тази група? Няма да получавате никакви съобщения, преди да ги разблокирате. + Отблокирайте тази група и споделете името и снимката си с членовете? Няма да получавате никакви съобщения, преди да ги отблокирате. Преглед Член на %1$s @@ -1584,9 +1604,20 @@ Създайте нов ПИН + + Изпращане на код чрез SMS + + Регистрация за Signal - Необходима помощ с пререгистриране на ПИН за Android + + Вашият ПИН е създаден от вас код с %1$d+ цифри, който може да съдържа само цифри или цифри и букви.\n\nАко не си спомняте своя ПИН, можете да си създадете нов. + + Ако не си спомняте своя ПИН, можете да си създадете нов. + + Нямате повече опити за познаване на ПИН кода, но все пак можете да отворите своя акаунт в Signal, като си създадете нов ПИН. + Внимание - Ако деактивирате ПИН кода, ще загубите всички данни, когато пререгистрирате Signal, освен ако не направите ръчно архивиране и възстановяване. Не можете да включите Регистрационно заключване, докато ПИН кодът е деактивиран. + Ако деактивирате ПИН кода, ще загубите всички данни, когато пререгистрирате Signal, освен ако не направите ръчно архивиране и възстановяване. Не можете да включите „Регистрационно заключване“, докато ПИН кодът е деактивиран. Деактивиране на ПИН @@ -1711,9 +1742,9 @@ Камера - Изключи тих режим + Изключване на тих режим - Заглуши + Заглушаване Звънни @@ -1726,12 +1757,12 @@ - %1$s е блокиран + Потребителят %1$s е блокиран Повече инфо Няма да получавате аудио или видео от тях и те няма да получават от вас Не може да приемате аудио & видео от %1$s Не може да приемате аудио и видео от %1$s - Това може да се дължи на факта, че те не са проверили промяната на номера ви за безопасност, има проблем с устройството им или са ви блокирали. + Може потребителят да не е потвърдил промяната на номера ви за безопасност, да има проблем с устройството си или да ви е блокирал. Плъзнете за да видите споделения екран @@ -1768,11 +1799,18 @@ Signal има нужда от разрешенията за контакти и мултимедия, за да ви помага да се свързвате с приятели и да изпращате съобщения. Контактите ви се качват чрез откриването на частни контакти на Signal, което означава, че са криптирани от край до край и никога не се виждат от услугата Signal. Signal се нуждае от разрешението за контакти, за да ви помага да се свързвате с приятели. Контактите ви се качват чрез откриването на частни контакти на Signal, което означава, че са криптирани от край до край и никога не се виждат от услугата Signal. Направихте прекалено много опити за регистриране на този номер. Моля, опитайте отново по-късно. + + Направихте прекалено много опити за регистриране на този номер. Моля, опитайте отново след %1$s. Неуспешно свързване с услугата. Моля, проверете мрежовата връзка и опитайте отново. Нестандартен формат на номера Въведеният от вас номер (%1$s) изглежда е в нестандартен формат.\n\nДа нямате предвид %2$s? Molly Android – Формат на телефонен номер + Поискано повикване + + Поискан е SMS + + Поискан е код за потвърждение Сега сте %1$d стъпка от изпращането на дебъг лога. Сега сте на %1$d стъпки от изпращането на дебъг лог. @@ -1792,6 +1830,16 @@ Обаждане Код за потвърждение Изпращане на нов код + + Имате затруднения с регистрирането? + + • Уверете се, че телефонът ви има обхват, за да получите SMS или обаждане\n • Потвърдете, че можете да получавате телефонни обаждания до номера\n • Проверете дали сте въвели телефонния си номер правилно. + + За повече информация, следвайте тези стъпки за отстраняване на неизправности или се свържете с поддръжката ни + + тези стъпки за отстраняване на неизправности + + Връзка с отдела за поддръжка Включване на регистрационно заключване? @@ -1953,11 +2001,15 @@ Реагира с %1$s на историята ви - Реагира с %1$s на историята + Реагирахте с %1$s на историята Плащане Насрочено съобщение + + Хронологията на съобщенията ви беше слята + + %1$s принадлежи на %2$s Обновление на Molly @@ -2094,6 +2146,8 @@ Реагира с %1$s на вашия файл. Реагира с %1$s на вашето аудио. Реагира с %1$s на вашата еднократна медия. + + Реагира с %1$s на вашето плащане. Реагира с %1$s на вашия стикер. Това съобщение беше изтрито. @@ -2487,8 +2541,8 @@ Грешка при доставяне - Съобщение, стикер, реакция или известие за прочетено съобщение не успя да бъде доставено до вас от %1$s. Може да са опитали да ви го изпратят лично на вас или на група. - Съобщение, стикер, реакция или известие за прочетено съобщение не успя да бъде доставено до вас от %1$s. + Съобщение, стикер, реакция или разписка за прочитане не успя да бъде доставена до вас от %1$s. Може да са опитали да го изпратят лично на вас или на група. + Съобщение, стикер, реакция или разписка за прочитане не успя да бъде доставена до вас от %1$s. Собствено име (задължително) @@ -2675,9 +2729,9 @@ Използване на снимки от адресната книга Показване на снимки на контакти от адресната ви книга, ако има такива - Keep Muted Chats Archived + Запазване на заглушените чатове в архива - Muted chats that are archived will remain archived when a new message arrives. + Заглушените чатове, които биват архивирани, ще останат архивирани, когато пристигне ново съобщение. Генерирайте визуализации на връзки Извличайте визуализации на връзки директно от уебсайтове за съобщения, които изпращате. Смени паролата @@ -3128,7 +3182,7 @@ - Изключи тих режим + Изключване на тих режим Тих режим за известия @@ -3295,6 +3349,8 @@ Въведете своя ПИН Въведете ПИН кода, който сте създали за своя акаунт. Той е различен от кода за потвърждение през SMS. + + Въведете ПИН кода, който сте създали за акаунта си. Въведете ПИН от букви и цифри Въведете ПИН от цифри Грешен ПИН. Опитайте отново. @@ -3398,7 +3454,10 @@ Архивът ви съдържа много голям файл, който не може да бъде архивиран. Моля, изтрийте го и създайте нов архив. Докоснете, за да управлявате резервни копия. Грешен номер? + Обадете ми се (%1$02d:%2$02d) + + Повторно изпращане на кода (%1$02d:%2$02d) Връзка с поддръжката на Signal Регистрация за Signal - Код за потвърждение за Android Некоректен код @@ -3406,6 +3465,18 @@ Непознат Виж телефонния номер Намери ме по Телефонен номер + + Телефонен номер + + Изберете кой може да вижда телефонния ви номер и кой може да се свързва с вас в Molly чрез него. + + Кой може да вижда номера ми + + Никой няма да вижда телефонния ви номер в Molly + + Кой може да ме намери по номер + + Вашият телефонен номер ще се вижда от хората и групите, на които пращате съобщения. Хората, които имат вашия номер в контактите на телефона си, ще го виждат и в Molly. Всички Моите контакти Никой @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" беше блокиран. - Блокирането на \"%1$s\" не бе успешно - \"%1$s\" беше отблокиран. + Потребителят „%1$s“ беше блокиран. + Неуспешно блокиране на „%1$s“ + Потребителят „%1$s“ беше отблокиран. Преглед на членовете @@ -3644,7 +3715,7 @@ Твоя контакт Премахване от групата Поднови контакт - Блокирай + Блокиране Изтриване Скоро смениха името от %1$s до %2$s @@ -3667,11 +3738,11 @@ Слаба Wi-Fi мрежа. Превключено е към мобилни данни. - Изтриването на профила Ви ще: + Изтриването на акаунта ви ще доведе до: Въведете телефонния си номер Изтриване на акаунт - Изтриване на информацията за профила ви и снимката ви - Изтриване на всички съобщения + Изтриване на информацията за акаунта и профилната ви снимка + Изтриване на всички ваши съобщения Изтриване на %1$s от вашия акаунт за плащания Не е избран код на държава Не е посочен номер @@ -4003,12 +4074,12 @@ Създаване на профил - Блокиране + Блокиран %1$d контакта Съобщения Изчезващи съобщения Сигурност - Забраняване на автоматично копиране на екрана в списъка с често използвани програми и в самата програма. + Блокиране на екранни снимки в списъка със скорошни разговори и в приложението Съобщения и повиквания в Signal, повикванията винаги да се предават и запечатан подател Таймер по подразбиране за нови чатове Настроете таймер за изчезващи съобщения по подразбиране за нови чатове, започнати от вас. @@ -4165,7 +4236,7 @@ Обаждане - Заглуши + Заглушаване Заглушен @@ -4175,7 +4246,7 @@ Детайли за контакта Преглеждане на числото за сигурност - Блокирай + Блокиране Блокиране на групата Отблокиране Отблокиране на групата @@ -4186,7 +4257,7 @@ Заявки и покани Връзка към групата Добави като контакт - Изключи тих режим + Изключване на тих режим Разговорът е заглушен до %1$s Разговорът е заглушен завинаги Телефонният номер е копиран в клипборда. @@ -4242,7 +4313,7 @@ Разговорът „%1$s“ е премахнат - Разговорът „%1$s“ е блокиран + Контактът „%1$s“ е блокиран Неуспешно премахване на %1$s @@ -4508,7 +4579,7 @@ Вашето дарение не беше изпратено заради мрежова грешка. Проверете връзката си и опитайте отново. - Дарение за %1$s + Дарение от името на %1$s %1$s дари на Signal от ваше име @@ -5601,5 +5672,15 @@ Изтриване на потребителско име + + + ч + + м + + Задаване + + Минималното време преди активиране на заключването на екрана е 1 минута. + diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 56c7a1915a..eb413d3bf2 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -14,13 +14,14 @@ + হ্যাঁ না মুছে ফেলুন অনুগ্রহপূর্বক অপেক্ষা করুন… সংরক্ষণ - নিজের উদ্দেশ্যে মন্তব্য + নিজের জন্য নোট @@ -98,8 +99,8 @@ ব্লক করা ইউজার ব্লক করা ইউজার যোগ করুন - ব্লক করা ইউজাররা আপনাকে কল করতে বা আপনাকে মেসেজ পাঠাতে পারবেন না। - কোনও ব্লক করা ইউজার নেই + ব্লক করা ইউজাররা আপনাকে কল করতে বা আপনাকে ম্যাসেজ পাঠাতে পারবেন না। + কোনো ব্লক করা ইউজার নেই ইউজারকে ব্লক করবেন? \"%1$s\" আপনাকে কল করতে বা আপনাকে মেসেজ পাঠাতে পারবেন না। ব্লক করুন @@ -138,8 +139,8 @@ চালিয়ে যান - ব্লক করুন ও ছেড়ে যান %1$s? - ব্লক %1$s? + %1$s-কে ব্লক করবেন এবং বের হয়ে যাবেন? + %1$s-কে ব্লক করবেন? আপনি আর এই গ্রুপ থেকে বার্তা বা আপডেট পাবেন না, এবং গ্রুপের সদস্যরা আপনাকে আবার এই গ্রুপে যুক্ত করতে পারবে না। গ্রুপের সদস্যরা আপনাকে পুনরায় কখনো এই গ্রুপে যুক্ত করতে পারবেনা। গ্রুপের সদস্যরা আপনাকে আবার এই গ্রুপে যুক্ত করতে পারবে। @@ -147,15 +148,15 @@ তোমরা একে অপরকে বার্তা পাঠাতে ও কল করতে পারবে এবং তোমার নাম ও ছবি তাদের সাথে শেয়ার করা হবে। আপনারা একে অপরকে ম্যাসেজ দিতে পারবেন। - অবরুদ্ধ লোকেরা আপনাকে কল করতে বা আপনাকে বার্তা প্রেরণ করতে পারবে না। - ব্লককৃত ব্যক্তিরা আপনাকে ম্যাসেজ পাঠাতে পারবে না। + ব্লক করা ব্যক্তিরা আপনাকে কল করতে বা আপনাকে ম্যাসেজ পাঠাতে পারবেন না। + ব্লক করা ব্যক্তিরা আপনাকে ম্যাসেজ পাঠাতে পারবে না। - Signal-এর আপডেট এবং বার্তা না পেতে চাইলে ব্লক করুন। + Signal-এর আপডেট এবং ম্যাসেজ পেতে না চাইলে ব্লক করুন। Signal-এর আপডেট এবং বার্তা পেতে চাইলে চালু করুন। - %1$s কে আনব্লক করুন? - অবরূদ্ধ - ব্লক করুন ও ছেড়ে যান + %1$s-কে আনব্লক করবেন? + ব্লক করুন + ব্লক করুন ও বের হয়ে যান স্প্যাম হিসেবে রিপোর্ট করুন এবং ব্লক করুন @@ -366,7 +367,7 @@ ছবি/ভিডিও পাঠাতে ত্রুটি - স্প্যাম হিসেবে রিপোর্ট এবং ব্লক করা হয়েছে + স্প্যাম হিসেবে রিপোর্ট এবং ব্লক করা হয়েছে। এসএমএস ম্যাসেজিং বর্তমানে নিষ্ক্রিয় রয়েছে। আপনি আপনার ফোনের অন্য অ্যাপে আপনার ম্যাসেজগুলো স্থানান্তর করতে পারবেন। @@ -455,7 +456,7 @@ বাতিল করুন - অবরূদ্ধ আছে + ব্লক করা হয়েছে ফিল্টার খালি করুন @@ -488,29 +489,29 @@ অপঠিত - পিন - পিন + পিন করুন + পিন করুন আনপিন করুন আনপিন করুন - মিউট - মিউট + নীরব করুন + নীরব করুন - আনমিউট করুন - আনমিউট করুন + সরব করুন + সরব করুন - নির্বাচন + নির্বাচন করুন - লুকাইয়া রাখুন - লুকাইয়া রাখুন + আর্কাইভ করুন + আর্কাইভ করুন - পুনরুদ্ধার করুন - পুনরুদ্ধার করুন + আনআর্কাইভ করুন + আনআর্কাইভ করুন মুছে ফেলুন @@ -542,6 +543,15 @@ + %1$d + + আপনার ডিভাইস আবারো লিংক করুন + + যখন আপনার ডিভাইসটি নিবন্ধনহীন অবস্থায় ছিলো তখন আপনার সংযুক্ত ডিভাইসগুলো আনলিংক করা হয়েছিলো। যেকোনো ডিভাইস আবারো লিংক করতে সেটিংসে যান। + + সেটিংস খুলুন + + পরে + সদস্য নির্বাচন করুন @@ -935,7 +945,7 @@ মেনশন করা হলে আমাকে অবহিত করুন - নিঃশব্দ করা বার্তাগুলোতে আপনার নাম নেয়া হলে নোটিফিকেশন পেতে চান? + মিউট করা চ্যাটে আপনাকে মেনশন করা হলে নোটিফিকেশন পেতে চান? সর্বদা আমাকে অবহিত করুন আমাকে অবহিত করবেন না @@ -953,6 +963,16 @@ ইউজারনেম তৈরি করা হয়েছে ইউজারনেম কপি করা হয়েছে + + ইউজারনেম মুছে ফেলা যায়নি। পরবর্তীতে আবার চেষ্টা করুন। + + ইউজারনেম মুছে ফেলা হয়েছে + + + + আপনার \'ব্যবহারকারীর নাম\' নিয়ে কিছু একটা সমস্যা হয়েছে, এটি আর আপনার অ্যাকাউন্টের জন্য বরাদ্দ করা নেই। আপনি আবার চেষ্টা করে এটি সেট করতে পারেন বা নতুন একটি বেছে নিতে পারেন। + + এখনই ঠিক করুন @@ -1156,8 +1176,8 @@ নতুন গ্রুপ বন্ধুদের আমন্ত্রণ এসএমএস ব্যবহার করুন - অ্যাপিয়ারেন্স - ছবি যুক্ত করুন + চ্যাটের রং + একটি প্রোফাইল ছবি যুক্ত করুন রিপ্লাই @@ -1468,11 +1488,11 @@ মানছি চলতে থাকুন মুছে ফেলুন - অবরূদ্ধ - মূক্ত করুন + ব্লক করুন + আনব্লক করুন %1$s-কে কি আপনাকে মেসেজ পাঠাতে এবং আপনার নাম ও ছবি শেয়ার করতে দেবেন? আপনি গ্রহণ না করা পর্যন্ত আপনি যে তাদের মেসেজটি দেখেছেন সেটি তারা জানতে পারবেন না। - %1$s-কে কি আপনাকে মেসেজ পাঠাতে এবং আপনার নাম ও ছবি শেয়ার করতে দেবেন? আপনি তাদের আনব্লক না করা পর্যন্ত তাদের কোনও বার্তা পাবেন না। + %1$s-কে কি আপনাকে ম্যাসেজ পাঠাতে এবং আপনার নাম ও ছবি শেয়ার করতে দেবেন? আপনি তাদের আনব্লক না করা পর্যন্ত তাদের পাঠানো কোনো ম্যাসেজ পাবেন না। %1$s-কে ম্যাসেজ পাঠানোর অনুমতি দিবেন? যতক্ষণ না আপনি তাদের আনব্লক করবেন ততক্ষণ আপনি কোনো ম্যাসেজ পাবেন না। %1$s-এর কাছ থেকে আপডেট এবং খবর পেতে চান? যতক্ষণ না আপনি তাদের আনব্লক করবেন ততক্ষণ আপনি কোনো আপডেট পাবেন না। @@ -1483,7 +1503,7 @@ আপনি কি এই গ্রুপে যোগ দিয়ে এর সদস্যদের সাথে আপনার নাম ও ছবি শেয়ার করবেন? আপনি গ্রহণ না করা পর্যন্ত তারা জানবেন না যে আপনি তাদের মেসেজ দেখেছেন। এই গ্রুপে যোগ দিন এবং এর গ্রুপের সদস্যদের সাথে আপনার নাম এবং ছবি শেয়ার করুন? সহমত প্রকাশ করার আগ পর্যন্ত আপনি তাদের ম্যাসেজ দেখতে পাবেন না। এই গ্রুপে যোগ দেবেন? আপনি স্বীকার না করা পর্যন্ত তারা জানবেন না যে আপনি তাদের মেসেজ দেখেছেন। - এই গ্রুপটিকে আনব্লক করে এর সদস্যদের সাথে আপনার নাম ও ছবি শেয়ার করবেন? আপনি তাদের আনব্লক না করা পর্যন্ত কোন মেসেজ পাবেন না। + এই গ্রুপটিকে আনব্লক করে এর সদস্যদের সাথে আপনার নাম ও ছবি শেয়ার করবেন? আপনি তাদের আনব্লক না করা পর্যন্ত কোন ম্যাসেজ পাবেন না। দেখান %1$sএর সদস্য @@ -1584,9 +1604,20 @@ PIN তৈরি করুন + + এসএমএস কোড পাঠান + + Signal রেজিস্ট্রেশন - অ্যান্ড্রয়েডের জন্য পিন পুনরায় রেজিস্ট্রেশন করতে সহায়তা প্রয়োজন + + আপনার পিন হলো আপনারই তৈরি একটি %1$d+ সংখ্যার কোড যা সংখ্যা বা আলফানিউমেরিক হতে পারে।\n\nআপনি যদি আপনার পিন মনে রাখতে না পারেন তবে আপনি একটি নতুন পিন তৈরি করতে পারেন। + + আপনি যদি আপনার পিন মনে রাখতে না পারেন তবে আপনি একটি নতুন পিন তৈরি করতে পারেন। + + আপনার পিন অনুমান করার সুযোগ শেষ হয়ে গেছে, তবে আপনি এখনো একটি নতুন পিন তৈরি করে আপনার Signal অ্যাকাউন্টে অ্যাক্সেস করতে পারবেন। + সতর্কতা - আপনি পিনটি নিষ্ক্রিয় করলে, আপনি Signal-এ সেভ করা ডেটা ম্যানুয়ালি ব্যাক-আপ ও পুনরুদ্ধার না করলে, পুনরায় Signal-এ রেজিস্টার করার সময় আপনি সমস্ত সেভ করা ডেটা হারাবেন। পিন নিষ্ক্রিয় থাকা অবস্থায় আপনি রেজিস্ট্রেশন লক চালু করতে পারবেন না। + পিনটি যদি নিষ্ক্রিয় করেন, তবে আপনি Signal-এ সেভ করা ডেটা ম্যানুয়ালি ব্যাক-আপ ও পুনরুদ্ধার না করলে, পুনরায় Signal-এ রেজিস্টার করার সময় সমস্ত সেভ করা ডেটা হারাবেন। পিন নিষ্ক্রিয় থাকা অবস্থায় আপনি রেজিস্ট্রেশন লক চালু করতে পারবেন না। পিন নিষ্ক্রিয় করুন @@ -1713,7 +1744,7 @@ আনমিউট করুন - নিরব + মিউট করুন রিং @@ -1731,7 +1762,7 @@ আপনি তাদের অডিও বা ভিডিও পাবেন না এবং তারাও আপনার পাবেন না। %1$s থেকে অডিও & ভিডিও গ্রহণ করতে পারবেন না %1$s থেকে অডিও ও ভিডিও গ্রহণ করতে পারবেন না - এর কারণ হতে পারে যে হয় তারা আপনার পরিবর্তিত নিরাপত্তা নাম্বার ভেরিফাই করেননি, নতুবা তাদের ডিভাইসে কোন সমস্যা হয়েছে বা তারা আপনাকে ব্লক করেছেন। + এর কারণ হতে পারে যে- হয়তো তারা আপনার পরিবর্তিত নিরাপত্তা নাম্বার ভেরিফাই করেননি, তাদের ডিভাইসে কোন সমস্যা হয়েছে, বা তারা আপনাকে ব্লক করেছেন। স্ক্রিন শেয়ার দেখতে সোয়াইপ করুন @@ -1768,11 +1799,18 @@ আপনাকে বন্ধুদের সাথে সংযোগ করতে এবং বার্তা পাঠাতে সাহায্য করার জন্য সিগন্যালের পরিচিতি এবং মিডিয়ার অনুমতির প্রয়োজন হয়। আপনার পরিচিতিগুলি সিগন্যালের ব্যক্তিগত পরিচিতি আবিষ্কার ব্যবহার করে আপলোড করা হয়, যার মানে সেগুলি এন্ড-টু-এন্ড এনক্রিপ্ট করা এবং সিগন্যাল পরিষেবাতে কখনই দৃশ্যমান নয়৷ আপনাকে বন্ধুদের সাথে সংযোগ করতে সাহায্য করার জন্য সিগন্যালের পরিচিতির অনুমতির প্রয়োজন হয়। আপনার পরিচিতিগুলি সিগন্যালের ব্যক্তিগত পরিচিতি আবিষ্কার ব্যবহার করে আপলোড করা হয়, যার মানে সেগুলি এন্ড-টু-এন্ড এনক্রিপ্ট করা এবং সিগন্যাল পরিষেবাতে কখনই দৃশ্যমান নয়। আপনি এই নম্বরটি নিবন্ধন করার জন্য অনেকবার চেষ্টা করেছেন। দয়া করে পরে আবার চেষ্টা করুন। + + আপনি এই নম্বরটি রেজিস্ট্রেশন করার জন্য ইতোমধ্যে অনেকবার চেষ্টা করে ফেলেছেন। অনুগ্রহ করে %1$s মিনিটের মধ্যে আবার চেষ্টা করুন। পরিষেবাতে সংযোগ করতে অক্ষম। নেটওয়ার্ক সংযোগ পরীক্ষা করুন এবং আবার চেষ্টা করুন। নন-স্ট্যান্ডার্ড নম্বর বিন্যাস আপনি যে নম্বরটি প্রবেশ করেছেন (%1$s) সেটি একটি অ-মানক বিন্যাস বলে মনে হচ্ছে।\n\nআপনি কি %2$s বলতে চান? সিগন্যাল অ্যান্ড্রয়েড - ফোন নম্বর ফর্ম্যাট + কল অনুরোধ করা হয়েছে + + এসএমএস-এর মাধ্যমে অনুরোধ করা হয়েছে + + যাচাইকরণ কোড-এর মাধ্যমে অনুরোধ করা হয়েছে আপনি এখন একটি ডিবাগ লগ জমা দেওয়ার থেকে %1$d পদক্ষেপ দূরে। আপনি এখন একটি ডিবাগ লগ জমা দেওয়ার থেকে %1$d পদক্ষেপ দূরে। @@ -1792,6 +1830,16 @@ কল করুন যাচাইকরণ কোড কোডটি আবারো পাঠান + + রেজিস্ট্রেশন করতে সমস্যা হচ্ছে? + + • নিশ্চিত করুন যে আপনার ফোনে আপনার এসএমএস বা কল পাওয়ার জন্য একটি সেলুলার সিগন্যাল রয়েছে\n • নিশ্চিত করুন যে আপনি আপনার নম্বরে একটি ফোন কল পেতে পারেন\n • আপনি আপনার ফোন নম্বর সঠিকভাবে লিখেছেন কি না তা যাচাই করুন। + + আরো তথ্যের জন্য, অনুগ্রহ করে সমস্যা সমাধানের এই ধাপগুলো অনুসরণ করুন বা সহায়তা কেন্দ্রের সাথে যোগাযোগ করুন + + সমস্যা সমাধানের এই ধাপগুলো + + সহায়তা কেন্দ্রে যোগাযোগ করুন রেজিস্ট্রেশন লক চালু করবেন? @@ -1958,6 +2006,10 @@ পেমেন্ট নির্ধারিত সূচি অনুযায়ী ম্যাসেজ + + আপনার ম্যাসেজ ইতিহাস সমন্বয় করা হয়েছে + + %1$s নম্বরটি %2$s-এর Molly আপডেট @@ -2030,7 +2082,7 @@ অ-বিদ্যমান সেশনের জন্য এমএমএস বার্তা এনক্রিপ্ট করা - নোটিফিকেশন নিঃশব্দ করুন + নোটিফিকেশন মিউট করুন আমদানি চলছে @@ -2087,14 +2139,16 @@ অনিরাপদ এসএমএস %1$s %2$s পরিচিতি - \"%2$s\" তে %1$s প্রতিক্রিয়া| - আপনার ভিডিওতে %1$s প্রতিক্রিয়া জানিয়েছে| - আপনার চিত্রে %1$s প্রতিক্রিয়া জানিয়েছে| - আপনার GIF তে %1$s প্রতিক্রিয়া জানিয়েছেন৷ - আপনার ফাইলে %1$s প্রতিক্রিয়া জানিয়েছে| - আপনার অডিওতে %1$s প্রতিক্রিয়া জানিয়েছে| - আপনার একবার-দেখা মিডিয়ায় %1$s প্রতিক্রিয়া দেখিয়েছে। - আপনার স্টিকারে %1$s প্রতিক্রিয়া জানিয়েছে| + %1$s রিঅ্যাক্ট করেছেন: \"%2$s\"-এ। + আপনার ভিডিওতে %1$s রিঅ্যাক্ট করেছেন। + আপনার ছবিতে %1$s রিঅ্যাক্ট করেছেন। + আপনার GIF-এ %1$s রিঅ্যাক্ট করেছেন। + আপনার ফাইলে %1$s রিঅ্যাক্ট করেছেন। + আপনার অডিওতে %1$s রিঅ্যাক্ট করেছেন। + আপনার ভিউ-ওয়ান্স মিডিয়াতে %1$s রিঅ্যাক্ট করেছেন। + + আপনার পেমেন্টে %1$s রিঅ্যাক্ট করেছেন। + আপনার স্টিকারে %1$s রিঅ্যাক্ট করেছেন। এই বার্তাটি মুছে ফেলা হয়েছে। কন্ট্যাক্টের Signal-এ যোগ দেওয়ার নোটিফিকেশনটি বন্ধ করবেন? আপনি Signal > সেটিংস > নোটিফিকেশন অপশনে গিয়ে এটি চালু করতে পারবেন। @@ -2487,8 +2541,8 @@ পাঠানোর ক্ষেত্রে সমস্যা - %1$s থেকে আপনাকে কোনও মেসেজ, স্টিকার, প্রতিক্রিয়া বা পাঠের রশিদ পাঠানো যায়নি। তারা হয়তো আপনাকে সরাসরি বা একটি গ্রুপে এটি পাঠানোর চেষ্টা করেছেন। - %1$s থেকে আপনাকে কোনও মেসেজ, স্টিকার বা পাঠের রসিদ পাঠানো যায়নি। + %1$s-এর কাছ থেকে আপনাকে কোনো ম্যাসেজ, স্টিকার, প্রতিক্রিয়া বা রিড রিসিপ্ট পাঠানো যায়নি। তারা হয়তো আপনাকে সরাসরি বা একটি গ্রুপে এটি পাঠানোর চেষ্টা করেছেন। + %1$s-এর কাছ থেকে আপনাকে কোনো ম্যাসেজ, স্টিকার বা রিড রিসিপ্ট পাঠানো যায়নি। নামের প্রথম অংশ (প্রয়োজনীয়) @@ -2555,7 +2609,7 @@ পেন্ডিং - একে পাঠানো হয়েছে + পাঠানো হয়েছে এর থেকে পাঠানো হয়েছে যাকে পাঠানো হয়েছে যিনি পড়েছেন @@ -2635,10 +2689,10 @@ ডিফল্ট ব্যবহার করুন কাস্টম ব্যবহার করুন - 1 ঘন্টা নিঃশব্দ করুন + 1 ঘন্টার জন্য মিউট করুন 8 ঘণ্টার জন্য মিউট করুন - 1 দিনের জন্য নিঃশব্দ করুন - 7 দিনের জন্য নিঃশব্দ করুন + 1 দিনের জন্য মিউট করুন + 7 দিনের জন্য মিউট করুন সবসময় সেটিংস ডিফল্ট @@ -2675,9 +2729,9 @@ অ্যাড্রেস বুকের ছবি ব্যবহার করুন উপলভ্য থাকলে আপনার অ্যাড্রেস বুক থেকে পরিচিতির ছবিগুলো দেখান - মিউটেড চ্যাট আর্কাইভকৃত অবস্থায় রাখুন + মিউট করা চ্যাট আর্কাইভকৃত অবস্থায় রাখুন - যখন নতুন ম্যাসেজ আসবে তখন আর্কাইভ করা মিউটেড চ্যাট আর্কাইভ অবস্থায়ই থাকবে। + যখন নতুন ম্যাসেজ আসবে তখন আর্কাইভকৃত মিউট করা চ্যাট আর্কাইভ অবস্থায়ই থাকবে। লিঙ্কের প্রিভিউ তৈরি করুন আপনার পাঠানো মেসেজের জন্য ওয়েবসাইটগুলি থেকে সরাসরি লিঙ্কের প্রিভিউ পুনরুদ্ধার করুন। পাসফ্রেজ পরিবর্তন করুন @@ -2971,7 +3025,7 @@ পেমেন্ট পাঠানো হয়েছে পেমেন্ট পেয়েছে %1$s পেমেন্ট সম্পন্ন হয়েছে - নাম্বার ব্লক করুন + নম্বর ব্লক করুন ট্রান্সফার করুন @@ -3128,10 +3182,10 @@ - সশব্দ + আনমিউট করুন - নোটিফিকেশন নিঃশব্দ করুন + নোটিফিকেশন মিউট করুন গ্রুপ সেটিংস @@ -3295,6 +3349,8 @@ আপনার পিন প্রবেশ করান আপনি নিজের অ্যাকাউন্ট এর জন্য তৈরি করা পিনটি প্রবেশ করুন। এটি আপনার এসএমএস যাচাইকরণ কোড থেকে ভিন্ন। + + আপনার অ্যাকাউন্টের জন্য তৈরি করা পিনটি লিখুন। বর্ণানুক্রমিক পিন প্রবেশ করান সংখ্যার পিন প্রবেশ করান ভুল পিন। আবার চেষ্টা করুন| @@ -3398,7 +3454,10 @@ আপনার ব্যাকআপে একটি বেশ বড় আকারের ফাইল রয়েছে যার ব্যাকআপ নেওয়া যাচ্ছে না। ফাইলটি মুছে দিন এবং একটি নতুন ব্যাকআপ তৈরি করুন। ব্যাকআপগুলি পরিচালনা করতে আলতো চাপুন। ভুল নম্বর? + আমাকে কল করুন (%1$02d:%2$02d) + + কোডটি আবারো পাঠান (%1$02d:%2$02d) Signal সহায়তায় যোগাযোগ করুন Signal নিবন্ধন - অ্যানড্রয়েড এর জন্য যাচাইকরণ কোড ভুল কোড @@ -3406,6 +3465,18 @@ অজানা আমার ফোন নম্বর দেখুন ফোন নম্বর দিয়ে আমাকে সন্ধান করুন + + ফোন নম্বর + + আপনার ফোন নম্বর কে দেখতে পাবেন এবং এটি ব্যবহার করে Molly-এ কে আপনার সাথে যোগাযোগ করতে পারবেন তা নির্বাচন করুন। + + আমার নম্বর কে দেখতে পাবেন + + কেউ Molly-এ আপনার ফোন নম্বর দেখতে পাবেন না + + নম্বরের মাধ্যমে কে আমাকে খুঁজে পাবেন + + আপনি যেসকল ব্যক্তি ও গ্রুপকে ম্যাসেজ পাঠাবেন তারা আপনার ফোন নম্বরটি দেখতে পাবেন। যেসকল ব্যক্তির ফোনের কন্টাক্টে আপনার ফোন নম্বরটি রয়েছে তারাও Molly-এ সেটি দেখতে পাবেন। সকলে আমার পরিচিতিগুলি কেউ না @@ -3562,8 +3633,8 @@ - অবরূদ্ধ - মূক্ত করুন + ব্লক করুন + আনব্লক করুন পরিচিতি তালিকায় যোগ করুন কন্ট্যাক্ট খুলতে সক্ষম এমন কোন অ্যাপ খুঁজে পাওয়া যাচ্ছে না। @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" কে ব্লক করা হয়েছে। - \"%1$s\" কে ব্লক করতে ব্যর্থ - \"%1$s\" কে ব্লক মুক্ত করা হয়েছে + \"%1$s\"-কে ব্লক করা হয়েছে। + \"%1$s\"-কে ব্লক করা যায়নি + \"%1$s\"-কে আনব্লক করা হয়েছে। সদস্যদের পর্যালোচনা করুন @@ -3644,7 +3715,7 @@ আপনার কন্টাক্ট গ্রুপ থেকে সরিয়ে ফেলুন কন্টাক্ট হালনাগাদ - অবরূদ্ধ + ব্লক করুন মুছে ফেলুন সাম্প্রতিককালে তাদের প্রোফাইল নাম %1$s থেকে %2$s তে পরিবর্তন করেছেন @@ -3671,13 +3742,13 @@ আপনার ফোন নম্বর লিখুন অ্যাকাউন্ট মুছে ফেলুন আপনার অ্যাকাউন্টের তথ্য এবং প্রোফাইলের ছবি মুছে ফেলুন - আপনার সকল বার্তা মুছে ফেলুন + আপনার সকল ম্যাসেজ মুছে ফেলুন আপনার পেমেন্ট অ্যাকাউন্টের %1$s মুছুন কোনও দেশের কোড নির্দিষ্ট করা নেই কোনও নম্বর নির্দিষ্ট করা হয়নি আপনি যে ফোন নম্বরটি লিখেছেন তা আপনার অ্যাকাউন্টের সাথে মেলে না। আপনি আপনার অ্যাকাউন্ট মুছে ফেলার ব্যাপারে নিশ্চিত? - এটি আপনার Signal অ্যাকাউন্ট মুছে ফেলবে এবং অ্যাপ্লিকেশনটি রিসেট করবে। প্রক্রিয়াটি শেষ হওয়ার পরে অ্যাপটি বন্ধ হয়ে যাবে। + এটি আপনার Signal অ্যাকাউন্ট মুছে ফেলবে এবং অ্যাপ্লিকেশনটি রিসেট করবে। প্রক্রিয়াটি শেষ হওয়ার পর অ্যাপটি বন্ধ হয়ে যাবে। স্থানীয় তথ্য মুছতে ব্যর্থ হয়েছে। আপনি এটিকে ম্যানুয়ালি সিস্টেম অ্যাপ্লিকেশন সেটিংস থেকে পরিষ্কার করতে পারবেন। অ্যাপ্লিকেশন সেটিংস চালু করুন @@ -3784,12 +3855,12 @@ ওয়ালেট বন্ধ করুন আপনার ব্যালেন্স - পেমেন্ট বন্ধ করার পূর্বে আপনাকে আপনার টাকা অন্য ওয়ালেটের ঠিকানায় পাঠানোর সুপারিশ করা হচ্ছে। আপনি এখন আপনার টাকা ট্রান্সফার করতে না চাইলে, আপনি পেমেন্ট পুনরায় চালু করলে সেগুলো আপনার Molly-এর সাথে লিংক করা ওয়ালেটেই থাকবে। + পেমেন্ট নিষ্ক্রিয় করার পূর্বে আপনাকে আপনার অর্থ অন্য ওয়ালেটের ঠিকানায় পাঠানোর জন্য পরামর্শ দেওয়া হচ্ছে। আপনি এখন আপনার অর্থ পাঠাতে না চাইলে, পেমেন্ট পুনরায় চালু করার পর সেগুলো আপনার Molly-এর সাথে লিংককৃত ওয়ালেটেই থাকবে। অবশিষ্ট ব্যালেন্স ট্রান্সফার করুন ট্রান্সফার না করেই বন্ধ করুন বন্ধ করুন ট্রান্সফার না করেই বন্ধ করবেন? - আপনি পেমেন্ট পুনঃসক্রিয় করা বেছে নিলে আপনার ব্যালান্স Molly-এর সাথে লিঙ্ক করা আপনার ওয়ালেটে থাকবে। + আপনি পেমেন্ট পুনরায় সক্রিয় করলে আপনার ব্যালান্স Molly-এর সাথে লিংককৃত ওয়ালেটে থাকবে। ওয়ালেট বন্ধ করতে ত্রুটি। @@ -4003,12 +4074,12 @@ প্রোফাইল তৈরি করুন - অবরূদ্ধ আছে + ব্লক করা হয়েছে %1$d পরিচিতিসমূহ বাদানুবাদ অদৃশ্য বার্তা অ্যাপ এর নিরাপত্তা - সাম্প্রতিক অ্যাপ তালিকা থেকে স্ক্রীনশট ব্লক করুন + রিসেন্ট লিস্ট এবং অ্যাপের মধ্যে স্ক্রীনশট ব্লক করুন Signal মেসেজ ও কল করে, সবসময় কল রিলে করে এবং প্রেরককে সিল করে রাখে নতুন চ্যাটের জন্য ডিফল্ট টাইমার আপনার শুরু করা সব নতুন চ্যাটের জন্য মেসেজ অদৃশ্য হওয়ার ডিফল্ট সময় সেট করুন। @@ -4165,7 +4236,7 @@ কল করুন - নিরব + মিউট করুন মিউট করা হয়েছে @@ -4175,9 +4246,9 @@ কন্ট্যাক্টের বিস্তারিত নিরাপত্তা নাম্বার দেখুন - অবরূদ্ধ + ব্লক করুন গ্রুপ ব্লক করুন - মূক্ত করুন + আনব্লক করুন গ্রুপ আনব্লক করুন গ্রুপে যুক্ত করো সব দেখুন @@ -4186,7 +4257,7 @@ অনুরোধ & আমন্ত্রণ গ্রুপের লিংক কন্ট্যাক্ট হিসেবে যোগ করুন - সশব্দ + আনমিউট করুন কথোপকথন %1$s পর্যন্ত মিউট করা হয়েছে কথোপকথন সবসময়ের জন্য মিউট করা হয়েছে ফোন নাম্বার ক্লিপবোর্ডে কপি করা হয়েছে। @@ -4204,8 +4275,8 @@ কারা বার্তা পাঠাতে পারবে? - নোটিফিকেশন নিঃশব্দ করুন - নিঃশব্দ নয় + নোটিফিকেশন মিউট করুন + মিউট করা হয়নি মেনশন সবসময় জানান জানাবেন না @@ -4330,7 +4401,7 @@ স্টোরি যোগ করুন একটি বার্তা যোগ করুন একটি জবাব যোগ করুন - প্রেরণ করুন + পাঠান একবার বার্তা দেখুন এক বা একাধিক আইটেম খুব বড় ছিল৷ এক বা একাধিক আইটেম বাতিল ছিল @@ -4453,7 +4524,7 @@ মাসিক ডোনেশন বাতিল করা হয়েছে আপনার বুস্ট ব্যাজের মেয়াদ শেষ হয়ে গেছে এবং আপনার প্রোফাইলে তা আর দৃশ্যমান নয়। - এককালীন অনুদানের মাধ্যমে আপনি আপনার বুস্ট ব্যাজকে আরও 30 দিনের জন্য পুনরায় সক্রিয় করতে পারেন। + এককালীন অনুদানের মাধ্যমে আপনি আপনার বুস্ট ব্যাজকে আরো 30 দিনের জন্য পুনরায় সক্রিয় করতে পারবেন। আপনি Signal ব্যবহার চালিয়ে যেতে পারেন তবে, আপনার জন্য তৈরি এই প্রযুক্তিকে সমর্থন করার জন্য, মাসিক ডোনেশন দিয়ে একজন সাসটেইনার হওয়ার কথা বিবেচনা করুন। একজন সাসটেইনার হোন @@ -4465,7 +4536,7 @@ আপনার পুনরাবৃত্ত মাসিক ডোনেশন বাতিল করা হয়েছে, কারণ আমরা আপনার পেমেন্টটি প্রক্রিয়া করতে পারিনি। আপনার ব্যাজটি আপনার প্রোফাইলে আর দৃশ্যমান নয়৷ আপনার পুনরাবৃত্ত মাসিক ডোনেশন বাতিল করা হয়েছে। %1$s আপনার %2$s ব্যাজ আপনার প্রোফাইলে আর দেখা যাবে না। - আপনি Signal ব্যবহার চালিয়ে যেতে পারেন তবে অ্যাপটিকে সমর্থন করতে এবং আপনার ব্যাজ পুনরায় সক্রিয় করতে, এখনই নবায়ন করুন। + আপনি Signal ব্যবহার চালিয়ে যেতে পারবেন, তবে অ্যাপটিকে সহায়তা করতে ও আপনার ব্যাজ পুনরায় সক্রিয় করতে, এখনই নবায়ন করুন। সাবস্ক্রিপশন নবায়ন করুন Google Pay-তে যান @@ -4508,7 +4579,7 @@ নেটওয়ার্ক ত্রুটির কারণে আপনার ডোনেশন পাঠানো যায়নি। আপনার নেটওয়ার্ক সংযোগ ঠিক আছে কি না দেখুন এবং আবার চেষ্টা করুন। - %1$s-কে ডোনেশন + %1$s-এর পক্ষ থেকে ডোনেশন আপনার পক্ষ হয়ে %1$s Signal-কে ডোনেট করেছেন @@ -4853,13 +4924,13 @@ আপনি এই স্টোরির উত্তর দিতে পারবেন না, কারণ আপনি আর এই গ্রুপের সদস্য নন। - স্টোরি-তে রিঅ্যাক্ট করা হয়েছে + স্টোরিতে রিঅ্যাক্ট করেছেন ভিউ রিপ্লাই - স্টোরি-তে রিঅ্যাক্ট করুন + এই স্টোরিতে রিঅ্যাক্ট করুন %1$s-কে ব্যক্তিগতভাবে রিপ্লাই করা হচ্ছে @@ -4905,11 +4976,11 @@ আপনার স্টোরি কে দেখতে পাবেন তা নির্ধারণ করুন৷ পরিবর্তনগুলো আপনার ইতোমধ্যে পাঠানো স্টোরিগুলোকে প্রভাবিত করবে না। - উত্তর & রিঅ্যাকশন + উত্তর ও প্রতিক্রিয়া - উত্তর & রিঅ্যাকশন অনুমোদন করুন + উত্তর ও প্রতিক্রিয়ার অনুমতি দিন - যারা আপনার স্টোরি দেখতে পাবেন তাদের রিঅ্যাক্ট এবং উত্তর দিতে দিন + আপনার স্টোরিতে কারা রিঅ্যাক্ট করতে ও উত্তর দিতে পারবেন তা নির্ধারণ করুন Signal কানেকশন @@ -5057,11 +5128,11 @@ - আপনি %1$s-এর স্টোরি-তে রিঅ্যাক্ট করেছেন + আপনি %1$s-এর স্টোরিতে রিঅ্যাক্ট করেছেন - আপনার স্টোরি-তে রিঅ্যাক্ট করেছেন + আপনার স্টোরিতে রিঅ্যাক্ট করেছেন - একটি স্টোরি-তে রিঅ্যাক্ট করেছেন + একটি স্টোরিতে রিঅ্যাক্ট করেছেন @@ -5087,7 +5158,7 @@ ডোনেশন নিশ্চিত করুন - প্রেরণ করুন + পাঠান প্রাপককে সরাসরি ম্যাসেজ পাঠানোর মাধ্যমে ডোনেশন সম্পর্কে জানানো হবে। নিচে আপনার নিজের ম্যাসেজ যোগ করুন। @@ -5322,7 +5393,7 @@ এসএমএস ম্যাসেজ এক্সপোর্ট করা হচ্ছে - কিছুটা সময় লাগতে পারে + এতে কিছুটা সময় লাগতে পারে %2$d-এর %1$d এক্সপোর্ট করা হচ্ছে… @@ -5601,5 +5672,15 @@ ব্যবহারকারীর নাম মুছে ফেলুন + + + ঘণ্টা + + মিনিট + + সেট করুন + + স্ক্রিন লক হওয়ার আগে সর্বনিম্ন সময় হল 1 মিনিট। + diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml index 754505bbcb..b3a76f9949 100644 --- a/app/src/main/res/values-bs/strings.xml +++ b/app/src/main/res/values-bs/strings.xml @@ -14,6 +14,7 @@ + Da Ne @@ -98,7 +99,7 @@ Blokirani korisnici Dodaj blokiranog korisnika - Blokirani korisnici neće biti u mogućnosti da Vas nazovu ili Vam pošalju poruku. + Blokirani korisnici neće vas moći zvati ili slati vam poruke. Nema blokiranih korisnika Blokirati korisnika? \"%1$s\" neće Vas moći nazvati ili Vam slati poruke. @@ -147,13 +148,13 @@ Moći ćete razmjenjivati poruke i pozive, a sa sagovornikom će biti podijeljeni i Vaše ime i slika. Moći ćete razmjenjivati poruke. - Blokirane osobe neće Vas više moći zvati niti Vam slati poruke. - Blokirane osobe više neće moći slati Vam poruke. + Blokirane osobe neće vas moći zvati ili slati vam poruke. + Blokirane osobe vam više neće moći slati poruke. - Blokiraj primanje ažuriranja Signala i novosti. + Blokiraj primanje ažuriranja i novosti za Signal. Nastavi primati ažuriranja Signala i novosti. - Deblokirati %1$s? + Odblokirati %1$s? Blokiraj Blokiraj i napusti Prijavi neželjenu poruku i blokiraj @@ -318,7 +319,7 @@ Signal poruka Pređimo na Molly %1$s Molimo odaberite kontakt - Deblokiraj + Odblokiraj Veličina priloga veća je od dopuštene za vrstu poruke koju šaljete. Nije moguće snimiti zvuk! Ne možete slati poruke ovoj grupi jer više niste njen član. @@ -467,7 +468,7 @@ Prekini - Blokirani + Blokirano Obriši filter @@ -582,6 +583,15 @@ +%1$d + + Ponovo povežite uređaje + + Veza s uređajima koje ste dodali je prekinuta kada je vaš uređaj odjavljen. Idite u Postavke da ponovo povežete bilo koji uređaj. + + Otvori postavke + + Kasnije + Izaberite članove @@ -1011,7 +1021,7 @@ Obavijesti me kad me neko spomene - Primiti obavještenja kad Vas neko spomene u prigušenim konverzacijama? + Želite li primati obavijesti kad vas neko spomene u chatovima s isključenim zvukom? Uvijek me obavijesti Ne obavještavaj me @@ -1029,6 +1039,16 @@ Korisničko ime je kreirano Korisničko ime je kopirano + + Brisanje korisničkog imena nije uspjelo. Pokušajte ponovo kasnije. + + Korisničko ime je izbrisano + + + + Nešto nije u redu s vašim korisničkim imenom, više nije dodijeljeno vašem računu. Možete ga pokušati ponovo postaviti ili odabrati novo. + + Popravi sada @@ -1252,8 +1272,8 @@ Nova grupa Pozovi prijatelje Koristi SMS - Izgled - Dodaj fotografiju + Boja razgovora + Postavite sliku profila Odgovori @@ -1585,13 +1605,13 @@ Nastavi Izbriši Blokiraj - Deblokiraj + Odblokiraj Dopustiti da Vam %1$s šalje poruke i vidi Vaše ime i sliku? On ili ona neće znati da ste vidjeli njihove poruke dok ne prihvatite. - Dopustiti da Vam %1$s šalje poruke i vidi Vaše ime i sliku? Nećete primati poruke od njega ili nje dok ih ne prestanete blokirati. + Želite li dopustiti da vam %1$s šalje poruke i vidi vaše ime i sliku? Nećete primati poruke dok ih ne odblokirate. - Dopustiti da Vam %1$s šalje poruke? Nećete primati poruke od njega ili nje dok ih ne prestanete blokirati. - Primati ažuriranja i novosti od %1$s? Nećete primati nikakve novosti dok ovu osobu ne deblokirate. + Želite li dopustiti da vam %1$s šalje poruke? Nećete primati poruke dok ih ne prestanete blokirati. + Želite li primati ažuriranja i novosti od %1$s? Nećete primati nikakve novosti dok ovu osobu ne odblokirate. Nastaviti konverzaciju s ovom grupom i dopustiti njenim članovima da vide Vaše ime i sliku? Nadogradite ovu grupu da biste aktivirali mogućnosti poput @spomena i administracije grupe. Članovi koji nisu podijelili svoje ime ili sliku s grupom bit će pozvani da se pridruže. Ova grupa više se ne može koristiti jer je prevelika. Maksimum za grupu iznosi %1$d. @@ -1599,7 +1619,7 @@ Pristupiti ovoj grupi i dopustiti članovima da vide Vaše ime i sliku? Oni neće znati da ste vidjeli njihove poruke dok ne prihvatite. Pristupiti ovoj grupi i dopustiti članovima da vide Vaše ime i sliku? Nećete vidjeti njihove poruke dok ne prihvatite. Pristupiti ovoj grupi? Članovi neće znati da ste vidjeli njihove poruke dok ne prihvatite. - Prestati s blokiranjem ove grupe i dopustiti njenim članovima da vide Vaše ime i sliku? Nećete primati poruke dok je ne prestanete blokirati. + Želite li odblokirati ovu grupu i dopustiti njenim članovima da vide vaše ime i sliku? Nećete primati poruke dok je ne odblokirate. Pregled Član grupe %1$s @@ -1712,9 +1732,20 @@ Kreiraj novi PIN + + Pošaljite SMS kȏd + + Signal registracija - Potrebna vam je pomoć s ponovnom registracijom PIN-a za Android + + Vaš PIN je kȏd od %1$d+ znamenki koji ste kreirali i koji može biti numerički ili alfanumerički.\n\nAko se ne možete sjetiti PIN-a, možete kreirati novi. + + Ako se ne možete sjetiti PIN-a, možete kreirati novi. + + Nemate više pokušaja pogađanja PIN-a, ali još uvijek možete pristupiti svom Signal računu kreiranjem novog PIN-a. + Upozorenje - Ako isključite PIN, svi Vaši podaci bit će izbrisani kada se ponovo registrujete na Signal, izuzev ako ručno kreirate rezervnu kopiju i koristite je za povrat podataka. Nećete moći aktivirati zaključavanje registracije dok god je PIN isključen. + Ako isključite PIN, svi vaši podaci bit će izbrisani kada se ponovo registrujete na Signal, osim ako ručno kreirate sigurnosnu kopiju i koristite je za povrat podataka. Nećete moći aktivirati zaključavanje registracije dok god je PIN onemogućen. Isključi PIN @@ -1745,7 +1776,7 @@ Blokiraj - Deblokiraj + Odblokiraj @@ -1849,9 +1880,9 @@ Kamera - Isključi stišavanje + Uključi zvuk - Stišaj + Isključi zvuk Zvoni @@ -1871,7 +1902,7 @@ Nećete primiti njihov zvuk i sliku a ni oni neće primiti Vaše. Nije moguće primiti zvuk i sliku od %1$s Nije moguće primiti zvuk i sliku od %1$s - Mogući razlog jeste taj što nisu verifikovali promjenu Vašeg sigurnosnog broja, ili postoji problem s njihovim uređajem, ili su Vas blokirali. + Mogući razlog jeste taj što nisu potvrdili promjenu vašeg sigurnosnog broja, ili postoji problem s njihovim uređajem, ili su vas blokirali. Prevucite da biste vidjeli dijeljenje ekrana @@ -1908,11 +1939,18 @@ Signalu je potrebno dopuštenje da pristupi kontaktima i multimediji kako bi Vam pomogao u povezivanju s prijateljima i slanju poruka. Vaši su kontakti preneseni na Signal server putem Signalovog privatnog uvezivanja kontakata, što znači da su šifrirani s kraja na kraj i nisu nikada vidljivi Signal servisu. Signalu je potrebno dopuštenje da pristupi kontaktima kako bi Vam pomogao u povezivanju s prijateljima. Vaši su kontakti preneseni na Signal server putem Signalovog privatnog uvezivanja kontakata, što znači da su šifrirani s kraja na kraj i nisu nikada vidljivi Signal servisu. Pokušali ste previše puta registrovati ovaj broj. Molimo pokušajte ponovo kasnije. + + Previše puta ste pokušali registrirati ovaj broj. Pokušajte ponovo za %1$s. Povezivanje nije uspjelo. Molimo provjerite internet-konekciju i pokušajte ponovo. Nestandardni oblik broja Broj koji ste unijeli (%1$s) izgleda nije standardnog oblika.\n\nJeste li mislili na %2$s? Molly Android – oblik telefonskog broja + Zahtijevan poziv + + SMS je zatražen + + Zatražen je verifikacijski kȏd Preostao vam je %1$d korak da pošaljete izvještaj o greškama. Preostala su vam %1$d koraka da pošaljete izvještaj o greškama. @@ -1934,6 +1972,16 @@ Poziv Potvrdni kôd Ponovno pošalji kod + + Imate problema s registracijom? + + • Provjerite ima li vaš telefon mobilni signal za primanje SMS-a ili poziva \n • Potvrdite da možete primiti telefonski poziv na broj \n • Proverite jeste li ispravno unijeli broj telefona. + + Za više informacija slijedite ove korake za rješavanje problema ili kontaktirajte podršku + + ove korake za rješavanje problema + + Kontaktirajte podršku Uključiti zaključavanje registracije? @@ -2093,13 +2141,17 @@ Prihvatili ste značku - Reagirao/la %1$s je na vašu priču + Reagirao/la je sa %1$s na vašu priču - Reagirali ste %1$s na priču korisnika + Reagirali ste sa %1$s na priču korisnika Plaćanje Zakazana poruka + + Vaša historija razmjene poruka je spojena + + %1$s pripada korisniku/ci %2$s Nova verzija Mollya @@ -2174,7 +2226,7 @@ MMS poruka šifrirana je za nepostojeću sesiju - Stišaj obavještenja + Isključi zvuk obavještenja Unošenje u toku @@ -2231,14 +2283,16 @@ Nezaštićen SMS %1$s %2$s Kontakt - Reagovao/la je sa %1$s na: \"%2$s\". - Reagovao/la je sa %1$s na Vaš videozapis. - Reagovao/la je sa %1$s na Vašu sliku. - Reagovao/la je sa %1$s na Vaš GIF. - Reagovao/la je sa %1$s na Vašu datoteku. - Reagovao/la je sa %1$s na Vaš audiozapis. - Reagovao/la je sa %1$s na Vaš jednokratni zapis. - Reagovao/la je sa %1$s na Vašu naljepnicu. + Reagirao/la je sa %1$s na: \"%2$s\". + Reagirao/la je sa %1$s na vaš videozapis. + Reagirao/la je sa %1$s na vašu sliku. + Reagirao/la je sa %1$s na vaš GIF. + Reagirao/la je sa %1$s na vašu datoteku. + Reagirao/la je sa %1$s na vaš audiozapis. + Reagirao/la je sa %1$s na vaš jednokratni medijski zapis. + + Reagirao/la je sa %1$s na vaše plaćanje. + Reagirao/la je sa %1$s na vašu naljepnicu. Ova je poruka izbrisana. Isključiti obavještenja o tome da se kontakt prijavio na Signal? Možete ih ponovo aktivirati kroz Signal > Podešavanja > Obavještenja. @@ -2653,8 +2707,8 @@ Problem s isporukom - Poruka, naljepnica, reakcija ili potvrda čitanja od %1$s nije Vam mogla biti isporučena. Moguće je da ih pokušao/la poslati Vama direktno, ili kroz grupu. - Poruka, naljepnica, reakcija ili potvrda čitanja od %1$s nije Vam mogla biti isporučena. + Poruka, naljepnica, reakcija ili potvrda o čitanju od korisnika %1$s nije vam mogla biti isporučena. Moguće je da ih je korisnik pokušao poslati vama direktno, ili kroz grupu. + Poruka, naljepnica, reakcija ili potvrda o čitanju od korisnika %1$s nije vam mogla biti isporučena. Ime (obavezno) @@ -2801,10 +2855,10 @@ Koristi standardno Koristi korisničko - Stišaj na 1 sat - Stišaj na 8 sati - Stišaj na 1 dan - Stišaj na 7 dana + Isključi zvuk na 1 sat + Isključi zvuk na 8 sati + Isključi zvuk na 1 dan + Isključi zvuk na 7 dana Stalno Sistemske postavke @@ -2843,9 +2897,9 @@ Koristi fotografije iz adresara Prikazati fotografiju kontakta iz adresara, ako je dostupna. - Keep Muted Chats Archived + Razgovore isključenog zvuka ostavi u arhivi - Muted chats that are archived will remain archived when a new message arrives. + Razgovori isključenog zvuka koji su arhivirani ostat će arhivirani kada stigne nova poruka. Kreiraj pregled linkova Preuzeti pregled linkova do internet-stranica u porukama koje šaljete. Promijeni lozinku @@ -3139,7 +3193,7 @@ Isplaćeno Uplaćeno Plaćanje izvršeno %1$s - Broj bloka + Blokiraj broj Prenos @@ -3298,10 +3352,10 @@ - Isključi stišavanje + Uključi zvuk - Stišaj obavještenja + Isključi zvuk obavještenja Podešavanja grupe @@ -3471,6 +3525,8 @@ Unesite svoj PIN Unesite PIN koji ste kreirali za svoj račun. PIN nije isto što i SMS kōd za verifikaciju. + + Unesite PIN koji ste kreirali za svoj račun. Unesite alfanumerički PIN Unesite numerički PIN Netačan PIN. Pokušajte ponovo. @@ -3584,7 +3640,10 @@ Vaša sigurnosna kopija sadrži veoma veliku datoteku za koju nije moguće kreirati sigurnosnu kopiju. Izbrišite je i kreirajte novu sigurnosnu kopiju. Pritisnite da rasporedite rezervne kopije. Pogrešan broj? + Pozovi me (%1$02d:%2$02d) + + Ponovo pošalji kȏd ( %1$02d : %2$02d ) Kontaktirajte Signalovu podršku Signal registracija – potvrda kōda za Android Pogrešan kôd @@ -3592,6 +3651,18 @@ Nepoznato Vidi moj telefonski broj Pronađi me putem telefonskog broja + + Broj telefona + + Odaberite ko može vidjeti vaš broj telefona i ko vas može kontaktirati na Mollyu pomoću njega. + + Ko može vidjeti moj broj + + Niko neće vidjeti vaš broj telefona na Mollyu + + Ko me može pronaći po broju + + Vaš broj telefona bit će vidljiv ljudima i grupama kojima šaljete poruke. Ljudi koji imaju vaš broj u svojim kontaktima također će ga vidjeti na Mollyu. Svako Moji kontakti Niko @@ -3749,7 +3820,7 @@ Blokiraj - Deblokiraj + Odblokiraj Dodaj među kontakte Nije pronađena aplikacija koja može otvoriti kontakte. @@ -3802,7 +3873,7 @@ %1$s/%2$s \"%1$s\" je blokiran/a. - Neuspjelo blokiranje \"%1$s\" + Neuspjelo blokiranje korisnika \"%1$s\" \"%1$s\" je deblokiran. @@ -3867,7 +3938,7 @@ Nije naveden broj Broj telefona koji ste unijeli ne podudara se s onim na Vašem računu. Da li ste sigurni da želite izbrisati vaš račun? - Ovo će izbrisati Vaš Signal račun i poništiti sve ranije postavke aplikacije. Kad se proces okonča, aplikacija će se zatvoriti. + Ovo će izbrisati Vaš Signal račun i poništiti sve ranije postavke aplikacije. Kad se proces završi, aplikacija će se zatvoriti. Neuspjelo brisanje lokalnih podataka. Možete ih ručno izbrisati kroz sistemska podešavanja aplikacija. Pokreni podešavanja aplikacije @@ -3976,12 +4047,12 @@ Deaktivirajte novčanik Vaša sredstva - Preporučuje se da svoja sredstva prenesete na adresu drugog novčanika prije nego što deaktivirate plaćanja. Ako odlučite da nećete sada prenijeti sredstva, ona će ostati u Vašem novčaniku koji je povezan sa Mollyom i biti dostupna kad ponovo aktivirate plaćanja. + Preporučuje se da svoja sredstva prenesete na adresu drugog novčanika prije nego što deaktivirate plaćanja. Ako odlučite da nećete sada prenijeti sredstva, ona će ostati u vašem novčaniku koji je povezan sa Mollyom i biti dostupna kad ponovo aktivirate plaćanja. Prenesi ostatak sredstava Deaktiviraj bez prenosa sredstava Deaktiviraj Deaktivirati bez prenosa sredstava? - Vaša će sredstva ostati u Vašem novčaniku povezanom sa Mollyom, ukoliko odlučite ponovo aktivirati plaćanja. + Vaša će sredstva ostati u vašem novčaniku povezanom sa Mollyom, ako odlučite ponovo aktivirati plaćanja. Greška prilikom deaktiviranja novčanika. @@ -4197,12 +4268,12 @@ Kreiraj profil - Blokirani + Blokirano %1$d kontakata Poruke Nestajuće poruke Sigurnost aplikacije - Onemogući slikanje ekrana u popisu nedavno korištenih aplikacija i unutar Signala + Onemogući snimku zaslona u popisu nedavnih razgovora i unutar aplikacije Signal poruke i pozivi, preusmjeravanje poziva i zapečaćeni pošiljalac Predefinisani rok za nove konverzacije Podesite vrijeme za nestajanje poruka koje će se automatski primijeniti na sve nove konverzacije kada ih Vi započnete. @@ -4363,9 +4434,9 @@ Nazovi - Stišaj + Isključi zvuk - Stišano + Isključen zvuk Traži Nestajuće poruke @@ -4375,8 +4446,8 @@ Vidi sigurnosni broj Blokiraj Blokiraj grupu - Deblokiraj - Prestani s blokiranjem grupe + Odblokiraj + Odblokiraj grupu Uvrsti u grupu Vidi sve Uvrsti članove @@ -4384,9 +4455,9 @@ Zahtjevi i pozivnice Link za grupu Dodaj kao kontakt - Isključi stišavanje - Konverzacije stišane do %1$s - Konverzacije zastalno stišane + Uključi zvuk + Zvuk chata je isključen do %1$s + Zvuk chata je zauvijek isključen Telefonski broj kopiran je u međuspremnik. Broj telefona Nabavite značke za svoj profil tako što ćete podržati Signal. Pritisnite značku kako biste saznali više. @@ -4402,8 +4473,8 @@ Ko može slati poruke? - Stišaj obavještenja - Nije stišano + Isključi zvuk obavještenja + Zvuk nije isključen Spomen Uvijek obavijesti Ne obavještavaj @@ -4659,7 +4730,7 @@ Mjesečna donacija je otkazana Vaša značka Boost istekla je i više nije vidljiva na Vašem profilu. - Možete ponovo aktivirati svoju značku Boost na period od 30 dana tako što ćete izvršiti jednokratnu uplatu. + Možete ponovo aktivirati značku Boost na period od 30 dana tako što ćete izvršiti jednokratnu uplatu. Možete nastaviti s korištenjem Signala, ali kako biste podržali tehnologiju koja je kreirana zbog Vas razmislite o tome da postanete pokrovitelj kroz mjesečnu donaciju. Postanite pokrovitelj @@ -4671,7 +4742,7 @@ Vaša redovna mjesečna donacija otkazana je jer nismo mogli obraditi Vašu uplatu. Vaša značka više nije prikazana na Vašem profilu. Vaša ponavljajuća mjesečna donacija je otkazana. %1$s Vaša značka %2$s više nije vidljiva na profilu. - Možete nastaviti s korištenjem Signala, ali ako želite pružiti podršku aplikaciji i ponovo aktivirati svoju značku, obnovite pretplatu. + Možete nastaviti s korištenjem Signala, ali ako želite pružiti podršku aplikaciji i ponovo aktivirati značku, obnovite pretplatu. Obnovi pretplatu Idi na Google Pay @@ -4714,7 +4785,7 @@ Slanje vaše donacije nije uspjelo zbog greške mreže. Provjerite svoju vezu i pokušajte ponovno. - Donacija korisniku/ci %1$s + Donacija u ime korisnika %1$s %1$s je donirao/la Signalu u vaše ime @@ -5065,13 +5136,13 @@ Ne možete odgovoriti na ovu priču jer više niste član ove grupe. - Reagovao/la je na priču + Reagirao/la je na priču Pregledi Odgovori - Reaguj na ovu priču + Reagiraj na ovu priču Odgovoriti privatno %1$s @@ -5127,7 +5198,7 @@ Dopusti odgovore i reakcije - Dopustite osobama koje mogu vidjeti vašu priču da reaguju i odgovore + Dopustite osobama koje mogu vidjeti vašu priču da reagiraju i odgovore Signal veze @@ -5277,11 +5348,11 @@ - Reagovali ste na priču koju je objavio/la %1$s + Reagirali ste na priču koju je objavio/la %1$s - Reagovao/la je na vašu priču + Reagirao/la je na vašu priču - Reagovao/la je na priču + Reagirao/la je na priču @@ -5851,5 +5922,15 @@ Brisanje korisničkog imena + + + h + + m + + Podesi + + Minimalno vrijeme prije zaključavanja ekrana je 1 minuta. + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a3861ec838..662a899b50 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -14,13 +14,14 @@ + No Esborra Espereu, si us plau… Desa - Notifica-m\'ho + Notes personals @@ -96,13 +97,13 @@ Es comprova si hi ha missatges… - Usuaris blocats - Afegeix un usuari blocat - Els usuaris blocats no podran trucar-vos ni enviar-vos missatges. - Sense usuaris blocats - Voleu blocar l\'usuari? + Usuaris bloquejats + Afegeix un usuari bloquejat + Els usuaris bloquejats no podran trucar-te ni enviar-te missatges. + Sense usuaris bloquejats + Vols bloquejar l\'usuari? %1$s no us podrà trucar ni enviar missatges. - Bloca + Bloquejar @@ -138,8 +139,8 @@ Continuar - Voleu blocar i abandonar %1$s? - Voleu blocar %1$s? + Vols bloquejar i abandonar %1$s? + Vols bloquejar %1$s? Ja no rebreu cap més missatge ni novetat d\'aquest grup i els membres no us hi podran tornar a afegir. Els membres del grup no us hi podran tornar a afegir. Els membres del grup us podran afegir a aquest grup un altre cop. @@ -147,16 +148,16 @@ Us podreu enviar missatges i fer trucades, i compartireu el nom i la fotografia. Podreu enviar-vos missatges mútuament. - Les persones blocades no us podran trucar ni enviar missatges. - Les persones blocades no us podran enviar missatges. + Les persones bloquejades no et podran trucar ni enviar missatges. + Les persones bloquejades no et podran enviar missatges. - Bloca rebre actualitzacions i notícies del Signal. + Bloqueja rebre actualitzacions i notícies del Signal. Continua rebent actualitzacions i notícies del Signal. - Voleu desblocar %1$s? - Bloca - Bloca i abandona - Informa\'n com a brossa i bloca-ho + Vols desbloquejar %1$s? + Bloquejar + Bloqueja i abandona + Informa\'n com a spam i bloqueja-ho Avui @@ -318,7 +319,7 @@ Missatge del Signal Canviem al Molly, %1$s Trieu un contacte - Desbloca + Desbloquejar El fitxer adjunt excedeix la mida màxima per a aquest tipus de missatges. No s\'ha pogut enregistrar l\'àudio. No podeu enviar missatges a aquest grup perquè ja no en sou membre. @@ -366,7 +367,7 @@ Error en enviar contingut - Se n\'ha informat com a brossa i s\'ha blocat. + Se n\'ha informat com a spam i s\'ha bloquejat. La missatjería SMS està desactivada. Pots exportar els teus missatges a una altra app del teu telèfon. @@ -451,11 +452,11 @@ %1$s no es podrà afegir ni sol·licitar-ho a aquest grup mitjançant l\'enllaç de grup. Encara es poden afegir al grup manualment. - Bloca la sol·licitud + Bloqueja la sol·licitud Cancel·la - Blocat + Bloquejat Restablir filtres @@ -488,33 +489,33 @@ No llegits - Fixat - Fixats + Fixar + Fixar No fixat No fixats - Silenciat - Silenciats + Silenciar + Silenciar - No silenciat - No silenciats + No silenciar + No silenciar Selecció - Arxivat - Arxivats + Arxivar + Arxivar - No arxivat - No arxivats + Desarxivar + Desarxivar - Suprimit - Suprimits + Eliminar + Eliminar Selecciona-ho tot @@ -542,6 +543,15 @@ +%1$d + + Torna a enllaçar els teus dispositius + + Els dispositius que has afegit s\'han desenllaçat quan el teu dispositiu no estava registrat. Ves a Ajustos per tornar a enllaçar qualsevol dispositiu. + + Obre els ajustos + + Després + Seleccioneu membres @@ -935,7 +945,7 @@ Notifica\'m les mencions - Voleu rebre notificacions quan us mencionin en converses silenciades? + Vols rebre notificacions quan se\'t mencioni en converses silenciades? Notifica-m\'ho sempre No m\'ho notifiquis mai @@ -953,6 +963,16 @@ S\'ha creat el nom d\'usuari S\'ha copiat el nom d\'usuari + + No s\'ha pogut eliminar l\'àlies. Torna-ho a provar més tard. + + Àlies eliminat + + + + S\'ha produït un error amb el teu àlies, ja no està assignat al teu compte. Pots provar de configurar-lo de nou o triar-ne un de diferent. + + Reparar-ho ara @@ -1156,8 +1176,8 @@ Grup nou Convideu-hi amistats Usa SMS - Aparença - Afegiu-hi una fotografia + Colors del xat + Afegeix una fotografia de perfil Respostes @@ -1468,14 +1488,14 @@ Ho accepto Continua Esborra - Bloca - Desbloca + Bloquejar + Desbloquejar Voleu permetre que %1$s us envii missatges i compartir-hi el nom i la fotografia? No sabran que heu vist el missatge fins que no ho accepteu. - Voleu permetre que %1$s us envii missatges i compartir-hi el nom i la fotografia? No rebreu cap missatge fins que no el desbloqueu. + Vols permetre que %1$s t\'enviï missatges i compartir-hi el nom i la fotografia? No rebràs cap missatge fins que no el desbloquegis. - Voleu permetre que %1$s us enviï missatges? No en rebreu cap missatge fins que no el desbloqueu. - Voleu rebre actualitzacions i notícies de %1$s? No en rebreu cap actualització fins que no les desbloqueu. + Vols permetre que %1$s t\'enviï missatges? No en rebràs cap missatge fins que no el desbloquegis. + Vols rebre actualitzacions i notícies de %1$s? No en rebràs cap actualització fins que no les desbloquegis. Voleu continuar la conversa amb aquest grup i compartir-hi el nom i la fotografia? Actualitzeu aquest grup i activeu-ne les funcions noves com ara les @mencions i els administradors. Els membres que no hi han compartit el nom o la fotografia es convidaran a afegir-s\'hi. Aquest grup de llegat ja no es pot usar perquè és massa gros. La mida màxima d\'un grup és %1$d. @@ -1483,7 +1503,7 @@ Voleu afegir-vos a aquest grup i compartir el nom i la fotografia amb els seus membres? No sabran que heu vist els seus missatges fins que no ho accepteu. Voleu afegir-vos a aquest grup i compartir el nom i la fotografia amb els seus membres? No veureu els seus missatges fins que no ho accepteu. Voleu afegir-vos a aquest grup? No sabran que n\'heu vist els missatges fins que no ho accepteu. - Voleu desblocar aquest grup i compartir el nom i la fotografia amb els seus membres? No rebreu cap missatge fins que no el desbloqueu. + Vols desbloquejar aquest grup i compartir el nom i la fotografia amb els seus membres? No rebràs cap missatge fins que no el desbloquegis. Mostra Membre de %1$s @@ -1584,6 +1604,17 @@ Crea un PIN nou + + Enviar codi per SMS + + Registre a Signal - Ajuda per tornar a registrar-se amb el PIN a Android + + El teu PIN és un codi creat per tu d\'almenys %1$d+ caràcters, i pot ser numèric o alfanumèric.\n\nSi no recordes el teu PIN, pots crear-ne un de nou. + + Si no recordes el teu PIN, pots crear-ne un de nou. + + T\'has quedat sense intents per introduir el PIN, però encara pots accedir al teu compte de Signal creant un PIN nou. + Advertiment Si desactiveu el PIN, perdreu totes les dades quan torneu a registrar-vos al Signal, si no és que en feu una còpia de seguretat i una restauració manualment. No podeu activar el bloqueig de registre mentre el PIN estigui desactivat. @@ -1616,8 +1647,8 @@ La meva història - Bloca - Desbloca + Bloquejar + Desbloquejar @@ -1726,12 +1757,12 @@ - %1$s està blocat. + %1$s està bloquejat. Més informació No en rebreu àudio ni vídeo ni rebran el vostre. No es pot rebre ni àudio ni vídeo de %1$s. No es pot rebre ni àudio ni vídeo de %1$s. - Això pot ser degut al fet que no han verificat el canvi del vostre número de seguretat, hi ha un problema al seu dispositiu o us han blocat. + Això pot ser degut al fet que no han verificat el canvi del teu número de seguretat, hi ha un problema al seu dispositiu o t\'han bloquejat. Llisqueu per veure la compartició de pantalla @@ -1768,11 +1799,18 @@ El Signal necessita els contactes i els permisos multimèdia per ajudar-vos a connectar-vos amb amics i enviar missatges. Els vostres contactes es carreguen mitjançant el descobriment de contactes privats del Signal, la qual cosa significa que estan encriptats d\'extrem a extrem i mai no són visibles per al servei de Signal. El Signal necessita el permís dels contactes per ajudar-vos a connectar-vos amb els amics. Els vostres contactes es carreguen mitjançant el descobriment de contactes privats del Signal, la qual cosa significa que estan encriptats d\'extrem a extrem i mai no són visibles per al servei de Signal. Heu fet massa intents per registrar aquest número. Si us plau, torneu-ho a provar més tard. + + Has fet massa intents per registrar aquest número. Si us plau, torna-ho a provar en %1$s. No es pot connectar al servei. Si us plau, comproveu la connexió de xarxa i torneu-ho a provar. Format de número no estàndard El número que heu introduït (%1$s) sembla d\'un format no estàndard.\n\nVoleu dir %2$s? Molly d\'Android. Format del número de telèfon + Trucada sol·licitada + + SMS sol·licitat + + Codi de verificació sol·licitat Ara us queda %1$d pas per enviar un informe de depuració. Ara us queden %1$d passos per enviar un informe de depuració. @@ -1792,6 +1830,16 @@ Truca Codi de verificació Torna a enviar el codi. + + Problemes per registrar-te? + + • Assegura\'t que el teu telèfon tingui senyal per rebre el teu SMS o trucada\n • Confirma que puguis rebre una trucada telefònica al número\n • Comprova que has introduït el teu número de telèfon correctament. + + Per obtenir més informació, segueix aquests passos de resolució de problemes o posa\'t en contacte amb el servei d\'assistència + + aquests passos de resolució de problemes + + Posar-se en contacte amb el servei d\'assistència Voleu activar el bloqueig de registre? @@ -1958,6 +2006,10 @@ Pagament Missatge programat + + El teu historial de missatges s\'ha combinat + + %1$s pertany a %2$s Actualització del Molly @@ -2087,14 +2139,16 @@ SMS no segur %1$s %2$s Contacte - %1$s ha reaccionat a : \"%2$s\". - %1$s ha reaccionat al vostre vídeo. - %1$s ha reaccionat al vostre missatge. - %1$s ha reaccionat al vostre GIF. - %1$s ha reaccionat al vostre fitxer. - %1$s ha reaccionat al vostre àudio. - %1$s ha reaccionat al vostre contingut d\'una visualització. - %1$s ha reaccionat al vostre adhesiu. + Ha reaccionat amb %1$s a: \"%2$s\". + Reacció al teu vídeo: %1$s + Reacció a la teva imatge: %1$s + Reacció al teu GIF: %1$s + Reacció al teu arxiu: %1$s + Reacció al teu àudio: %1$s. + Reacció al teu arxiu multimèdia d\'una visualització: %1$s. + + Ha reaccionat al teu pagament amb: %1$s + Reacció al teu sticker: %1$s. Aquest missatge s\'ha esborrat. Voleu desactivar les notificacions de contactes afegits al Signal? Les podeu tornar a activar a Signal > Configuració > Notificacions. @@ -2298,7 +2352,7 @@ Activa les notificacions de trucada Actualitza el contacte - Bloca la sol·licitud + Bloqueja la sol·licitud Cap grup en comú. Reviseu les sol·licituds amb atenció. Cap contacte en aquest grup. Reviseu les sol·licituds amb atenció. Vista @@ -2487,8 +2541,8 @@ Problema de lliurament - No se us ha pogut lliurar un missatge, adhesiu, reacció o rebut de lectura de %1$s. És possible que hagin intentat enviar-vos-ho directament o en grup. - No se us ha pogut lliurar un missatge, adhesiu, reacció o rebut de lectura de %1$s. + No se t\'ha pogut entregar un missatge, sticker, reacció o confirmació de lectura de %1$s. És possible que hagin intentat enviar-t\'ho directament o a través d\'un grup. + No se t\'ha pogut entregar un missatge, sticker, reacció o confirmació de lectura de %1$s. Nom (cal) @@ -2757,7 +2811,7 @@ Configuració avançada del PIN Missatges i trucades privades gratuïtes per als usuaris del Signal Envia un registre de depuració - Suprimeix el compte + Eliminar el compte Compatibilitat «Trucada per WiFi» Activeu-ho si el dispositiu fa servir SMS / MMS per WiFi (feu-ho només si la «Trucada per WiFi» està activada al dispositiu) Teclat d\'incògnit @@ -2971,7 +3025,7 @@ Pagament enviat Pagament rebut Pagament fet: %1$s - Número de bloc + Número de bloqueig Transferència @@ -3056,7 +3110,7 @@ Missatge nou per a… - Bloca l\'usuari + Bloqueja l\'usuari Afegeix-lo al grup @@ -3295,6 +3349,8 @@ Marqueu el PIN Marqueu el PIN que heu creat per al compte. Això és diferent del codi de verificació d\'SMS. + + Introdueix el PIN que has creat per al teu compte. Escriviu un PIN alfanumèric Marqueu un PIN numèric PIN incorrecte. Torneu-ho a provar. @@ -3398,7 +3454,10 @@ La teva còpia de seguretat conté un arxiu molt gran que no pot desar-se. Si us plau, suprimeix-lo i crea una nova còpia de seguretat. Toqueu per gestionar les còpies de seguretat. El número no és correcte? + Truca\'m (%1$02d:%2$02d) + + Torna a enviar el codi (%1$02d:%2$02d) Contacteu amb el suport de Signal Registre del Signal - Codi de verificació per a Android Codi incorrecte @@ -3406,6 +3465,18 @@ Desconegut Mostra el meu número de telèfon Troba\'m pel número de telèfon + + Número de telèfon + + Tria qui pot veure el teu número de telèfon i qui pot contactar-te a Molly a través d\'ell. + + Qui pot veure el meu número + + Ningú no veurà el teu número de telèfon a Molly + + Qui pot trobar-me a través del meu número + + El teu número de telèfon serà visible per a totes les persones i grups als quals enviïs un missatge. Les persones que tinguin el teu número als contactes del telèfon també et veuran a Molly. Tothom Els meus contactes Ningú @@ -3562,8 +3633,8 @@ - Bloca - Desbloca + Bloquejar + Desbloquejar Afegeix als contactes No trobo cap aplicació que pugui obrir contactes. @@ -3616,8 +3687,8 @@ %1$s/%2$s S\'ha blocat %1$s. - Ha fallat blocar %1$s. - S\'ha desblocat %1$s. + Ha fallat bloquejar %1$s. + S\'ha desbloquejat a %1$s. Reviseu els mebres @@ -3644,7 +3715,7 @@ Contacte Esborrar del grup Actualitza el contacte - Bloca + Bloquejar Esborrar Recentment han canviat el nom del perfil de %1$s a %2$s. @@ -3667,17 +3738,17 @@ La connexió Wi-Fi és feble. S\'ha canviat a dades del telèfon. - Suprimir el compte comportarà el següent: + Eliminar el compte comportarà el següent: Marqueu el número de telèfon. - Suprimeix el compte - Suprimeix la informació del compte i la fotografia del perfil. - Suprimeix tots els missatges. + Eliminar el compte + Eliminar la informació del compte i la fotografia del perfil. + Eliminar tots els missatges Suprimeix %1$s del compte de pagaments No s\'ha especificat el codi del país. No s\'ha especificat el número. El número de telèfon marcat no coincideix amb el del compte. Segur que vols suprimir el compte? - Això suprimirà el compte del Signal i restablirà l\'aplicació. L\'aplicació es tancarà quan acabi el procés. + Això eliminarà el compte del Signal i restablirà l\'aplicació. L\'aplicació es tancarà quan acabi el procés. No s\'han pogut suprimir les dades locals. Les podeu suprimir manualment des de la configuració del sistema. Obre la configuració de l\'aplicació @@ -3784,12 +3855,12 @@ Desactiva la cartera El saldo - Es recomana transferir els fons a una altra adreça de cartera abans de desactivar els pagaments. Si decidiu no transferir els fons ara, quedaran a la cartera enllaçada al Molly si reactiveu els pagaments. + Es recomana transferir els fons a una altra adreça de cartera abans de desactivar els pagaments. Si decideixes no transferir els fons ara, quedaran a la cartera enllaçada al Molly si reactives els pagaments. Transfereix el saldo restant Desactiveu-ho sense transferència Desactiva\'ls Voleu desactivar els fons sense transferir-los? - El saldo es mantindrà a la cartera enllaçada amb el Molly si decidiu reactivar els pagaments. + El saldo es mantindrà a la cartera enllaçada amb Molly si decideixes reactivar els pagaments. Error en desactivar la cartera @@ -4003,12 +4074,12 @@ Crea un perfil - Blocat + Bloquejat %1$d contactes Missatges Missatges efímers Seguretat de l\'aplicació - Bloca les captures de pantalla a les llistes de recents i dins de l\'aplicació + Bloqueja les captures de pantalla a les llistes de recents i dins de l\'aplicació Missatges i trucades del Signal: sempre retransmissió de trucades i remitent segellat Temporitzador per defecte per a les converses noves Establiu un temporitzador per defecte de desaparició de missatges per a totes les converses noves que hàgiu iniciat. @@ -4175,10 +4246,10 @@ Detalls del contacte Mostra el número de seguretat - Bloca + Bloquejar Bloca el grup - Desbloca - Desbloca el grup + Desbloquejar + Desbloquejar el grup Afegeix-lo a un grup Mostra-ho tot Afegeix-hi membres @@ -4234,7 +4305,7 @@ Suprimeix - Bloca + Bloquejar Vols suprimir %1$s? @@ -4330,7 +4401,7 @@ Afegeix-ho a la història Afegiu-hi un missatge Afegiu-hi una resposta - Envia a + Enviar a Mostra el missatge un cop Un o més elements eren massa grossos. Un o més elements no eren vàlids. @@ -4508,7 +4579,7 @@ No s\'ha pogut enviar la teva donació a causa d\'un error de la xarxa. Comprova la connexió i torna-ho a provar. - Donació per %1$s + Donació en nom de %1$s %1$s ha fet una donació a Signal en nom teu @@ -4859,7 +4930,7 @@ Respostes - Reaccioneu a aquesta història + Reaccionar a aquesta història Es respon privadament a %1$s @@ -4907,9 +4978,9 @@ Respostes i reaccions - Permet respostes i reaccions + Permetre respostes i reaccions - Permet que les persones que puguin veure la història hi reaccionin i hi responguin. + Permet que les persones que poden veure la història hi reaccionin i hi responguin. Contactes de Signal @@ -5061,7 +5132,7 @@ Ha reaccionat a la teva història - Ha reaccionat a una història. + Ha reaccionat a una història @@ -5087,7 +5158,7 @@ Confirmar la donació - Envia a + Enviar a El destinatari serà notificat de la donació en un missatge privat. Afegeix el teu missatge personalitzat a continuació. @@ -5601,5 +5672,15 @@ Suprimir àlies + + + h + + min + + Estableix + + El temps mínim abans que s\'apliqui el bloqueig de pantalla és d\'1 minut. + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3d4fbda25d..0b56e2bbec 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -14,13 +14,14 @@ + Ano Ne Odstranit Prosím čekejte… Uložit - = Poznámka pro mne = + Poznámka pro sebe @@ -96,13 +97,13 @@ Kontroluji zprávy… - Zablokovaní uživatelé + Blokovaní uživatelé Přidat blokovaného uživatele - Zablokovaní uživatelé vám nebudou moci zavolat nebo posílat zprávy. - Žádní zablokovaní uživatelé - Zablokovat uživatele? + Blokovaní uživatelé vám nebudou moci volat ani posílat zprávy. + Žádní blokovaní uživatelé + Blokovat uživatele? \"%1$s\" vám nebude moci volat nebo posílat zprávy. - Zablokovat + Blokovat @@ -138,8 +139,8 @@ Pokračovat - Zablokovat a opustit %1$s? - Zablokovat %1$s? + Blokovat a opustit %1$s? + Blokovat %1$s? Od této skupiny již nebudete dostávat zprávy ani aktualizace a členové vás do této skupiny už nebudou moci znovu přidat. Členové skupiny vás nebudou moci znovu do této skupiny přidat. Členové skupiny vás budou moci znovu přidat do této skupiny. @@ -147,16 +148,16 @@ Budete si schopni vzájemně posílat zprávy a volat a bude s nimi sdíleno vaše jméno a fotografie. Budete si moci vzájemně posílat zprávy a volat. - Zablokované osoby vám nebudou moci volat ani posílat zprávy. - Zablokované osoby vám nebudou moci posílat zprávy. + Blokované osoby vám nebudou moci volat ani posílat zprávy. + Blokované osoby vám nebudou moci posílat zprávy. - Blokovat novinky a aktualizace aplikace Signal. + Blokovat přijímání aktualizací a novinek ze služby Signal. Odblokovat novinky a aktualizace aplikace Signal. - Odblokovat %1$s? - Zablokovat - Zablokovat a odejít - Nahlásit spam a zablokovat + Odblokovat: %1$s? + Blokovat + Blokovat a odejít + Nahlásit spam a zablokovat Dnes @@ -366,7 +367,7 @@ Chyba při odesílání médií - Nahlášeno jako spam a zablokováno + Nahlášeno jako spam a zablokováno. Odesílání SMS zpráv není v současné době dostupné. Zprávy můžete exportovat do jiné aplikace v telefonu. @@ -459,15 +460,15 @@ %1$s zapnuto - Zablokovat žádost? + Blokovat žádost? %1$s se nebude moci připojit ani požádat o připojení k této skupině prostřednictvím odkazu na skupinu. Přesto jej/jí lze do skupiny přidat ručně. - Zablokovat žádost + Blokovat žádost Zrušit - Blokován(a) + Zablokováno Odebrat filtr @@ -530,10 +531,10 @@ Ztlumit - Zrušit ztlumení - Zrušit ztlumení - Zrušit ztlumení - Zrušit ztlumení + Zrušit ztišení + Zrušit ztišení + Zrušit ztišení + Zrušit ztišení Vybrat @@ -543,10 +544,10 @@ Archivovat - Nearchivovat - Nearchivovat - Nearchivovat - Nearchivovat + Zrušit archivaci + Zrušit archivaci + Zrušit archivaci + Zrušit archivaci Odstranit @@ -582,6 +583,15 @@ +%1$d + + Připojte znovu svá zařízení + + Zařízení, která jste přidali, byla při odhlášení vašeho zařízení odpojena. Přejděte do nastavení a znovu zařízení připojte. + + Otevřít nastavení + + Později + Vyberte členy @@ -1011,7 +1021,7 @@ Upozornit mne na zmínky - Zobrazit upozornění při zmínce ve ztišených konverzacích? + Přijímat oznámení, když jste zmíněn/a ve ztlumených konverzacích? Vždy mne upozornit Neupozorňovat mne @@ -1029,6 +1039,16 @@ Uživatelské jméno vytvořeno Uživatelské jméno zkopírováno + + Uživatelské jméno se nepodařilo odstranit. Zkuste to znovu později. + + Uživatelské jméno odstraněno + + + + Něco se pokazilo s vaším uživatelským jménem. Již není přiřazeno k vašemu účtu. Můžete ho zkusit nastavit znovu, nebo si zvolit nové. + + Opravit hned @@ -1252,8 +1272,8 @@ Nová skupina Pozvat přátele Použít SMS - Vzhled - Přidat fotku + Barvy konverzace + Přidat profilovou fotografii Odpovědi @@ -1584,14 +1604,14 @@ Přijmout Pokračovat Odstranit - Zablokovat + Blokovat Odblokovat Dovolit %1$s zasílat vám zprávy a sdílet vaše jméno a fotografii? Nebudou vědět, že jste viděl jejich zprávy, dokud je nepřijmete. - Dovolit %1$s zasílat vám zprávy a sdílet vaše jméno a fotografii? Dokud je neodblokujete, tak žádné zprávy neobdržíte. + Nechat si od %1$s posílat zprávy a sdílet s nimi vaše jméno a fotografii? Dokud je neodblokujete, nebudete dostávat žádné zprávy. - Povolit %1$s, aby vám napsal? Dokud je neodblokujete, nebudete dostávat žádné zprávy. - Získávat aktualizace a novinky od %1$s? Další aktualizace nedostanete, dokud je neodblokujete. + Nechat si od %1$s posílat zprávy? Dokud je neodblokujete, nebudete dostávat žádné zprávy. + Dostávat aktuality a novinky od %1$s? Dokud je neodblokujete, nebudete dostávat žádné zprávy. Pokračovat ve vaší konverzaci s touto skupinou a sdílet vaše jméno a fotografii s jejími členy? Pro aktivaci nových funkcí jako @zmínky a správci aktualizujte tuto skupinu. Členové, kteří dosud nesdílejí své jméno nebo fotografii v této skupině, budou pozváni, aby se připojili. Tuto starší verzi skupiny již nelze používat, protože je příliš velká. Maximální velikost skupiny je %1$d. @@ -1599,7 +1619,7 @@ Připojit se k této skupině a sdílet vaše jméno a fotografii s jejími členy? Nebudou vědět, že jste viděl jejich zprávy, dokud je nepřijmete. Přidat se k této skupině a sdílet své jméno a fotografii s jejími členy? Jejich zprávy uvidíte až po přijetí. Chcete se připojit k této skupině? Dokud tak neučiníte, členové se nedozví, že jste viděli jejich zprávy. - Odblokovat tuto skupinu a sdílet vaše jméno a fotografii s jejími členy? Dokud je neodblokujete, tak žádné zprávy neobdržíte. + Odblokovat tuto skupinu a sdílet své jméno a fotografii s jejími členy? Dokud ji neodblokujete, nebudete dostávat žádné zprávy. Zobrazit Člen %1$s @@ -1712,9 +1732,20 @@ Vytvořit nový PIN? + + Poslat SMS kód + + Registrace Signal – Potřebuji pomoct s opětovnou registrací PIN pro Android + + PIN je vámi vytvořený nejméně %1$dmístný kód, který může být numerický nebo alfanumerický.\n\nPokud si svůj PIN nepamatujete, můžete si vytvořit nový. + + Pokud si svůj PIN nepamatujete, můžete si vytvořit nový. + + Došly vám pokusy na zadání PIN, ale přístup ke svému účtu Signal můžete stále získat tak, že si vytvoříte nový PIN. + Varování - Pokud PIN deaktivujete, tak při opětovné registraci Signal ztratíte všechna data, jestliže si je ručně nezazálohujete a neobnovíte. Když je PIN deaktivován, nemůžete si zapnout zámek registrace. + Pokud deaktivujete PIN, ztratíte při opětovné registraci aplikace Signal všechna data, pokud je nezálohujete a neobnovíte ručně. S deaktivovaným PIN kódem nelze zapnout funkci zámek registrace. Deaktivovat PIN @@ -1744,7 +1775,7 @@ Můj příběh - Zablokovat + Blokovat Odblokovat @@ -1866,12 +1897,12 @@ - %1$s je zablokován + %1$s je blokován Více informací Nebudete přijímat jejich audio ani video a oni vaše. Nemohu přijmout ani audio ani video od %1$s Nemohu přijmout audio ani video od %1$s - K tomu může dojít v situaci, kdy jste neprovedli ověření bezpečnostního čísla, jejich zařízení je porouchané nebo vás zablokovali. + Důvodem může být to, že uživatel neověřil změnu vašeho bezpečnostního čísla, vyskytl se problém s jeho zařízením nebo vás zablokoval. Přejetím zobrazíte sdílení obrazovky @@ -1908,11 +1939,18 @@ Signal potřebuje oprávnění pro přístup ke kontaktům a médiím, abyste se mohli spojit s přáteli a posílat zprávy. Vaše kontakty jsou nahrávány pomocí soukromého zjišťování kontaktů služby Signal, což znamená, že jsou end-to-end šifrovány a pro Signal nejsou nikdy viditelné. Signal potřebuje oprávnění pro přístup ke kontaktům, abyste se mohli spojit s přáteli a posílat zprávy. Vaše kontakty jsou nahrávány pomocí soukromého zjišťování kontaktů služby Signal, což znamená, že jsou end-to-end šifrovány a pro Signal nejsou nikdy viditelné. Učinili jste příliš mnoho pokusů zaregistrovat toto číslo. Zkuste to prosím znovu později. + + Učinili jste příliš mnoho pokusů o registraci tohoto čísla. Zkuste to prosím znovu za %1$s. Nelze se připojit k službě. Prosím zkontrolujte připojení k internetu a poté to zkuste znovu. Nestandardní formát čísla Zadané číslo (%1$s) má zřejmě nestandardní formát.\n\nMysleli jste %2$s? Molly pro Android - Formát telefonního čísla + Vyžádané volání + + SMS vyžádána + + Ověřovací kód vyžádán Zbývá vám %1$d krok k odeslání ladícího logu. Zbývají vám %1$d kroky k odeslání ladícího logu. @@ -1934,6 +1972,16 @@ Volat Ověřovací kód Znovu odeslat kód + + Máte potíže s registrací? + + • Ujistěte se, že váš telefon má signál, abyste mohli přijímat SMS nebo volání\n • Potvrďte, že můžete na tomto čísle přijímat telefonní hovory\n • Zkontrolujte, že jste své telefonní číslo zadali správně. + + Další informace získáte podle těchto kroků pro řešení problémů, nebo kontaktujte podporu + + kroků pro řešení problémů + + Kontaktovat podporu Zapnout zámek registrace? @@ -2093,13 +2141,17 @@ Uplatnil/a jste odznak - Na váš příběh reagovali %1$s + Reagoval/a %1$s na váš příběh - Na jeho/její příběh reagovali %1$s + Reagoval/a %1$s na příběh Platba Naplánovaná zpráva + + Historie vašich zpráv byla sloučena + + %1$s patří uživateli %2$s Aktualizace Molly @@ -2174,7 +2226,7 @@ MMS zpráva byla zašifrována pro neexistující sezení - Ztišit upozornění + Vypnout oznámení Probíhá import @@ -2231,14 +2283,16 @@ Nezabezpečená SMS %1$s %2$s Kontakt - Reagoval(a) %1$s na: \"%2$s\". - Reagoval(a) %1$s na vaše video. - Reagoval(a) %1$s na váš obrázek. - Reagoval(a) %1$s na váš GIF. - Reagoval(a) %1$s na váš soubor. - Reagoval(a) %1$s na vaše audio. - Reagoval(a) %1$s na vaše média pro jednorázové zobrazení. - Reagoval(a) %1$s na vaši nálepku. + Reagoval/a %1$s na: „%2$s“. + Reagoval/a %1$s na vaše video. + Reagoval/a %1$s na váš obrázek. + Reagoval/a %1$s na váš GIF. + Reagoval/a %1$s na váš soubor. + Reagoval/a %1$s na vaše audio. + Reagoval/a %1$s na vaše média pro jednorázové zobrazení. + + Reagoval/a %1$s na vaši platbu. + Reagoval/a %1$s na vaši nálepku. Tato zpráva byla odstraněna. Vypnout upozornění, že se kontakt připojil k Signal? Můžete je znovu zapnout v Signal > Nastavení > Upozornění. @@ -2450,7 +2504,7 @@ Povolit oznámení hovorů Aktualizovat kontakt - Zablokovat žádost + Blokovat žádost Žádné společné skupiny. Požadavky pečlivě prověřte. Žádné kontakty v této skupině. Požadavky pečlivě prověřte. Zobrazit @@ -2653,8 +2707,8 @@ Potíže s doručením - Zpráva, nálepka, reakce nebo potvrzení o přečtení od %1$s vám nemohla být doručena. Možná se vám ji pokusili poslat přímo nebo ve skupině. - Zpráva, nálepka, reakce nebo potvrzení o přečtení od %1$s vám nemohla být doručena. + Zprávu, nálepku, reakci nebo potvrzení o přečtení od uživatele %1$s vám nebylo možné doručit. Pokusil se vám ji poslat přímo nebo ve skupině. + Zprávu, nálepku, reakci nebo potvrzení o přečtení od uživatele %1$s vám nebylo možné doručit. Křestní jméno (povinné) @@ -2721,7 +2775,7 @@ Probíhá - Odesláno pro + Odesláno uživateli Odesláno od Doručeno Přečteno @@ -2801,10 +2855,10 @@ Použít výchozí Použít vlastní - Ztišit na 1 hodinu - Ztlumit na 8 hodin - Ztišit na 1 den - Ztišit na 7 dnů + Vypnout na 1 hodinu + Vypnout na 8 hodin + Vypnout na 1 den + Vypnout na 7 dnů Vždy Výchozí nastavení @@ -2843,9 +2897,9 @@ Použít fotografii z adresáře Pokud je k dispozici, zobrazit fotografii kontaktu z adresáře - Zachování ztlumených konverzací v archivu + Ponechat ztlumené konverzace archivované - Ztlumené chaty, které jsou archivovány, zůstanou archivovány i po příchodu nové zprávy. + Ztlumené konverzace, které jsou archivovány, zůstanou archivovány i po příchodu nové zprávy. Vygenerovat náhledy odkazů Pro zprávy, které odesíláte, získat náhledy odkazů přímo z webů. Změnit heslo @@ -3139,7 +3193,7 @@ Odeslaná platba Přijatá platba Platba dokončena %1$s - Blokovat číslo + Číslo bloku Převést @@ -3224,7 +3278,7 @@ Nová zpráva pro… - Zablokovat uživatele + Blokovat uživatele Přidat do skupiny @@ -3298,10 +3352,10 @@ - Nahlas + Zrušit ztlumení - Ztišit upozornění + Vypnout oznámení Nastavení skupiny @@ -3471,6 +3525,8 @@ Zadejte váš PIN Zadejte PIN, který jste vytvořili pro váš účet. Je to jiný kód, než ten v ověřovací SMS. + + Zadejte PIN, který jste si vytvořili pro svůj účet. Zadejte alfanumerický PIN Zadejte číselný PIN Nesprávný PIN. Zkuste to znovu. @@ -3584,7 +3640,10 @@ Vaše záloha obsahuje příliš velký soubor, který nelze zálohovat. Odstraňte ho prosím a vytvořte novou zálohu. Klepněte pro práci se zálohami Špatné číslo? + Zavolejte mi (%1$02d:%2$02d) + + Znovu odeslat kód (%1$02d:%2$02d) Kontaktujte podporu Signal Registrace Signal - ověřovací kód pro Android Špatný kód @@ -3592,6 +3651,18 @@ Neznámý Zobrazit mé telefonní číslo Najít mě podle telefonního čísla + + Telefonní číslo + + Zvolte si, kdo uvidí vaše telefonní číslo a kdo vás pomocí něj může v aplikaci Molly kontaktovat. + + Kdo uvidí moje číslo + + Vaše telefonní číslo v aplikaci Molly nikdo neuvidí + + Kdo mě může najít podle čísla + + Vaše telefonní číslo bude viditelné pro všechny lidi a skupiny, kterým pošlete zprávu. Lidé, kteří mají vaše číslo ve svých telefonních kontaktech, jej uvidí i v aplikaci Molly. Všichni Moje kontakty Nikdo @@ -3748,7 +3819,7 @@ - Zablokovat + Blokovat Odblokovat Přidat do kontaktů @@ -3801,9 +3872,9 @@ %1$s/%2$s - \"%1$s\" byl zablokován. - Nepodařilo se zablokovat \"%1$s\" - \"%1$s\" byl odblokován. + Uživatel „%1$s“ byl zablokován. + Nepodařilo se zablokovat: „%1$s“ + Uživatel „%1$s“ byl odblokován. Prověřit členy @@ -3834,7 +3905,7 @@ Váš kontakt Odebrat ze skupiny Aktualizovat kontakt - Zablokovat + Blokovat Odstranit Nedávno změněno profilové jméno z %1$s na %2$s @@ -3857,11 +3928,11 @@ Slabá wi-fi. Přepnuto na mobilní data. - Vymazání vašeho účtu způsobí: + Smazáním účtu: Zadejte vaše tel. číslo Smazat účet - Smazat informace o účtu a profilovou fotografii - Odstranit všechny vaše zprávy + se odstraní informace o  vašem účtu a profilová fotografie + se odstraní všechny vaše zprávy Odstranit %1$s z vašeho platebního účtu Kód země nebyl zadán Číslo nebylo zadáno @@ -3976,12 +4047,12 @@ Deaktivovat peněženku Váš zůstatek - Před deaktivací plateb doporučujeme převést prostředky na jinou adresu peněženky. Pokud se rozhodnete nepřevést své prostředky nyní, zůstanou ve vaší peněžence propojené se Molly, pokud znovu aktivujete platby. + Před deaktivací plateb doporučujeme převést prostředky na jinou adresu peněženky. Pokud se rozhodnete své prostředky nyní nepřevádět, zůstanou ve vaší peněžence propojené se službou Molly, pokud platby znovu aktivujete. Převést zbývající zůstatek Deaktivovat bez převodu Deaktivovat Deaktivovat bez převodu? - Pokud se rozhodnete znovu aktivovat platby, váš zůstatek zůstane v peněžence propojené se Molly. + Pokud se rozhodnete platby znovu aktivovat, zůstanou vaše prostředky v peněžence propojené se službou Molly. Chyba při deaktivování peněženky. @@ -4197,12 +4268,12 @@ Vytvořit profil - Blokován(a) + Zablokováno %1$d kontakty Odesílání zpráv Mizející zprávy Zabezpečení aplikace - Zablokovat náhled v seznamu aplikací + Blokovat snímky obrazovky v seznamu posledních položek a uvnitř aplikace Signal zprávy a hovory, vždy předávat hovory, a utajený odesílatel. Výchozí časovač pro nové konverzace Nastavení výchozího časovače zmizení zprávy pro všechny vámi zahájené nové konverzace. @@ -4373,8 +4444,8 @@ Detaily kontaktu Zobrazit bezpečnostní číslo - Zablokovat - Zablokovat skupinu + Blokovat + Blokovat skupinu Odblokovat Odblokovat skupinu Přidat do skupiny @@ -4384,7 +4455,7 @@ Požadavky & pozvánky Odkaz skupiny Přidat jako kontakt - Nahlas + Zrušit ztlumení Konverzace ztlumena do %1$s Konverzace ztlumena navždy Telefonní číslo zkopírováno do schránky. @@ -4402,8 +4473,8 @@ Kdo může odesílat zprávy? - Ztišit upozornění - Není ztlumeno + Vypnout oznámení + Není vypnuto Zmínky Vždy poslat notifikaci Neupozorňovat @@ -4432,7 +4503,7 @@ Odstranit - Zablokovat + Blokovat Odebrat %1$s? @@ -4440,7 +4511,7 @@ %1$s byl/a odebrán/a - %1$s byl(a) zablokován(a) + Uživatel %1$s byl zablokován %1$s se nepodařilo odebrat @@ -4536,7 +4607,7 @@ Přidat do příběhu Přidat zprávu Přidat odpověď - Odeslat + Odeslat uživateli Zpráva pro jednorázové zobrazení Jeden nebo více souborů je moc velkých Jeden nebo více souborů je neplatných @@ -4659,7 +4730,7 @@ Měsíční příspěvek zrušen Platnost vašeho odznaku Boost vypršela a odznak již není a vašem profilu viditelný. - Odznak Boost si můžete znovu aktivovat na dalších 30 dní jednorázovým příspěvkem. + Jednorázovým příspěvkem si můžete odznak Boost znovu aktivovat na dalších 30 dní. Signál můžete používat i nadále, ale abyste podpořili technologii, která je vytvořena pro vás, zvažte zda se nestát stálým členem a přispívat měsíčně. Staňte se podporovatelem @@ -4671,7 +4742,7 @@ Váš pravidelný měsíční příspěvek byl zrušen, protože jsme nemohli zpracovat vaši platbu. Váš odznak již není na vašem profilu viditelný. Váš pravidelný měsíční finanční příspěvek byl zrušen. %1$s Váš odznak %2$s již není na vašem profilu viditelný. - Aplikaci Signal můžete používat i nadále, ale abyste ji podpořili a znovu aktivovali svůj odznak, obnovte nyní předplatné. + Signal můžete používat i nadále, ale chcete-li aplikaci podpořit a znovu aktivovat svůj odznak, obnovte nyní předplatné. Obnovit předplatné Jít do Google Pay @@ -4714,7 +4785,7 @@ Váš příspěvek nemohl být odeslán z důvodu chyby sítě. Zkontrolujte připojení k internetu a zkuste to znovu. - Příspěvek uživateli %1$s + Příspěvek jménem uživatele %1$s %1$s přispěl/a vaším jménem na aplikaci Signal @@ -5123,11 +5194,11 @@ Vyberte, kdo si může váš příběh zobrazit. Změny se nedotknou příběhů, které jste už sdíleli. - Odpovědi & reakce + Odpovědi a reakce - Povolit odpovědi & reakce + Povolit odpovědi a reakce - Nechte lidi, kteří si mohou váš příběh zobrazit, reagovat a odpovídat + Dovolte lidem, kteří si mohou zobrazit váš příběh, reagovat a odpovídat Spojení Signal @@ -5277,7 +5348,7 @@ - Reagovali jste na příběh od %1$s + Reagoval/a jste na příběh uživatele %1$s Reagoval/a na váš příběh @@ -5311,7 +5382,7 @@ Potvrdit příspěvek - Odeslat + Odeslat uživateli Příjemce bude o příspěvku informován v soukromé zprávě. Níže můžete přidat vlastní zprávu. @@ -5851,5 +5922,15 @@ Odstranit uživatelské jméno + + + hod. + + min. + + Nastavit + + Minimální doba, než se aktivuje zámek obrazovky, je 1 minuta. + diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 1bc323345e..3c279542f2 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -14,13 +14,14 @@ + Ja Nej Slet Vent venligst … Gem - Egen note + Personlig note @@ -150,7 +151,7 @@ Blokerede personer vil hverken kunne ringe eller sende beskeder til dig. Blokerede personer vil ikke kunne sende beskeder til dig. - Bloker modtagelse af Signal-opdateringer og nyheder. + Blokér modtagelse af Signal-opdateringer og nyheder. Genoptag modtagelse af Signal-opdateringer og nyheder. Fjern blokering af %1$s? @@ -447,11 +448,11 @@ %1$s aktiveret - Bloker anmodning? + Blokér anmodning? %1$s vil ikke kunne tilslutte sig eller anmode om at blive medlem af denne gruppe via gruppelinket. Vedkommende kan stadig tilføjes til gruppen manuelt. - Bloker anmodning + Blokér anmodning Annullér @@ -542,6 +543,15 @@ +%1$d + + Tilknyt dine enheder igen + + De enheder, du tilføjede, mistede forbindelse, da enhedens registrering blev fjernet. Gå til Indstillinger for at tilknytte enheder igen. + + Åbn indstillinger + + Senere + Vælg medlemmer @@ -600,7 +610,7 @@ Seneste sikkerhedskopi: %1$s Mappe til sikkerhedskopi - Tid til en backup + Tidspunkt for sikkerhedskopiering Verificer adgangssætning for sikkerhedskopi Test din adgangssætning for sikkerhedskopi og bekræft, at den matcher Aktiver @@ -935,7 +945,7 @@ Underret mig ved omtaler - Vil du modtage notifikationer, når du omtales i ignorerede samtaler? + Vil du modtage notifikationer, når du bliver tagget i ignorerede samtaler? Underret mig altid Underret mig ikke @@ -953,6 +963,16 @@ Brugernavn oprettet Brugernavn kopieret + + Kunne ikke slette brugernavn. Prøv igen senere. + + Brugernavn slettet + + + + Noget gik galt med dit brugernavn, det er ikke længere tildelt din konto. Du kan prøve at indstille det igen eller vælge et nyt. + + Få hjælp nu @@ -1156,8 +1176,8 @@ Ny gruppe Inviter venner Brug SMS - Udseende - Tilføj billede + Chatfarver + Tilføj et profilbillede Svar @@ -1469,12 +1489,12 @@ Fortsæt Slet Blokér - Ophæv blokering + Fjern blokering Lad %1$s sende dig beskeder og del dit navn og billede med vedkommende? De ved ikke, at du har set deres besked, før du accepterer. Lad %1$s sende dig beskeder og del dit navn og billede med vedkommende? Du modtager ikke nogen beskeder, før du fjerner blokeringen. - Vil du lade %1$s sende beskeder til dig? Du vil ingen beskeder modtage før blokeringen fjernes. + Vil du give %1$s tilladelse til at sende dig beskeder? Du vil ingen beskeder modtage før blokeringen fjernes. Få opdateringer og nyheder fra %1$s? Du vil ikke modtage opdateringer, før du ophæver din blokering. Fortsæt din samtale med gruppen, og del dit navn og billede med dens medlemmer? Opgrader gruppen for adgang til nye funktioner, som @omtaler og administrator. Medlemmer som ikke har delt deres navn eller billede i gruppen, vil blive inviteret til at deltage @@ -1584,9 +1604,20 @@ Opret ny pinkode + + Send sms-kode + + Tilmelding på Signal – brug for hjælp til at genregistrere pinkoden på Android + + Din pinkode er en numerisk eller alfanumerisk kode på %1$d+ cifre, som du har oprettet.\n\nHvis du ikke kan huske din pinkode, kan du oprette en ny. + + Hvis du ikke kan huske din pinkode, kan du oprette en ny. + + Du har ikke flere gæt, men du kan stadig få adgang til din Signal-konto ved at oprette en ny pinkode. + Advarsel - Hvis du deaktiverer pinkode mister du alle data, når du omregistrerer Signal. Medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret. + Hvis du deaktiverer pinkoden, mister du alle data, når du opretter Signal igen, medmindre du sikkerhedskopierer og gendanner manuelt. Du kan ikke aktivere registreringslås, mens pinkoden er deaktiveret. Deaktiver pinkode @@ -1731,7 +1762,7 @@ Du vil ikke modtage deres lyd eller billede og de vil ikke modtage dine. Kan ikke modtage lyd & video fra %1$s Kan ikke modtage lyd og video fra %1$s - Dette kan skyldes, at personen ikke har bekræftet ændringen i jeres sikkerhedsnummer, at der er et problem med deres enhed, eller at de har blokeret dig. + Dette kan skyldes at personen ikke har bekræftet ændringen i dit sikkerhedsnummer, at der er et problem med deres enhed eller at de har blokeret dig. Stryg for at få vist skærmdeling @@ -1768,11 +1799,18 @@ Signal har brug for tilladelse til at tilgå kontakter og lagerplads for at du kan oprette forbindelse til venner og sende beskeder. Dine kontakter uploades ved hjælp af Signals private kontaktopdagelse, hvilket betyder, at de er end-to-end-krypteret og aldrig synlige for Signal-tjenesten. Signal har brug for tilladelse til at tilgå kontakter for at du kan oprette forbindelse til venner. Dine kontakter uploades ved hjælp af Signals private kontaktopdagelse, hvilket betyder, at de er end-to-end-krypteret og aldrig synlige for Signal-tjenesten. Du har foretaget for mange forsøg på at registrere dette nummer. Prøv igen senere. + + Du har forsøgt at registrere dette nummer for mange gange. Prøv igen om %1$s. Ikke muligt at få forbindelse til service. Tjek venligst din netværksforbindelse og prøv igen. Ikke-standardiseret nummerformat Det nummer du har indtastet (%1$s) ser ud til at være et ikke-standardiseret format.\n\nMente du %2$s? Molly Android - Telefonnummerformat + Anmodet om opkald + + Sms anmodet + + Verifikationskode anmodet Du er nu kun %1$d trin fra at indsende en fejllog Du er nu kun %1$d trin fra at indsende en fejlsøgningslog. @@ -1792,6 +1830,16 @@ Ring op Verifikationskode Gensend kode + + Problemer ved tilmelding? + + • Sørg for, at din telefon har forbindelse, så du kan modtage din sms eller dit opkald\n • Bekræft, at du kan modtage et opkald på nummeret\n • Tjek, at du har angivet dit telefonnummer korrekt. + + Du kan få flere oplysninger ved at følge disse fejlfindingstrin eller kontakte support + + disse fejlfindingstrin + + Kontakt support Aktiver registreringslås? @@ -1958,6 +2006,10 @@ Betaling Planlagt meddelelse + + Din beskedhistorik er blevet slået sammen + + %1$s tilhører %2$s Opdater Molly @@ -2093,7 +2145,9 @@ Reagerede med %1$s på din GIF. Reagerede med %1$s på din fil. Reagerede med %1$s på din lydfil. - Reagerede med %1$s på din vis-én-gang mediefil. + Reagerede med %1$s på din engangsmediefil. + + Reagerede %1$s på din betaling. Reagerede med %1$s på dit klistermærke. Beskeden blev slettet. @@ -2298,7 +2352,7 @@ Aktiver opkaldsnotifikationer Opdatér kontakt - Bloker anmodning + Blokér anmodning Ingen grupper til fælles. Gennemgå anmodninger omhyggeligt. Ingen kontakter i gruppen. Gennemgå anmodninger omhyggeligt. Vis @@ -2487,8 +2541,8 @@ Leveringsproblem - En besked, klistermærke, reaktion eller læsekvittering kunne ikke leveres til dig fra %1$s. Vedkommende har muligvis forsøgt at sende direkte til dig eller i en gruppe. - En besked, klistermærke, reaktion eller læsekvittering kunne ikke leveres til dig fra %1$s. + En besked, klistermærke, reaktion eller kvittering for læsning kunne ikke leveres til dig fra %1$s. Vedkommende har muligvis forsøgt at sende direkte til dig eller i en gruppe. + En besked, klistermærke, reaktion eller kvittering for læsning kunne ikke leveres til dig fra %1$s. Fornavn (påkrævet) @@ -2635,7 +2689,7 @@ Brug standard Brug tilpasset - Ignorer 1 time + Ignorer i 1 time Ignorer i 8 timer Ignorer 1 dag Ignorer i 7 dage @@ -2971,7 +3025,7 @@ Sendt betaling Modtaget betaling Betaling afsluttet %1$s - Bloker nummer + Blokér nummer Overfør @@ -3295,6 +3349,8 @@ Indtast din pinkode Indtast den pinkode du oprettede til din konto. Den er forskellig fra din SMS-verifikationskode. + + Angiv den pinkode, du har oprettet til din konto. Indtast alfanumerisk pinkode Indtast numerisk pinkode Forkert pinkode. Prøv igen. @@ -3398,7 +3454,10 @@ Din seneste sikkerhedskopi indeholder en meget stor fil, som vi ikke kunne lave en sikkerhedskopi af. Slet filen, og opret en ny sikkerhedskopi. Tryk for at administrere sikkerhedskopier. Forkert nummer? + Ring til mig (%1$02d:%2$02d) + + Send kode igen (%1$02d:%2$02d) Kontakt Signal Support Signal-registrering - Verifikationskode for Android Forkert kode @@ -3406,6 +3465,18 @@ Ukendt Se mit telefonnummer Find mig via telefonnummer + + Telefonnummer + + Vælg, hvem der kan se dit telefonnummer, og hvem der kan kontakte dig på Molly med det. + + Personer, der kan se mit nummer + + Ingen kan se dit telefonnummer på Molly + + Personer, der kan finde mig med mit nummer + + Dit telefonnummer vil være synligt for personer og grupper, du sender beskeder til. Personer, der har dit nummer i deres telefonkontakter, vil også se det på Molly. Alle Mine kontakter Ingen @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" er blevet blokeret + \"%1$s\" er blevet blokeret. Fejl ved blokering af \"%1$s\" - \"%1$s\" er ikke længere blokeret + \"%1$s\" er ikke længere blokeret. Gennemgå medlemmer @@ -3677,7 +3748,7 @@ Intet nummer angivet Det indtastede telefonnummer matcher ikke med kontoen. Er du sikker på, du vil slette din konto? - Dette vil slette din Signal-konto og nulstille applikationen. Appen vil lukke når processen er færdig. + Dette vil slette din Signal-konto og nulstille applikationen. Appen vil lukke, når processen er færdig. Lokale data kunne ikke slettes. Du kan rydde det manuelt i systemets applikationsindstillinger. Åbn appindstillinger @@ -4008,7 +4079,7 @@ Beskeder Forsvindende beskeder App-sikkerhed - Forhindr, at der tages skærmbilleder i oversigten over senest anvendte apps og inde i Signal-appen på din egen enhed. + Bloker muligheden for skærmbilleder i oversigten over senest anvendte apps og i Signal-appen Signal-beskeder og opkald, videresend altid opkald og forseglet afsender. Standardudløbstid for nye samtaler Indstil en udløbstid for forsvindende beskeder, der skal gælde som standard for alle nye samtaler startet af dig. @@ -4178,7 +4249,7 @@ Blokér Blokér gruppe Fjern blokering - Ophæv blokering af gruppe + Fjern blokering af gruppe Tilføj til en gruppe Se alle Tilføj medlemmer @@ -4453,7 +4524,7 @@ Månedlig donation annulleret Din Boost-badge er udløbet, og er ikke længere synligt på din profil. - Du kan genaktivere din Boost-badge i yderligere 30 dage med et engangsbidrag. + Du kan genaktivere din Boost-badge i 30 dage med et engangsbidrag. Du kan fortsætte med at bruge Signal, men hvis du vil støtte teknologi, der er udviklet til dig, kan du overveje at støtte ved at lave en månedlig donation. Bliv bidragsyder @@ -4508,7 +4579,7 @@ Din donation kunne ikke sendes på grund af en netværksfejl. Tjek din forbindelse, og prøv igen. - Donation til %1$s + Donation på vegne af %1$s %1$s donerede til Signal på dine vegne @@ -4905,9 +4976,9 @@ Vælg, hvem der kan se din historie. Ændringer vil ikke påvirke historier, som du allerede har sendt. - Svar & reaktioner + Svar og reaktioner - Tillad svar & reaktioner + Tillad svar og reaktioner Lad folk, der kan se din historie, reagere og svare @@ -5601,5 +5672,15 @@ Slet brugernavn + + + t + + m + + Indstil + + Minimum tid inden skærmen låses er 1 minut. + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index aca41dfee0..acfada6af7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -14,6 +14,7 @@ + Ja Nein @@ -97,7 +98,7 @@ Blockierte Nutzer - Zu blockierenden Nutzer hinzufügen + Zu blockierten Nutzern hinzufügen Blockierte Nutzer werden dich nicht anrufen oder dir Nachrichten senden können. Keine blockierten Nutzer Nutzer blockieren? @@ -150,7 +151,7 @@ Blockierte Personen werden dich nicht anrufen oder dir Nachrichten senden können. Blockierte Personen können dir keine Nachrichten senden. - Signal-Versionshinweise und Neuigkeiten blockieren + Signal-Versionshinweise und Neuigkeiten blockieren. Signal-Versionshinweise und Neuigkeiten entsperren %1$s freigeben? @@ -421,7 +422,7 @@ Löschen Nachrichten werden gelöscht … Für mich löschen - Für jeden löschen + Für alle löschen Auf diesem Gerät löschen @@ -542,6 +543,15 @@ +%1$d + + Kopple deine Geräte erneut + + Die von dir hinzugefügten Geräte wurden entkoppelt, als dein Gerät abgemeldet wurde. Gehe zu Einstellungen, um alle Geräte erneut zu koppeln. + + Einstellungen öffnen + + Später + Mitglieder auswählen @@ -659,7 +669,7 @@ Geplant senden - Nachricht geplante senden + Geplante Nachricht senden Alle Uhrzeiten in (%1$s) %2$s @@ -953,6 +963,16 @@ Username erstellt Username kopiert + + Nutzername konnte nicht gelöscht werden. Versuche es später erneut. + + Nutzername gelöscht + + + + Mit deinem Nutzernamen ist etwas schief gelaufen, er ist deinem Konto nicht mehr zugeordnet. Du kannst versuchen, ihn erneut einzugeben oder einen neuen zu wählen. + + Jetzt beheben @@ -1156,8 +1176,8 @@ Neue Gruppe Freunde einladen SMS verwenden - Darstellung - Foto hinzufügen + Unterhaltungsfarben + Füge ein Profilbild hinzu Antworten @@ -1472,10 +1492,10 @@ Freigeben Darf %1$s dir Nachrichten schreiben und deinen Namen und dein Foto sehen? Der Nutzer weiß nicht, dass du seine Nachricht gesehen hast, bis du die Anfrage annimmst. - Darf %1$s dir Nachrichten schreiben und deinen Namen und dein Foto sehen? Du wirst keine Nachrichten erhalten, bis du die Unterhaltung freigibst. + Darf %1$s dir Nachrichten schreiben und deinen Namen und dein Foto sehen? Du wirst keine Nachrichten erhalten, es sei denn, du erteilst eine Freigabe. - Darf %1$s dir Nachrichten schreiben? Du wirst keine Nachrichten erhalten, solange du die Unterhaltung nicht freigibst. - Aktualisierungen und Neuigkeiten von %1$s erhalten? Du wirst keine Aktualisierungen erhalten, bis du sie freigibst. + Darf %1$s dir Nachrichten schreiben? Du wirst keine Nachrichten erhalten, es sei denn, du erteilst eine Freigabe. + Aktualisierungen und Neuigkeiten von %1$s erhalten? Du wirst keine Aktualisierungen erhalten, es sei denn, du erteilst eine Freigabe. Unterhaltung mit dieser Gruppe fortsetzen und deinen Namen und dein Foto mit deren Mitgliedern teilen? Aktualisiere diese Gruppe für neue Funktionen wie @Erwähnungen und Admins. Mitglieder, die ihre Namen und Fotos nicht mit dieser Gruppe geteilt haben, werden eingeladen, beizutreten. Diese Gruppe alten Typs kann nicht mehr verwendet werden. Die maximale Gruppengröße von %1$d Mitgliedern ist überschritten. @@ -1483,7 +1503,7 @@ Möchtest du dieser Gruppe beitreten und deinen Namen und dein Foto mit ihren Mitgliedern teilen? Diese wissen nicht, dass du ihre Nachrichten gesehen hast, bis du die Anfrage annimmst. Möchtest du dieser Gruppe beitreten und deinen Namen und dein Foto mit ihren Mitgliedern teilen? Du kannst deren Nachrichten nicht sehen, bis du die Anfrage annimmst. Dieser Gruppe beitreten? Solange du nicht beitrittst, werden die Gruppenmitglieder nicht wissen, dass du ihre Nachrichten gesehen hast. - Möchtest du diese Gruppe freigeben und deinen Namen und dein Foto mit deren Mitgliedern teilen? Du wirst keine Nachrichten erhalten, bis du die Gruppe freigibst. + Möchtest du diese Gruppe freigeben und deinen Namen und dein Foto mit deren Mitgliedern teilen? Du wirst keine Nachrichten erhalten, es sei denn, du erteilst eine Freigabe. Anzeigen Mitglied von %1$s @@ -1584,9 +1604,20 @@ Neue PIN erstellen + + SMS-Code senden + + Signal-Registrierung – ich benötige Hilfe bei der Neuregistrierung der PIN für Android + + Deine PIN ist ein von dir erstellter, numerischer oder alphanumerischer Code von mindestens %1$d Zeichen.\n\nWenn du dich nicht mehr an deine PIN erinnern kannst, kannst du eine neue erstellen. + + Wenn du dich nicht mehr an deine PIN erinnern kannst, kannst du eine neue erstellen. + + Die Anzahl der zu erratenden PINs ist erschöpft, aber du kannst immer noch auf dein Signal-Konto zugreifen, indem du eine neue PIN erstellst. + Warnung - Falls du deine PIN deaktivierst, wirst du bei erneutem Registrieren von Signal alle Daten verlieren, sofern du sie nicht manuell per Datensicherung sicherst und wiederherstellst. Bei deaktivierter PIN kannst du die Registrierungssperre nicht einschalten. + Falls du deine PIN deaktivierst, verlierst du bei erneutem Registrieren bei Signal alle Daten, sofern du sie nicht manuell per Datensicherung sicherst und wiederherstellst. Bei deaktivierter PIN kannst du die Registrierungssperre nicht einschalten. PIN deaktivieren @@ -1711,9 +1742,9 @@ Kamera - Stumm­schaltung aufheben + Stummschaltung aufheben - Stumm + Stumm­schalten Klingeln @@ -1726,12 +1757,12 @@ - »%1$s« ist blockiert + %1$s ist blockiert Mehr Details Weder du noch dieser Teilnehmer werden gegenseitig Audio- oder Videodaten empfangen. Audio- & Videodaten von %1$s können nicht empfangen werden Audio- und Videodaten von %1$s können nicht empfangen werden - Dies kann daran liegen, dass der Teilnehmer die Änderung deiner Sicherheitsnummer nicht verifiziert hat, ein Problem mit dessen Gerät besteht oder er dich blockiert. + Dies kann daran liegen, dass der Teilnehmer die Änderung deiner Sicherheitsnummer nicht verifiziert hat, ein Problem mit dessen Gerät besteht oder er dich blockiert hat. Wische, um Bildschirmfreigabe anzuzeigen @@ -1768,11 +1799,18 @@ Signal benötigt die Berechtigungen »Kontakte« und »Speicher«, um dir das Kontaktieren deiner Freunde und das Senden von Nachrichten zu erleichtern. Deine Kontakte werden mittels Signals vertraulicher Kontaktfindung auf die Signal-Server hochgeladen. Das heißt, sie sind Ende-zu-Ende-verschlüsselt und somit zu keiner Zeit dem Signal-Dienst bekannt. Signal benötigt die Berechtigung »Kontakte«, um dir das Kontaktieren deiner Freunde zu erleichtern. Deine Kontakte werden mittels Signals vertraulicher Kontaktfindung auf die Signal-Server hochgeladen. Das heißt, sie sind Ende-zu-Ende-verschlüsselt und somit zu keiner Zeit dem Signal-Dienst bekannt. Du hast zu oft versucht, diese Rufnummer zu registrieren. Bitte versuche es später erneut. + + Du hast zu oft versucht, diese Nummer zu registrieren. Bitte versuche es erneut in %1$s. Keine Verbindung zum Dienst möglich. Bitte überprüfe deine Netzverbindung und versuche es erneut. Nicht standardisiertes Zahlenformat Die eingegebene Rufnummer (%1$s) scheint nicht im Standardformat zu sein.\n\nHast du %2$s gemeint? Molly Android - Rufnummernformat + Anruf angefragt + + SMS angefordert + + Verifikationscode angefordert Nur noch %1$d Schritt, und du kannst ein Diagnoseprotokoll übermitteln. Nur noch %1$d Schritte, und du kannst ein Diagnoseprotokoll übermitteln. @@ -1792,6 +1830,16 @@ Mich anrufen Verifikationscode Code erneut senden + + Probleme bei der Anmeldung? + + • Vergewissere dich, dass dein Mobiltelefon ein Mobilfunksignal hat, um deine SMS oder deinen Anruf zu empfangen\n • Bestätige, dass du unter dieser Rufnummer einen Anruf empfangen kannst\n • Überprüfe, ob du deine Rufnummer richtig eingegeben hast. + + Für weitere Infos folge bitte diesen Schritten zur Fehlerbehebung oder kontaktiere den Support + + diesen Schritten zur Fehlerbehebung + + Support kontaktieren Registrierungssperre einschalten? @@ -1951,13 +1999,17 @@ Du hast ein Abzeichen eingelöst - Mit %1$s auf deine Story reagiert + Hat mit %1$s auf deine Story reagiert - Mit %1$s auf ihre*seine Story reagiert + Hat mit %1$s auf ihre*seine Story reagiert Zahlung Geplante Nachricht + + Dein Nachrichtenverlauf wurde zusammengeführt + + %1$s gehört zu %2$s Molly-Aktualisierung @@ -2087,14 +2139,16 @@ Unverschlüsselte SMS %1$s %2$s Kontakt - Mit %1$s reagiert auf: »%2$s«. - Mit %1$s auf dein Video reagiert. - Mit %1$s auf dein Bild reagiert. - Mit %1$s auf dein GIF reagiert. - Mit %1$s auf deine Datei reagiert. - Mit %1$s auf dein Audio reagiert. - Mit %1$s auf deine einmalig anzeigbaren Medieninhalte reagiert. - Mit %1$s auf deinen Sticker reagiert. + Hat mit %1$s auf »%2$s« reagiert. + Hat mit %1$s auf dein Video reagiert. + Hat mit %1$s auf dein Bild reagiert. + Hat mit %1$s auf dein GIF reagiert. + Hat mit %1$s auf deine Datei reagiert. + Hat mit %1$s auf dein Audio reagiert. + Hat mit %1$s auf deine einmalig anzeigbaren Medieninhalte reagiert. + + Hat mit %1$s auf deine Zahlung reagiert. + Hat mit %1$s auf deinen Sticker reagiert. Diese Nachricht wurde gelöscht. Benachrichtigungen über neue Signal-Kontakte ausschalten? Du kannst sie wieder einschalten in Signal → Einstellungen → Benachrichtigungen. @@ -2757,7 +2811,7 @@ Erweiterte PIN-Einstellungen Kostenlos und verschlüsselt mit Signal-Nutzern Nachrichten austauschen und telefonieren Diagnoseprotokoll übermitteln - Nutzerkonto löschen + Dein Konto löschen Kompatibilität WLAN-Telefonie Aktivieren, falls das Gerät SMS/MMS über WLAN nutzt (nur bei aktivierter WLAN-Telefonie) Inkognito-Tastatur @@ -3295,6 +3349,8 @@ PIN eingeben Gib die PIN ein, die du für dein Konto erstellt hast. Sie unterscheidet sich von deinem SMS-Verifikationscode. + + Gib die PIN ein, die du für dein Konto festgelegt hast. Alphanumerische PIN eingeben Numerische PIN eingeben Falsche PIN. Bitte versuche es erneut. @@ -3398,7 +3454,10 @@ Dein Backup enthält eine sehr große Datei, die nicht gesichert werden kann. Bitte lösche sie und erstelle ein neues Backup. Antippen zum Verwalten von Datensicherungen. Falsche Rufnummer? + Ruf mich an (%1$02d:%2$02d) + + Code erneut senden (%1$02d:%2$02d) Signal-Support kontaktieren Signal-Registrierung – Verifikationscode für Android Falscher Code @@ -3406,6 +3465,18 @@ Unbekannt meine Rufnummer sehen mich über die Rufnummer finden + + Rufnummer + + Lege fest, wer deine Rufnummer sehen kann und wer dich auf Molly mit ihr kontaktieren kann. + + Wer meine Rufnummer sehen kann + + Niemand wird deine Rufnummer in Molly sehen + + Wer mich anhand der Rufnummer finden kann + + Deine Rufnummer ist sichtbar für alle Personen und Gruppen, mit denen du dich unterhälst. Personen, die deine Rufnummer in ihren Telefonkontakten gespeichert haben, sehen diese auch in Molly. Jeder Meine Kontakte Niemand @@ -3667,9 +3738,9 @@ Schwaches WLAN-Signal. Zu mobile Daten gewechselt. - Das Löschen deines Nutzerkontos wird: + Das Löschen deines Kontos wird: Gib deine Rufnummer ein - Nutzerkonto löschen + Dein Konto löschen Deine Kontodetails und dein Profilfoto löschen Alle deine Nachrichten löschen Lösche %1$s in deinem Zahlungskonto @@ -4003,7 +4074,7 @@ Profil erstellen - Blockierte Nutzer + Blockiert %1$d Nutzer Nachrichtenübermittlung Verschwindende Nachrichten @@ -4508,7 +4579,7 @@ Deine Spende konnte aufgrund eines Netzwerkfehlers nicht gesendet werden. Überprüfe deine Verbindung und versuche es erneut. - Spende an %1$s + Spende im Namen von %1$s %1$s hat in deinem Namen an Signal gespendet @@ -4853,7 +4924,7 @@ Du kannst nicht auf diese Story antworten, weil du nicht mehr Mitglied in dieser Gruppe bist. - Auf Story reagiert + Hat auf Story reagiert Aufrufe @@ -4905,9 +4976,9 @@ Wähle aus, wer deine Story sehen kann. Änderungen wirken sich nicht auf bereits gesendete Storys aus. - Antworten & Reaktionen + Antworten und Reaktionen - Antworten & Reaktionen erlauben + Antworten und Reaktionen erlauben Erlaube Personen, die deine Story sehen können, auf sie zu antworten und zu reagieren @@ -5059,9 +5130,9 @@ Du hast auf die Story von %1$s reagiert - Auf deine Story reagiert + Hat auf deine Story reagiert - Auf eine Story reagiert + Hat auf eine Story reagiert @@ -5601,5 +5672,15 @@ Nutzername löschen + + + Std. + + Min. + + Festlegen + + Mindestzeit, bevor die Bildschirmsperre aktiviert wird, beträgt 1 Minute. + diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index cbd2e3c23f..7701d4402a 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -14,6 +14,7 @@ + Ναι Όχι @@ -96,11 +97,11 @@ Ελέγχουμε για μηνύματα… - Αποκλεισμένοι χρήστες/τριες - Προσθήκη αποκλεισμένου/ης χρήστη/τριας + Αποκλεισμένοι χρήστες + Προσθήκη αποκλεισμένου χρήστη Οι αποκλεισμένοι χρήστες δεν θα μπορούν να σε καλέσουν ή να σου στείλουν μηνύματα. - Δεν υπάρχουν αποκλεισμένοι χρήστες/τριες - Αποκλεισμός χρήστη/χρήστριας; + Δεν υπάρχουν αποκλεισμένοι χρήστες + Αποκλεισμός χρήστη; Ο/Η «%1$s» δεν θα μπορεί να σε καλέσει ή να σου στείλει μηνύματα. Αποκλεισμός @@ -148,9 +149,9 @@ Θα μπορείτε να ανταλλάσετε μηνύματα. Τα αποκλεισμένα άτομα δεν θα μπορούν να σε καλέσουν ή να σου στείλουν μηνύματα. - Τα μπλοκαρισμένα άτομα δεν θα μπορούν να σου στέλνουν μηνύματα. + Τα αποκλεισμένα άτομα δεν θα μπορούν να σου στέλνουν μηνύματα. - Φραγή της λήψης ενημερώσεων και νέων του Signal. + Αποκλεισμός της λήψης ενημερώσεων και νέων του Signal. Επαναφορά της λήψης ενημερώσεων και νέων του Signal. Κατάργηση αποκλεισμού του/της %1$s; @@ -447,15 +448,15 @@ %1$s ενεργό - Μπλοκάρισμα αιτήματος; + Αποκλεισμός αιτήματος; Ο/Η %1$s δεν θα μπορέσει να γίνει μέλος ή να ζητήσει να γίνει μέλος αυτής της ομάδας μέσω του συνδέσμου ομάδας. Θα μπορεί να προστεθεί παρόλα αυτά στην ομάδα χειροκίνητα. - Μπλοκάρισμα αιτήματος + Αποκλεισμός αιτήματος Ακύρωση - Λίστα αποκλεισμένων + Λίστα αποκλεισμού Διαγραφή φίλτρου @@ -480,12 +481,12 @@ %1$d συνομιλίες μεταφέρθηκαν στα εισερχόμενα - Σημ. αναγνωσμ. - Σήμ. ως αναγν. + Διαβάστηκε + Διαβάστηκαν - Σημ. μη αναγν. - Σήμ. μη αναγν. + Μη αναγνωσμένο + Μη αναγνωσμένα Καρφίτσωμα @@ -500,17 +501,17 @@ Σίγαση - Αποσίγαση - Διακοπή σίγασης + Αναίρεση σίγασης + Αναίρεση σίγασης Επιλογή - Αρχειοθ. + Αρχειοθέτηση Αρχειοθέτηση - Αποαρχειοθ. - Κατάργ. αρχ. + Κατάργηση αρχειοθέτησης + Κατάργηση αρχειοθέτησης Διαγραφή @@ -542,6 +543,15 @@ +%1$d + + Σύνδεσε ξανά τις συσκευές σου + + Οι συσκευές που πρόσθεσες αποσυνδέθηκαν όταν έγινε κατάργηση της συσκευής σου. Πήγαινε στις Ρυθμίσεις για να συνδέσεις ξανά τυχόν συσκευές. + + Άνοιγμα ρυθμίσεων + + Αργότερα + Επιλογή μελών @@ -935,7 +945,7 @@ Να ειδοποιούμαι για αναφορές - Θέλεις να λαμβάνεις ειδοποιήσεις όταν σε αναφέρουν σε σιγασμένες συνομιλίες; + Θέλεις να λαμβάνεις ειδοποιήσεις όταν σε αναφέρουν σε συνομιλίες σε σίγαση; Να ειδοποιούμαι πάντα Να μην ειδοποιούμαι @@ -953,6 +963,16 @@ Το όνομα χρήστη δημιουργήθηκε Το όνομα χρήστη αντιγράφηκε + + Δεν ήταν δυνατή η διαγραφή του ονόματος χρήστη. Δοκίμασε ξανά αργότερα. + + Το όνομα χρήστη διαγράφηκε + + + + Κάτι πήγε στραβά με το όνομα χρήστη σου, δεν αντιστοιχεί πλέον στον λογαριασμό σου. Μπορείς να δοκιμάσεις να το ρυθμίσεις ξανά ή να επιλέξεις ένα νέο. + + Διόρθωση τώρα @@ -1156,8 +1176,8 @@ Νέα ομάδα Πρόσκληση φίλων Χρήση SMS - Εμφάνιση - Προσθήκη φωτογραφίας + Χρώματα συνομιλίας + Προσθήκη φωτογραφίας προφίλ Απαντήσεις @@ -1468,14 +1488,14 @@ Αποδοχή Συνέχεια Διαγραφή - Φραγή - Κατάργηση φραγής + Αποκλεισμός + Κατάργηση αποκλεισμού Να επιτραπεί στον/στην %1$s να σου στείλει μηνύματα, και να μοιραστεί το όνομα και η φωτογραφία σου μαζί του/της; Δεν θα γνωρίζει ότι έχεις δει τα μηνύματα που έστειλε μέχρι να δεχτείς. - Να επιτραπεί στον/στην %1$s να σου στείλει μηνύματα, και να μοιραστεί το όνομα και η φωτογραφία σου μαζί του/της; Δεν θα λάβεις κανένα μήνυμα μέχρι να καταργήσεις τη φραγή του/της. + Να επιτραπεί στον/στην %1$s να σου στείλει μηνύματα, και να μοιραστείς το όνομα και τη φωτογραφία σου μαζί του/της; Δεν θα λάβεις κανένα μήνυμα μέχρι να καταργήσεις τον αποκλεισμό του/της. - Να επιτραπεί στον/στην %1$s να σου στείλει μηνύματα; Δεν θα λάβεις μηνύματα μέχρι να τον/την ξεμπλοκάρεις. - Λήψη ενημερώσεων και νέων από τον/την %1$s; Δεν θα λαμβάνεις καμία ενημέρωση μέχρι να τον/την ξεμπλοκάρεις. + Να επιτραπεί στον/στην %1$s να σου στείλει μηνύματα; Δεν θα λάβεις κανένα μήνυμα μέχρι να καταργήσεις τον αποκλεισμό του/της. + Λήψη ενημερώσεων και νέων από τον/την %1$s; Δεν θα λάβεις καμία ενημέρωση μέχρι να καταργήσεις τον αποκλεισμό του/της. Να συνεχίσεις τη συνομιλία με αυτή την ομάδα, και να μοιραστείς το όνομα και τη φωτογραφία σου με τα μέλη της; Αναβάθμισε την ομάδα για να αποκτήσεις νέες δυνατότητες όπως οι @αναφορές και οι διαχειριστές. Τα μέλη που δεν έχουν μοιραστεί τη φωτογραφία ή το όνομά τους με την ομάδα θα προσκληθούν να μπουν. Αυτή η ομάδα παλαιού τύπου δεν μπορεί πλέον να χρησιμοποιηθεί επειδή είναι πολύ μεγάλη. Το μέγιστο μέγεθος της ομάδας είναι %1$d. @@ -1483,7 +1503,7 @@ Θέλεις να μπεις στην ομάδα και να μοιραστείς το όνομα και τη φωτογραφία σου με τα μέλη της; Δεν θα ξέρουν ότι έχεις δει τα μηνύματά τους μέχρι να δεχτείς. Θέλεις να μπεις στην ομάδα και να μοιραστείς το όνομα και τη φωτογραφία σου με τα μέλη της; Δεν θα μπορείς να δεις τα μηνύματά τους μέχρι να δεχτείς. Θέλεις να μπεις σε αυτή την ομάδα; Δεν θα γνωρίζουν οτι έχεις δει τα μηνυματά τους μέχρι να δεχτείς. - Θέλεις να καταργηθεί η φραγή αυτής της ομάδας και να μοιραστεί το όνομα και η φωτογραφία σου με τα μέλη της; Δεν θα λάβεις κανένα μήνυμα μέχρι να καταργήσεις τη φραγή της. + Θέλεις να καταργηθεί ο αποκλεισμός αυτής της ομάδας και να μοιραστείς το όνομα και τη φωτογραφία σου με τα μέλη της; Δεν θα λάβεις κανένα μήνυμα μέχρι να καταργήσεις τον αποκλεισμό της. Εμφάνιση Μέλος του %1$s @@ -1584,9 +1604,20 @@ Δημιουργία νέου PIN + + Αποστολή κωδικού SMS + + Εγγραφή στο Signal - Βοήθεια με την επανεγγραφή PIN σε Android + + Το PIN σου είναι ένας κωδικός με %1$d+ ψηφία που δημιούργησες και μπορεί να αποτελείται από αριθμούς ή αλφαριθμητικά.\n\nΑν δεν μπορείς να θυμηθείς το PIN σου, μπορείς να δημιουργήσεις ένα νέο. + + Αν δεν μπορείς να θυμηθείς το PIN σου, μπορείς να δημιουργήσεις ένα νέο. + + Σου τέλειωσαν οι απόπειρες για το PIN, αλλά μπορείς να αποκτήσεις πρόσβαση στο λογαριασμό Signal σου δημιουργώντας νέο PIN. + Προσοχή - Αν απενεργοποιήσεις το PIN, θα χάσεις όλα τα δεδομένα όταν επανεγγραφτείς στο Signal, εκτός εάν δημιουργήσεις χειροκίνητα αντίγραφο ασφαλείας και το ανακτήσεις. Δεν μπορείς να ενεργοποιήσεις το Κλείδωμα Εγγραφής όσο το PIN είναι απενεργοποιημένο. + Αν απενεργοποιήσεις το PIN, θα χάσεις όλα τα δεδομένα όταν επανεγγραφείς στο Signal, εκτός εάν δημιουργήσεις χειροκίνητα αντίγραφο ασφαλείας και το ανακτήσεις. Δεν μπορείς να ενεργοποιήσεις το Κλείδωμα Εγγραφής όσο το PIN είναι απενεργοποιημένο. Απενεργοποίηση PIN @@ -1616,8 +1647,8 @@ Η ιστορία μου - Φραγή - Κατάργηση φραγής + Αποκλεισμός + Κατάργηση αποκλεισμού @@ -1711,7 +1742,7 @@ Κάμερα - Αποσίγαση + Αναίρεση σίγασης Σίγαση @@ -1726,12 +1757,12 @@ - Ο/Η %1$s έχει φραγεί + Ο/Η %1$s έχει αποκλειστεί Περισσότερες πληροφορίες Δεν θα μπορείς να λάβεις τον ήχο ή την εικόνα του/της και αυτός/ή δεν θα λαμβάνει τα δικά σου. Δεν μπορείς να λάβεις ήχο & βίντεο από τον/την %1$s Δεν μπορείς να λάβεις ήχο και βίντεο από τον/την %1$s - Αυτό ίσως οφείλεται στο ότι δεν έχει επιβεβαιώσει την αλλαγή του αριθμού ασφαλείας σου, ότι υπάρχει πρόβλημα με τη συσκευή του/της ή ότι σε έχει μπλοκάρει. + Αυτό ίσως οφείλεται στο ότι δεν έχει επιβεβαιώσει την αλλαγή του αριθμού ασφαλείας σου, ότι υπάρχει πρόβλημα με τη συσκευή του/της ή ότι σε έχει αποκλείσει. Σύρε για να δεις την διαμοιρασμένη οθόνη @@ -1768,11 +1799,18 @@ Το Signal χρειάζεται τα δικαιώματα επαφών και πολυμέσων για να σας βοηθήσει να συνδεθείτε με φίλους και να στείλετε μηνύματα. Οι επαφές σας μεταφορτώνονται χρησιμοποιώντας τον ιδιωτικό εντοπισμό επαφών του Signal, πράγμα που σημαίνει ότι είναι κρυπτογραφημένες end-to-end και ποτέ ορατές από την υπηρεσία Signal. Το Signal χρειάζεται την άδεια επαφών για να σας βοηθήσει να συνδεθείτε με φίλους. Οι επαφές σας μεταφορτώνονται χρησιμοποιώντας τον ιδιωτικό εντοπισμό επαφών του Signal, πράγμα που σημαίνει ότι είναι κρυπτογραφημένες end-to-end και ποτέ ορατές από την υπηρεσία Signal. Έχεις προσπαθήσει πάρα πολλές φορές να εγγράψεις αυτόν τον αριθμό. Παρακαλώ ξαναπροσπάθησε αργότερα. + + Έχεις προσπαθήσει πάρα πολλές φορές να εγγράψεις αυτόν τον αριθμό. Ξαναπροσπάθησε σε %1$s. Αδυναμία σύνδεσης στην υπηρεσία. Παρακαλώ έλεγξε τη σύνδεση στο δίκτυο και ξαναπροσπάθησε. Μη-τυπική μορφή αριθμού Ο αριθμός που έγραψες (%1$s) φαίνεται να μην έχει μια τυπική μορφή.n\nΜήπως εννοούσες %2$s; Molly Android - Μορφή αριθμού τηλεφώνου + Ζητήθηκε κλήση + + Ζητήθηκε SMS + + Ζητήθηκε κωδικός επαλήθευσης Είσαι %1$d βήμα μακριά από την καταχώρηση ενός αρχείου αποσφαλμάτωσης. Είσαι %1$d βήματα μακριά από την καταχώρηση ενός αρχείου αποσφαλμάτωσης. @@ -1792,6 +1830,16 @@ Κλήση Κωδικός Επαλήθευσης Αποστολή κωδικού + + Αντιμετωπίζεις προβλήματα με την εγγραφή; + + • Βεβαιώσου ότι το τηλέφωνό σου διαθέτει σήμα κινητής τηλεφωνίας για να λάβεις το SMS ή την κλήση σου \n • Επιβεβαίωσε ότι μπορείς να δεχτείς τηλεφωνικές κλήσεις στον αριθμό \n • Έλεγξε ότι έχεις εισαγάγει σωστά τον αριθμό τηλεφώνου σου. + + Για περισσότερες πληροφορίες, ακολούθησε αυτά τα βήματα αντιμετώπισης προβλημάτων ή Επικοινώνησε με την Υποστήριξη + + αυτά τα βήματα αντιμετώπισης προβλημάτων + + Επικοινωνία με την υποστήριξη Ενεργοποίηση του Κλειδώματος εγγραφής; @@ -1953,11 +2001,15 @@ Αντέδρασε με %1$s στην ιστορία σου - Αντέδρασε με %1$s στην ιστορία τους + Αντέδρασες με %1$s στην ιστορία τους Πληρωμή Προγραμματισμένο μήνυμα + + Το ιστορικό μηνυμάτων σου έχει συγχωνευθεί + + Ο αριθμός %1$s ανήκει στον χρήστη %2$s Αναβάθμιση Molly @@ -2087,13 +2139,15 @@ Μη ασφαλές SMS %1$s %2$s Επαφή - Αντέδρασε με %1$s στο \"%2$s\". + Αντέδρασε με %1$s στη δημοσίευση \"%2$s\". Αντέδρασε με %1$s στο βίντεό σου. Αντέδρασε με %1$s στη φωτογραφία σου. Αντέδρασε με %1$s στο GIF σου. Αντέδρασε με %1$s στο αρχείο σου. Αντέδρασε με %1$s στο ηχητικό σου. - Αντέδρασε με %1$s στο πολυμέσο μιας μόνο προβολής + Αντέδρασε με %1$s στο πολυμέσο μεμονωμένης προβολής. + + Αντέδρασε με %1$s στην πληρωμή σου. Αντέδρασε με %1$s στο αυτοκόλλητό σου. Αυτό το μήνυμα διαγράφηκε. @@ -2298,7 +2352,7 @@ Ενεργοποίηση ειδοποιήσεων κλήσεων Ανανέωση επαφής - Μπλοκάρισμα αιτήματος + Αποκλεισμός αιτήματος Χωρίς κοινές ομάδες. Εξέτασε τα αιτήματα προσεκτικά. Ομάδα με καμία γνωστή επαφή. Εξέτασε τα αιτήματα προσεκτικά. Εμφάνιση @@ -2487,8 +2541,8 @@ Θέμα κατά τη παράδοση - Ένα μήνυμα, αυτοκόλλητο, αντίδραση ή αποδεικτικό ανάγνωσης δεν μπόρεσε να σου παραδοθεί από τον/την %1$s. Είτε προσπάθησαν να σου στείλουν απ\'ευθείας, είτε σε κάποια ομάδα. - Ένα μήνυμα, αυτοκόλλητο, αντίδραση ή αποδεικτικό ανάγνωσης δεν μπόρεσε να σου παραδοθεί από τον/την %1$s. + Δεν ήταν δυνατή η παράδοση μηνύματος, αυτοκόλλητου, αντίδρασης ή αποδεικτικού ανάγνωσης από τον χρήστη %1$s. Είτε προσπάθησε να σου στείλει απευθείας, είτε σε κάποια ομάδα. + Ένα μήνυμα, αυτοκόλλητο, αντίδραση ή αποδεικτικό ανάγνωσης από τον χρήστη %1$s δεν σου παραδόθηκε. Όνομα (απαιτείται) @@ -2971,7 +3025,7 @@ Αποστολή πληρωμής Ληφθείσα πληρωμή Πληρωμή ολοκληρώθηκε %1$s - Φραγή αριθμού + Αποκλεισμός αριθμού Μεταφορά @@ -3056,7 +3110,7 @@ Νέο μήνυμα προς… - Φραγή χρήστη/τριας + Αποκλεισμός χρήστη Προσθήκη στην ομάδα @@ -3128,7 +3182,7 @@ - Αποσίγαση + Αναίρεση σίγασης Σίγαση ειδοποιήσεων @@ -3295,6 +3349,8 @@ Γράψε το PIN σου Γράψε το PIN που δημιούργησες για το λογαριασμό σου. Αυτό είναι διαφορετικό από τον κωδικό επαλήθευσης που έλαβες μέσω SMS. + + Εισήγαγε το PIN που δημιούργησες για τον λογαριασμό σου. Εισαγωγή αλφαριθμητικού PIN Εισαγωγή αριθμητικού PIN Λάθος PIN. Ξαναδοκίμασε. @@ -3398,7 +3454,10 @@ Το αντίγραφο ασφαλείας σου περιέχει ένα πολύ μεγάλο αρχείο που δεν μπορεί να αντιγραφεί. Διάγραψέ το και δημιούργησε ένα καινούργιο αντίγραφο ασφαλείας. Πάτα για διαχείριση αντίγραφων ασφαλείας. Λάθος αριθμός; + Κάλεσέ με (%1$02d:%2$02d) + + Επαναποστολή κωδικού σε(%1$02d:%2$02d) Επικοινωνία με την Υποστήριξη Signal Εγγραφή Signal - Κωδικός επαλήθευσης για Android Λάθος κωδικός @@ -3406,6 +3465,18 @@ Άγνωστο Να δουν τον αριθμό τηλεφώνου μου Να με βρίσκουν με τον αριθμό τηλεφώνου μου + + Αριθμός τηλεφώνου + + Επίλεξε ποια άτομα μπορούν να δουν τον αριθμό τηλεφώνου σου και ποια μπορούν να τον χρησιμοποιήσουν για να επικοινωνήσουν μαζί σου στο Molly. + + Ποιοι χρήστες μπορούν να δουν τον αριθμό μου + + Κανένας δεν θα μπορεί να δει τον αριθμό τηλεφώνου σου στο Molly + + Ποιοι χρήστες μπορούν να με βρουν με τον αριθμό μου + + Ο αριθμός τηλεφώνου σου θα είναι ορατός σε όλα τα άτομα και τις ομάδες που στέλνεις μηνύματα. Τα άτομα που έχουν τον αριθμό σου στις επαφές τους θα μπορούν επίσης να τον δουν στο Molly. Όλοι Οι επαφές μου Κανένας @@ -3562,8 +3633,8 @@ - Φραγή - Κατάργηση φραγής + Αποκλεισμός + Κατάργηση αποκλεισμού Προσθήκη στις επαφές Δεν βρέθηκε εφαρμογή για το άνοιγμα των επαφών. @@ -3615,9 +3686,9 @@ %1$s/%2$s - Ο/Η \"%1$s\" έχει φραγεί - Αποτυχία φραγής του/της \"%1$s\" - Η φραγή του/της \"%1$s\" καταργήθηκε. + Ο/Η \"%1$s\" έχει αποκλειστεί. + Αποτυχία αποκλεισμού του/της \"%1$s\" + Ο αποκλεισμός του/της \"%1$s\" καταργήθηκε. Ανασκόπηση μελών @@ -3644,7 +3715,7 @@ Επαφή σου Αφαίρεση απ\' την ομάδα Ανανέωση επαφής - Φραγή + Αποκλεισμός Διαγραφή Άλλαξε πρόσφατα το όνομα προφίλ του/της από %1$s σε %2$s @@ -3784,12 +3855,12 @@ Απενεργοποίηση Πορτοφολιού Το Υπόλοιπό Σας - Συνιστάται να μεταφέρετε τα χρήματά σας σε άλλη διεύθυνση πορτοφολιού πριν απενεργοποιήσετε τις πληρωμές. Εάν επιλέξετε να μην μεταφέρετε τα χρήματά σας τώρα, θα παραμείνουν στο πορτοφόλι σας που είναι συνδεδεμένο με το Molly εάν ενεργοποιήσετε εκ νέου τις πληρωμές. + Συνιστάται να μεταφέρεις τα χρήματά σου σε άλλη διεύθυνση πορτοφολιού πριν απενεργοποιήσεις τις πληρωμές. Εάν επιλέξεις να μη μεταφέρεις τα χρήματά σου τώρα, θα παραμείνουν στο πορτοφόλι σου που είναι συνδεδεμένο με το Molly εάν ενεργοποιήσεις εκ νέου τις πληρωμές. Μεταφορά εναπομείναντος υπολοίπου Απενεργοποίηση χωρίς μεταφορά Απενεργοποίηση Απενεργοποίηση χωρίς μεταφορά; - Το υπόλοιπό σας θα παραμείνει στο πορτοφόλι που είναι συνδεδεμένο με το Molly, εάν επιλέξετε να ενεργοποιήσετε εκ νέου τις πληρωμές. + Το υπόλοιπό σου θα παραμείνει στο πορτοφόλι που είναι συνδεδεμένο με το Molly, εάν επιλέξεις να ενεργοποιήσεις εκ νέου τις πληρωμές. Σφάλμα κατά την απενεργοποίηση πορτοφολιού. @@ -4003,12 +4074,12 @@ Δημιουργία προφίλ - Λίστα Φραγής + Λίστα αποκλεισμού %1$d επαφές Συνομιλίες Μηνύματα που εξαφανίζονται Ασφάλεια εφαρμογής - Να μην επιτρέπεται η καταγραφή της οθόνης (screenshots) στη λίστα με τα πρόσφατα και μέσα στην εφαρμογή + Να μην επιτρέπονται τα στιγμιότυπα οθόνης (screenshots) στη λίστα με τα πρόσφατα και μέσα στην εφαρμογή Κλήσεις και μηνύματα Signal, κλήσεις που πάντα αναμεταδίδονται, και προστατευμένος αποστολέας Προκαθορισμένο χρονόμετρο για νέες συνομιλίες Ορισμός ενός προκαθορισμένου χρόνου εξαφάνισης μηνυμάτων για όλες τις συνομιλίες που ξεκινάς εσύ. @@ -4176,9 +4247,9 @@ Λεπτομέρειες επαφής Προβολή αριθμού ασφαλείας Αποκλεισμός - Φραγή ομάδας + Αποκλεισμός ομάδας Κατάργηση αποκλεισμού - Κατάργηση φραγής ομάδας + Κατάργηση αποκλεισμού ομάδας Προσθήκη σε μια ομάδα Προβολή όλων Προσθήκη μελών @@ -4188,7 +4259,7 @@ Προσθήκη ως επαφή Αναίρεση σίγασης Η συνομιλία είναι σε σίγαση έως %1$s - Η συνομιλία είνα σε σίγαση για πάντα + Η συνομιλία θα είναι σε σίγαση για πάντα Ο αριθμός τηλεφώνου αντιγράφτηκε στο πρόχειρο. Αριθμός τηλεφώνου Πάρε σήματα για το προφίλ σου στηρίζοντας το Signal. Πάτα σε ένα σήμα για να μάθεις περισσότερα. @@ -4205,7 +4276,7 @@ Σίγαση ειδοποιήσεων - Μη σιγασμένο + Χωρίς σίγαση Αναφορές Πάντα να ειδοποιούμαι Να μην ειδοποιούμαι @@ -4508,7 +4579,7 @@ Η δωρεά δεν εστάλη λόγω σφάλματος δικτύου. Έλεγξε τη σύνδεσή σου και δοκίμασε ξανά. - Δωρεά στον/στην %1$s + Δωρεά εκ μέρους του χρήστη %1$s Ο/Η %1$s έκανε δωρεά στη Signal εκ μέρους σου @@ -4859,7 +4930,7 @@ Απαντήσεις - Απαντήσεις σε αυτή την ιστορία + Απάντηση σε αυτή την ιστορία Ιδιωτική απάντηση στον/στην %1$s @@ -5057,7 +5128,7 @@ - Αντέδρασες στην ιστορία του/της %1$s + Αντέδρασες στην ιστορία του χρήστη %1$s Αντέδρασε στην ιστορία σου @@ -5322,7 +5393,7 @@ Εξαγωγή μηνυμάτων SMS - Αυτό μπορεί να πάρει λίγο χρόνο + Η διαδικασία μπορεί να χρειαστεί λίγο χρόνο Εξαγωγή %1$d από %2$d… @@ -5601,5 +5672,15 @@ Διαγραφή ονόματος χρήστη + + + ώ + + λ + + Ρύθμιση + + Ο ελάχιστος χρόνος πριν από την εφαρμογή του κλειδώματος οθόνης είναι 1 λεπτό. + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 231e79f83e..98fcb032b0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -14,6 +14,7 @@ + No @@ -542,6 +543,15 @@ +%1$d + + Vuelve a vincular tus dispositivos + + Los dispositivos que has añadido se desvincularon cuando se canceló el registro de tu dispositivo. Ve a Ajustes para volver a vincular cualquier dispositivo. + + Abrir ajustes + + Más tarde + Seleccionar participantes @@ -953,6 +963,16 @@ Se ha creado el nombre de usuario Se ha copiado el nombre de usuario + + No se ha podido eliminar el alias. Inténtalo de nuevo más tarde. + + Alias eliminado + + + + Ha habido un problema con tu alias, ya no está asignado a tu cuenta. Puedes intentar configurarlo de nuevo o elegir uno distinto. + + Reparalo ahora @@ -1156,8 +1176,8 @@ Nuevo grupo Invitar amistades Usar SMS - Apariencia - Añade una foto + Colores del chat + Añade una foto a tu perfil Respuestas @@ -1472,7 +1492,7 @@ Desbloquear ¿Deseas que %1$s te envíe mensajes y compartir tu nombre y foto? Esta persona no sabrá que has visto sus mensajes hasta que aceptes. - ¿Deseas que %1$s te envíe mensajes y compartir tu nombre y foto? Esta persona no sabrá que has visto sus mensajes hasta que aceptes. + ¿Deseas que %1$s te envíe mensajes y compartir tu nombre y foto? Esta persona no sabrá que has visto sus mensajes hasta lx desbloquees. ¿Deseas permitir a %1$s chatear contigo? No recibirás ningún mensaje si no desbloqueas el chat. ¿Deseas recibir noticias y novedades de %1$s? No recibirás ninguna hasta que desbloquees a esta persona. @@ -1584,6 +1604,17 @@ Crear nuevo PIN + + Enviar código por SMS + + Registro en Signal - Ayuda para volver a registrarse con el PIN en Android + + Tu PIN es un código de %1$d+ caracteres creado por ti, que puede constar de dígitos o caracteres alfanuméricos.\n\nSi no puedes recordar tu PIN, puedes crear uno nuevo. + + Si no puedes recordar tu PIN, puedes crear uno nuevo. + + Se te han acabado los intentos para introducir tu PIN, pero aún puedes acceder a tu cuenta de Signal creando un nuevo PIN. + Advertencia Al desactivar el PIN perderás todos los datos de tu cuenta al volver a instalar Signal, a menos que hagas una copia de seguridad manual y la restaures. No se puede activar el bloqueo de registro mientras el PIN esté desactivado. @@ -1726,7 +1757,7 @@ - %1$s está bloquead@ + %1$s está bloqueadx Más detalles No recibirás su audio ni vídeo y ni ell@s el tuyo. No se puede recibir audio ni vídeo de %1$s @@ -1768,11 +1799,18 @@ Signal necesita los permisos de acceso a tus contactos y medios para chatear y contactar con tus amistades. Tu lista de contactos se cifra en tu teléfono y se trasmite al servicio de Signal para descubrir contactos. Como la lista está cifrada, ni Signal ni nadie tienen acceso. Signal necesita los permisos de acceso a tus contactos para chatear y contactar con tus amistades. Tu lista de contactos se cifra en tu teléfono y se trasmite al servicio de Signal para descubrir contactos. Como la lista está cifrada, ni Signal ni nadie tienen acceso. Has hecho demasiados intentos para registrar este número. Inténtalo de nuevo más tarde. + + Has hecho demasiados intentos para registrar este número. Inténtalo de nuevo en %1$s. No se puede conectar con el servidor. Por favor, comprueba la conexión de red e inténtalo de nuevo. Número en formato no estándar El número que has introducido (%1$s) parece usar un formato no estándar.\n\n ¿Es %2$s el correcto? Molly Android - Formato de número + Llamada solicitada + + SMS solicitado + + Código de verificación solicitado Estás a %1$d paso de enviar un informe de depuración. Estás a %1$d pasos de enviar un informe de depuración. @@ -1792,6 +1830,16 @@ Llamada Código de verificación Enviar código de nuevo + + ¿Tienes problemas para registrarte? + + • Asegúrate de que tu teléfono tenga señal para recibir tu SMS o llamada\n • Confirma que puedes recibir una llamada telefónica al número\n • Verifica que hayas ingresado tu número de teléfono correctamente. + + Para más información, sigue estos pasos de resolución de problemas o ponte en contacto con el soporte técnico + + estos pasos de resolución de problemas + + Contactar con el soporte técnico ¿Activar el bloqueo de registro? @@ -1958,6 +2006,10 @@ Pago Mensaje programado + + Tu historial de mensajes se ha combinado + + %1$s pertenece a %2$s Actualizar Molly @@ -2087,13 +2139,15 @@ SMS no cifrado %1$s %2$s Contacto - Reacción %1$s a: «%2$s». - Reacción a tu video: %1$s - Reacción a tu foto: %1$s + Ha reaccionado con %1$s a: \"%2$s\". + Reacción a tu vídeo: %1$s. + Reacción a tu foto: %1$s. Reacción a tu GIF: %1$s. - Reacción a tu fichero: %1$s. + Reacción a tu archivo: %1$s. Reacción a tu audio: %1$s. - Reacción a tu adjunto para ver una vez: %1$s. + Reacción a tu archivo multimedia para ver una vez: %1$s. + + Ha reaccionado con %1$s a tu pago. Reacción a tu sticker: %1$s. Mensaje eliminado. @@ -2487,8 +2541,8 @@ Fallo en la entrega - No se ha podido recibir un mensaje, sticker, adjunto, reacción o notificación de lectura de %1$s. Lo ha enviado en un chat o en un grupo común. - No se ha podido recibir un mensaje, sticker, adjunto, reacción o notificación de lectura de %1$s. + No se ha podido recibir un mensaje, sticker, reacción o notificación de lectura de %1$s. Puede haberlo enviado en un chat privado o en un grupo común. + No se ha podido recibir un mensaje, sticker, reacción o notificación de lectura de %1$s. Nombre (necesario) @@ -3295,6 +3349,8 @@ Introduce tu PIN Introduce el PIN que has seleccionado al crear tu cuenta de Signal. El PIN es diferente al del SMS de verificación. + + Introduce el PIN que creaste para tu cuenta. Introduce PIN alfanumérico Introduce PIN numérico PIN incorrecto. Inténtalo de nuevo. @@ -3398,7 +3454,10 @@ Tu copia de seguridad contiene un archivo muy grande que no puede ser copiado. Por favor, bórralo y crea una nueva copia de seguridad. Toca para gestionar las copias. ¿Número erróneo? + Llámame (%1$02d:%2$02d) + + Volver a enviar código (%1$02d:%2$02d) Contacta con el Centro de Asistencia de Signal Registro de Signal - Código de verificación para Android Código incorrecto @@ -3406,6 +3465,18 @@ Desconocido Ver mi número de teléfono Encontrarme por número de teléfono + + Número de teléfono + + Elige quién puede ver tu número de teléfono y quién puede contactarte en Molly con él. + + Quién puede ver mi número + + Nadie podrá ver que usas tu número de teléfono para comunicarte a través de Molly + + Quién puede encontrarme a través de mi número + + Tu número de teléfono será visible para la gente a quien envíes mensajes y para lxs participantes de tus grupos. La gente con tu número entre sus contactos también podrá ver que usas Molly. Cualquiera Mis contactos Nadie @@ -4508,7 +4579,7 @@ No se ha podido mandar tu donación a causa de un error de la red. Comprueba tu conexión y vuelve a intentarlo. - Donación para %1$s + Donación en nombre de %1$s %1$s ha donado a Signal en tu nombre @@ -5601,5 +5672,15 @@ Eliminar alias (nombre de usuarix) + + + h + + min + + Fijar + + El tiempo mínimo antes de que se aplique el bloqueo de pantalla es de 1 minuto. + diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 1e1c39bb9f..92726b0eb9 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -14,6 +14,7 @@ + Jah Ei @@ -98,8 +99,8 @@ Blokeeritud kasutajad Lisa blokeeritud kasutaja - Blokeeritud kasutajatel pole võimalik sulle helistada ega sõnumeid saata. - Blokeeritud kasutajad puuduvad + Blokeeritud kasutajad ei saa sulle helistada ega sõnumeid saata. + Blokeeritud kasutajaid ei ole Blokeerid kasutaja? Kasutajal %1$s pole võimalik sulle helistada ega sõnumeid saata. Blokeeri @@ -138,7 +139,7 @@ Jätka - Kas blokeerida ja lahkuda %1$s? + Kas blokeerida ja lahkuda grupist %1$s? Kas blokeerida %1$s? Sa ei saa enam sellelt grupilt sõnumeid ega värskendusi ja liikmed ei saa sind uuesti sellesse gruppi lisada. Grupi liikmetel pole võimalik sind sellesse gruppi uuesti lisada. @@ -147,16 +148,16 @@ Teil on võimalik üksteisele sõnumeid saata ja helistada ning sinu nimi ja foto jagatakse teistega. Teil on võimalik teineteisele sõnumeid saata. - Blokeeritud isikutel pole võimalik sulle helistada ega sõnumeid saata. + Blokeeritud isikud ei saa sulle helistada ega sõnumeid saata. Blokeeritud isikud ei saa sulle sõnumeid saata. - Keeldu infost Signali uuenduste ja uudiste kohta. + Blokeeri teavitused Signali uuenduste ja uudiste kohta. Telli taas info Signali uuenduste ja uudiste kohta. - Kas eemaldada %1$sblokeering? + Kas deblokeerida %1$s? Blokeeri Blokeeri ja lahku - Raporteeri rämpspostitus ja blokeeri + Teata rämpspostitusest ja blokeeri Täna @@ -366,7 +367,7 @@ Meedia saatmisel tekkis tõrge - Raporteeritud rämpspostitusena ja blokeeritud + Rämpspostitusest teatatud ja blokeeritud. SMS-sõnumite toetamine on praegu välja lülitatud. Saad eksportida oma sõnumid mõnda teise rakendusse oma telefonis. @@ -542,6 +543,15 @@ +%1$d + + Seo oma seadmed uuesti + + Kui su seadme registreering eemaldati, seoti sinu lisatud seadmed lahti. Saad seadmed uuesti siduda sätete alt. + + Ava sätted + + Hiljem + Vali liikmed @@ -935,7 +945,7 @@ Teavita mind mainimise korral - Kas saada teavitusi, kui sind mainitakse vaigistatud vestlustes? + Kas soovid saada teavitusi, kui sind mainitakse vaigistatud vestlustes? Teavita mind alati Ära teavita mind @@ -953,6 +963,16 @@ Kasutajanimi loodud Kasutajanimi kopeeritud + + Kasutajanime ei saanud kustutada. Proovi hiljem uuesti. + + Kasutajanimi kustutatud + + + + Sinu kasutajanimega seoses läks midagi valesti ja see ei ole enam sinu kontoga seotud. Saad proovida seda uuesti seadistada või valida uue. + + Paranda @@ -1156,8 +1176,8 @@ Uus grupp Kutsu sõpru Kasuta SMSi - Välimus - Lisa foto + Vestluse värvid + Lisa profiilifoto Vastused @@ -1472,10 +1492,10 @@ Eemalda blokeering Kas lubad kasutajal %1$s sulle sõnumeid saata ja jagad temaga enda nime ja fotot? Ta ei tea, et oled seda sõnumit näinud enne kui oled sellega nõustunud. - Kas lubad kasutajal %1$s sulle sõnumeid saata ja jagad temaga enda nime ja fotot? Ta ei tea, et oled seda sõnumit näinud enne kui oled sellega nõustunud. + Kas lubad kasutajal %1$s sulle sõnumeid saata ja jagad temaga oma nime ja fotot? Sa ei saa sõnumeid enne kui oled blokeeringu eemaldanud. Kas lubad kasutajal %1$s sulle sõnumeid saata? Sa ei saa sõnumeid enne kui oled blokeeringu eemaldanud. - Kas soovid näha kasutaja %1$s uuendusi ja uudiseid? Sa ei saa sõnumeid enne kui oled blokeeringu eemaldanud. + Kas soovid näha kasutaja %1$s uuendusi ja uudiseid? Sa ei saa uuendusi enne kui oled blokeeringu eemaldanud. Kas jätkata vestlust selle grupiga ja jagada sinu nime ning fotot grupi liikmetele? Uuenda seda gruppi, et aktiveerida uus funktsionaalsus nagu @mainimised ja administraatorid. Liikmed, kes pole selles grupis jaganud enda nime ja fotot kutsutakse liituma. Seda vana gruppi ei saa rohkem kasutada, sest see on liiga suur. Maksimaalne grupi suurus on %1$d. @@ -1483,7 +1503,7 @@ Kas soovid selle grupiga liituda ja jagada enda nime ning pilti selle liikmetega? Nad ei tea, et oled seda sõnumit näinud enne kui oled sellega nõustunud. Kas soovid selle grupiga liituda ning jagada enda nime ja pilti selle liikmetega? Sa ei näe nende sõnumeid enne kui oled sellega nõustunud. Kas liitud grupiga? Nad ei tea, et oled nende sõnumeid näinud, kuni nõustud. - Kas eemaldada sellelt grupilt blokeering ja jagada enda nime ning fotot selle liikmetele? Sa ei saa enne ühtegi sõnumit kuni eemaldad blokeeringu. + Kas soovid eemaldada sellelt grupilt blokeering ja jagada selle liikmetega oma nime ja fotot? Sa ei saa sõnumeid enne kui oled blokeeringu eemaldanud. Näita %1$s liige @@ -1584,9 +1604,20 @@ Loo uus PIN-kood + + Saada SMS-kood + + Signalis registreerimine - vajan abi PIN-koodi Androidis uuesti registreerimiseks + + Sinu PIN-kood on sinu loodud %1$d+ märgist koosnev kood, mis võib sisaldada ainult numbreid või tähti ja numbreid.\n\nKui sa ei mäleta oma PIN-koodi, saad luua uue. + + Kui sa ei mäleta oma PIN-koodi, saad luua uue. + + Sinu PIN-koodi sisestamiste arv on täis, kuid kui uue PIN-koodi lood, pääsed endiselt oma Signali kontole ligi. + Hoiatus - Kui keelad PIN-koodi, siis kaotad kõik andmed, kui eemaldad Signal rakenduse ja pole seda käsitsi varundanud ja taastanud. Registreerimislukku pole võimalik sisse lülitada, kui PIN-kood on keelatud. + Kui keelad PIN-koodi, siis kaotad Signalisse uuesti registreerudes kõik andmed, mida sa pole käsitsi varundanud ja taastanud. Registreerimislukku pole võimalik sisse lülitada, kui PIN-kood on keelatud. Keela PIN @@ -1726,12 +1757,12 @@ - %1$s on blokitud + %1$s on blokeeritud Rohkem infot Sina ei saa nende heli või videot ja nemad ei saa sinu oma. Kasutajalt %1$s heli & video saamine ei õnnestu Kasutajalt %1$s heli ja video saamine ei õnnestu - See võib juhtuda, sest nad ei ole kinnitanud sinu turvanumbri muutmist, nende seadmetega on probleeme või nad on sinu blokeerinud. + See võib olla tingitud sellest, et ta ei ole sinu turvanumbri muutmist kinnitanud, tema seadmes on probleem või ta on su blokeerinud. Lohista, et näha ekraani jagamise võimalusi @@ -1768,11 +1799,18 @@ Signal vajab sinu kontaktidele ja meediale ligipääsu, et võimaldada sul ühenduda sõpradega ja vahetada sõnumeid. Sinu kontaktid laetakse üles Signali privaatse kontaktituvastuse abil, mis tähendab, et need on lõpp-punkti krüpteeringuga ega ole Signali teenusele kunagi nähtavad. Signal vajab kontaktidega seotud luba, et võimaldada sul sõpradega ühenduda. Sinu kontaktid laetakse üles Signali privaatse kontaktituvastuse abil, mis tähendab, et need on lõpp-punkti krüpteeringuga ega ole Signali teenusele kunagi nähtavad. Oled sooritanud liiga palju selle numbri registreerimise katseid. Palun proovi hiljem uuesti. + + Oled proovinud seda numbrit liiga palju kordi registreerida. Palun proovi uuesti %1$s pärast. Teenusega ei saadud ühendust. Palun kontrolli võrguühendust ja proovi uuesti. Ebastandardne numbriformaat Sisestatud number (%1$s) tundub olevat mittestandardses formaadis.\n\nKas mõtlesid hoopis %2$s? Molly Android - telefoninumbri formaat + Kõnepäring esitatud + + SMS tellitud + + Kinnituskood tellitud Sa oled silumislogi saatmisest %1$d sammu kaugusel. Sa oled silumislogi saatmisest %1$d sammu kaugusel. @@ -1792,6 +1830,16 @@ Helista Kinnituskood Saada kood uuesti + + Probleemid registreerimisega? + + • Veendu, et su telefonil on levi, et sõnumeid ja kõnesid vastu võtta\n • Kinnita, et saad sellel numbril kõnesid vastu võtta\n • Kontrolli, kas sisestasid telefoninumbri õigesti. + + Täiendava teabe saamiseks ava need tõrkeotsingu juhised või võta ühendust kasutajatoega. + + need tõrkeotsingu juhised + + Võta ühendust kasutajatoega Kas lülitada registreerimislukk sisse? @@ -1951,13 +1999,17 @@ Lunastasid märgi - Reageeris %1$s sinu loole + Reageeris sinu loole: %1$s - Reageeris %1$s tema loole + Reageeris tema loole: %1$s Makse Ajastatud sõnum + + Sinu sõnumiajalugu on liidetud + + %1$s omanik on %2$s Mollyi uuendus @@ -2087,14 +2139,16 @@ Turvamata SMS %1$s %2$s Kontakt - Reageeris %1$s sõnumile: \"%2$s\". - Reageeris %1$s sinu videole. - Reageeris %1$s sinu pildile. - Reageeris %1$s sinu GIF-ile. - Reageeris %1$s sinu failile. - Reageeris %1$s sinu helile. - Reageeris %1$s sinu ühekordsele meediale - Reageeris %1$s sinu kleepsule. + Reageeris küsimusele „%2$s“: %1$s + Reageeris sinu videole: %1$s + Reageeris sinu pildile: %1$s + Reageeris sinu GIF-ile: %1$s + Reageeris sinu failile: %1$s + Reageeris sinu häälsõnumile: %1$s + Reageeris sinu ühekordselt vaadatavale sisule: %1$s + + Reacted %1$s to your payment. + Reageeris sinu kleebisele: %1$s See sõnum kustutati. Kas lülitada Signaliga liitumise teavitused välja? Neid saab uuesti lubada, valides Signal > Seaded > Teavitused. @@ -2487,8 +2541,8 @@ Edastustõrge - Sõnumi, kleebise, reaktsiooni või lugemiskinnituse kontaktilt %1$s sulle saatmine ei õnnestunud. Ta võis püüda saata seda sulle otse või grupi kaudu. - Sõnumi, kleebise, reaktsiooni või lugemiskinnituse kontaktilt %1$s sulle saatmine ei õnnestunud. + Kontakt %1$s saatis sulle sõnumi, kleebise, reaktsiooni või lugemiskinnituse, kuid see ei jõudnud pärale. Ta võis püüda saata seda sulle otse või grupi kaudu. + Kontakt %1$s saatis sulle sõnumi, kleebise, reaktsiooni või lugemiskinnituse, kuid see ei jõudnud pärale. Eesnimi (kohustuslik) @@ -2555,7 +2609,7 @@ Ootel - Saajale + Saaja Saatjalt Kohaletoimetatud saajale Loetud: @@ -2675,9 +2729,9 @@ Kasuta telefoniraamatu fotosid Kuva kontaktide fotosid telefoniraamatust, kui olemas - Keep Muted Chats Archived + Hoia vaigistatud vestlused arhiveerituna - Muted chats that are archived will remain archived when a new message arrives. + Vaigistatud vestlused, mis on arhiveeritud, jäävad uue sõnumi saabudes arhiveerituks. Genereeri lingieelvaateid Hangib lingieelvaated otse saitidelt, mida sõnumina saadad. Muuda salasõna @@ -2971,7 +3025,7 @@ Makse saadetud Makse saadud Makse teostatud %1$s - Bloki number + Blokeeri number Kanna üle @@ -3128,7 +3182,7 @@ - Keela vaigistus + Eemalda vaigistus Vaigista teavitused @@ -3295,6 +3349,8 @@ Sisesta enda PIN-kood Sisesta PIN-kood, mille tegid enda konto jaoks. See erineb SMS-kontrollkoodist. + + Sisesta PIN-kood, mille oled oma konto jaoks loonud. Sisesta tähtnumbriline PIN-kood Sisesta numbriline PIN-kood Lubamatu PIN-kood. Proovi uuesti. @@ -3398,7 +3454,10 @@ Sinu varukoopia sisaldab väga suurt faili, millest ei ole võimalik varukoopiat teha. Palun kustuta see ja loo uus varukoopia. Koputa varunduste haldamiseks. Vale number? + Helista mulle (%1$02d.%2$02d) + + Saada kood uuesti (%1$02d.%2$02d) Võta ühendust Signali kasutajatoega Signali registreerimine - kinnituskood Androidile Sobimatu kood @@ -3406,6 +3465,18 @@ Tundmatu Vaata mu telefoninumbrit Leia mind telefoninumbri järgi + + Telefoninumber + + Vali, kes sinu telefoninumbrit näevad ja sinuga selle kaudu Mollyis ühendust saavad võtta. + + Kes mu numbrit näevad + + Mitte keegi ei näe Mollyis sinu telefoninumbrit + + Kes mind numbri järgi leida saavad + + Sinu telefoninumber on nähtav isikutele ja gruppidele, kellega suhtled. Isikud, kellel on sinu telefoninumber nende telefonikontaktides, näevad seda ka Mollyis. Igaüks Minu kontaktid Mitte keegi @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" on blokitud - \"%1$s\" blokkimine ei õnnestunud - \"%1$s\" blokkimine on eemaldatud. + %1$s on blokeeritud. + Kasutaja %1$s blokeerimine ei õnnestunud + Kasutaja %1$s blokeering on eemaldatud. Vaata liikmed üle @@ -3667,17 +3738,17 @@ Nõrk Wi-Fi signaal. Lülitumine mobiilsele andmesidele. - Sinu konto kustutamine tähendab: + Sinu konto kustutamine tähendab järgmist: Sisesta enda telefoninumber Kustuta konto - Kustuta enda konto info ja profiili foto - Kustuta kõik sinu sõnumid + kustutatakse sinu konto teave ja profiilifoto + kustutakse kõik sinu sõnumid Kustuta %1$s oma maksete kontol Riigi kood määramata Number määramata Sinu sisestatud telefoninumber ei vasta konto telefoninumbrile. Kas sa tõesti soovid enda kontot kustutada? - See toiming kustutab Signali konto ja lähtestab rakenduse. Rakendus sulgub pärast protsessi lõpetamist. + See toiming kustutab sinu Signali konto ja lähtestab rakenduse. Rakendus sulgub pärast protsessi lõpetamist. Kohalike andmete kustutamine ei õnnestunud. Andmeid saab käsitsi kustutada süsteemi rakenduse sätetest. Käivita rakenduse sätted @@ -3784,7 +3855,7 @@ Deaktiveeri rahakott Sinu kontojääk - Soovitatav on enne maksete deaktiveerimist kanda enda vahendid teisele rahakoti aadressile. Kui otsustad praegu vahendeid mitte kanda, jäävad need sinu Mollyiga lingitud rahakotti, kuni sa Mollyi maksed uuesti aktiveerid. + Soovitatav on enne maksete desaktiveerimist kanda enda vahendid teisele rahakoti aadressile. Kui otsustad praegu vahendeid mitte kanda, jäävad need sinu Mollyiga lingitud rahakotti, kuni sa Mollyi maksed uuesti aktiveerid. Kanna kontojääk üle Deaktiveeri ilma ülekandeta Deaktiveeri @@ -4008,7 +4079,7 @@ Sõnumside Kaduvad sõnumid Rakenduse turvalisus - Blokeeri kuvatõmmised viimatiste nimekirjas ja rakenduse sees + Blokeeri kuvatõmmised hiljutiste fotode nimekirjas ja rakenduse sees Signali sõnumid ja kõned, alati kõnede suunamine ja turvatud saatja Uute gruppide sõnumite vaikimisi kestus Määra kaduvate sõnumite vaikimisi kestus kõigile sinu poolt loodud uutele gruppidele. @@ -4330,7 +4401,7 @@ Lisa loosse Lisa sõnum Lisa vastus - Saaja + Vali saaja Ühekordne sõnum Üks või mitu üksust olid liiga suured Üks või mitu üksust olid kehtetud @@ -4508,7 +4579,7 @@ Sinu annetust ei saanud saata võrgu vea tõttu. Kontrolli oma ühendust ja proovi uuesti. - Annetus kasutajale %1$s + Annetus kasutaja %1$s nimel %1$s annetas Signalile sinu nimel @@ -4905,9 +4976,9 @@ Vali, kes saavad sinu lugu vaadata. Muudatused ei mõjuta lugusid, mida oled juba jaganud. - Vastused & reaktsioonid + Vastused ja reaktsioonid - Luba vastuseid & reaktsioone + Luba vastuseid ja reaktsioone Luba inimestel, kes saavad su lugu vaadata, reageerida ja vastata @@ -5057,7 +5128,7 @@ - Sa reageerisid kasutaja %1$s loole + Reageerisid kasutaja %1$s loole Reageeris sinu loole @@ -5087,7 +5158,7 @@ Kinnita annetus - Saaja + Vali saaja Saajat teavitatakse annetusest privaatsõnumiga. Lisa allpool ka isiklik sõnum. @@ -5601,5 +5672,15 @@ Eemalda kasutajanimi + + + h + + min + + Määra + + Minimaalne ekraaniluku käivitumise aeg on 1 minut. + diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 8137efb24e..22b7cad282 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -14,6 +14,7 @@ + Bai Ez @@ -97,10 +98,10 @@ Blokeatutako erabiltzaileak - Gehitu blokeatutako erabiltzailea - Blokeatutako erabiltzaileek ezin izango dizute deitu; ezta mezuak bidali ere. - Blokeatutako erabiltzailerik ez - Blokeatu nahi duzu erabiltzailea? + Gehitu blokeatutako erabiltzaile bat + Blokeatutako erabiltzaileek ezin izango dizute deitu, ezta mezuak bidali ere. + Ez dago blokeatutako erabiltzailerik + Erabiltzailea blokeatu nahi duzu? \"%1$s\" erabiltzaileak ezingo dizu deitu edo mezurik bidali Blokeatu @@ -138,8 +139,8 @@ Jarraitu - %1$s blokeatu eta irten? - Blokeatu %1$s? + %1$s blokeatu, eta irten nahi duzu? + %1$s blokeatu nahi duzu? Ez duzu talde honetatik mezu edo egunerapen gehiagorik jasoko, eta kideek ezingo zaituzte talde honetara berriro gehitu. Taldeko kideak ez dira gai izango zu talde honetan berriro sartzeko. Taldeko kideak gai izango dira zu talde honetan berriro sartzeko. @@ -147,15 +148,15 @@ Elkarri deitzeko eta mezuak bidaltzeko gai izango zarete; horrez gain, zure izena eta argazkia beraiekin partekatuko dira. Elkarri mezuak bidali ahal izango dizkiozue. - Blokeatutako pertsonek ezingo dizute deitu edo mezuak bidali. + Blokeatutako pertsonek ezingo dizute deitu, ezta mezuak bidali ere. Blokeatutako pertsonek ezingo dizute mezurik bidali. Ez jaso Signal-en informazio eguneratua eta albisteak. Hasi berriro Signal-en informazio eguneratua eta albisteak jasotzen. - Desblokeatu %1$s? + %1$s desblokeatu nahi duzu? Blokeatu - Blokeatu eta Irten + Blokeatu eta irten Salatu spama dela eta blokeatu @@ -496,12 +497,12 @@ Kendu finkapena - Isildu - Isildu + Desaktibatu jakinarazpenak + Desaktibatu jakinarazpenak - Soinua aktibatu - Soinua aktibatu + Aktibatu jakinarazpenak + Aktibatu jakinarazpenak Aukeratu @@ -542,6 +543,15 @@ +%1$d + + Lotu gailuak berriro + + Gailua erregistrotik kentzean, gehitutako gailuen lotura kendu egin da. Joan ezarpenetara gailuak berriro lotzeko. + + Ireki ezarpenak + + Geroago + Hautatu kideak @@ -935,7 +945,7 @@ Nahi dut aipamenak jakinaraztea - Nahi duzu jakinarazpenak jaso isildutako txatetan aipatzen zaituztenean? + Jakinarazpenak desaktibatuta dauzkaten txatetan aipatzen zaituztenean, jakinarazpenik jaso nahi duzu? Beti jakinarazi Ez jakinarazi @@ -953,6 +963,16 @@ Sortu da erabiltzaile-izena Kopiatu da erabiltzaile-izena + + Ezin izan da ezabatu erabiltzaile-izena. Saiatu berriro geroago. + + Ezabatu da erabiltzaile-izena + + + + Zerbait gertatu da erabiltzaile-izenarekin, eta jada ez dago kontuari esleitua. Berriro konfiguratu, edo beste bat aukera dezakezu. + + Konpondu orain @@ -1156,8 +1176,8 @@ Talde berria Gonbidatu lagunak SMSa erabili - Itxura - Gehitu argazki bat + Txataren koloreak + Gehitu profileko argazkia Erantzunak @@ -1472,10 +1492,10 @@ Desblokeatu %1$s erabiltzaileari baimena eman nahi diozu zuri mezuak bidaltzeko eta zure izena eta argazkia beraiekin partekatzeko. Zuk onartu arte ez dute jakingo beraien mezua ikusi duzunik. - %1$s erabiltzaileari baimena eman nahi diozu zuri mezuak bidaltzeko eta zure izena eta argazkia beraiekin partzekatzeko. Ez duzu mezurik jasoko zuk desblokeatu arte. + Zuri mezuak bidaltzeko baimena eman nahi diozu %1$s erabiltzaileari, eta zure izena eta argazkia harekin partzekatu? Desblokeatu arte, ez duzu jasoko haren mezurik. - Zuri mezuak bidaltzeko baimena eman nahi diozu %1$s(r)i? Desblokeatzen duzun arte, ez duzu jasoko haren mezurik. - %1$s(r)en berritasun eta albisteak jaso nahi dituzu? Desblokeatzen duzun arte, ez duzu eguneratzerik jasoko. + Zuri mezuak bidaltzeko baimena eman nahi diozu %1$s erabiltzaileari? Desblokeatu arte, ez duzu jasoko haren mezurik. + %1$s erabiltzailearen berritasun eta albisteak jaso nahi dituzu? Desblokeatzen duzun arte, ez duzu eguneratzerik jasoko. Jarraitu nahi duzu talde honekin duzun solasaldian eta zure izena eta argazkia bere kideekin partekatu? Eguneratu talde hau ezaugarri berriak, hala nola @aipamenak eta administratzaileak, aktibatzeko. Bere izen edo argazkia partekatu ez duten kideek taldera sartzeko gonbidatuak izango dira. Jarauntsitako Talde Hau ezin da erabili jadanik oso handia delako. Gehienezko talde tamaina %1$d da. @@ -1483,7 +1503,7 @@ Talde honetara sartu eta zure izena eta argazkia kideekin partekatu nahi duzu? Zuk onartu arte, ez dute jakingo beraien mezuak ikusi duzunik. Talde honetan sartu, eta izena eta argazkia bertako kideekin partekatu nahi duzu? Onartu arte, ez duzu haien mezurik ikusiko. Talde honetara sartu nahi duzu? Onartu arte ez dute jakingo beraien mezuak ikusi dituzunik. - Desblokeatu talde hau eta zure izena eta argazkia beraiekin partekatu? Ez duzu mezurik jasoko zuk desblokeatu arte. + Talde hau desblokeatu nahi duzu, eta zure izena eta argazkia hartako kideekin partekatu? Desblokeatu arte, ez duzu mezurik jasoko. Ikusi %1$s taldeko kidea @@ -1584,9 +1604,20 @@ Sortu PIN berria + + Bidali SMS kodea + + Signal-eko erregistroa: Laguntza behar dut Android-erako PINa berriro erregistratzeko + + PINa zeuk sortutako kode bat da, %1$d digitu baino gehiagokoa, eta zenbakizkoa nahiz alfanumerikoa izan daiteke.\n\nPINa gogoratzen ez baduzu, beste bat sor dezakezu. + + PINa gogoratzen ez baduzu, beste bat sor dezakezu. + + PINa idazteko saiakerak agortu zaizkizu, baina, beste PIN bat sortzen baduzu, oraindik ere Signal-eko kontuan sar zaitezke. + Markatutako zenbakia ez dago Signal-era erregistratuta. SMS bidez gonbidatu nahiko zenuke? - Ez baduzu modu manualean segurtasun kopiak egiten, PINa desaktibatuz gero datu guztiak galduko dituzu signal berriro erregistratzen duzunean. Ezin duzu erregistratzeko blokeoa aktibatu PINa desaktibatuta dagoen bitartean. + PINa desgaitzen baduzu, datu guztiak galduko dituzu Signal berriro erregistratzen duzunean, babeskopiak eskuz egin eta leheneratu ezean. PINa desgaituta badago, ezin duzu aktibatu erregistratzeko blokeoa. Desaktibatu PINa @@ -1711,9 +1742,9 @@ Kamera - Soinua aktibatu + Aktibatu jakinarazpenak - Isildu + Desaktibatu jakinarazpenak Deitu @@ -1731,7 +1762,7 @@ Ez duzu bere audio edo bideorik jasoko eta ez dute zurerik jasoko. Ezin da %1$s erabiltzailearen audio eta bideorik jaso. Ezin da %1$s erabiltzailearen audio eta bideorik jaso. - Hau zure segurtasun-zenbakiaren aldaketa egiaztatu ez duzulako, euren gailuan arazoren bat dutelako edo blokeatu egin zaituztelako izan daiteke. + Beharbada ez duzu egiaztatu segurtasun-zenbakiaren aldaketa, arazoren bat du gailuan, edo blokeatu egin zaituzte. Pasatu hatza pantaila partekatua ikusteko. @@ -1768,11 +1799,18 @@ Lagunekin konektatzeko eta mezuak bidali ahal izan ditzazun, Signal-ek kontaktuak eta multimedia-edukia atzitzeko baimena behar du. Signal-en kontaktuak aurkitzeko aukera pribatuaren bidez kargatzen dira kontaktuak; hau da, muturretik muturrera enkriptatuta daude, eta Signal zerbitzuak ezin ditu ikusi. Lagunekin konektatzeko, Signal-ek kontaktuak eta multimedia-edukia atzitzeko baimena behar du. Signal-en kontaktuak aurkitzeko aukera pribatuaren bidez kargatzen dira kontaktuak; hau da, muturretik muturrera enkriptatuta daude, eta Signal zerbitzuak ezin ditu ikusi. Zenbakia erregistratzeko saiakera gehiegi egin dituzu. Mesedez, saia zaitez beranduago. + + Saiakera gehiegi egin dituzu zenbaki hau erregistratzeko. Saiatu berriro denbora hau igarotakoan: %1$s. Ezin da zerbitzura konektatu. Egiaztatu sarearen ezarpenak eta saiatu berriz. Zenbaki-formatu ez-estandarra Idatzi duzun zenbakiak (%1$s) formatu ez-estandar bat du.\n\n%2$s esan nahi al zenuen? Molly Android - Telefono-zenbakien formatua + Deia eskatu da + + SMSa eskatu da + + Egiaztapen-kodea eskatu da Hurrats %1$d falta zaizu arazterko txosten bat bidaltzeko. %1$d hurrats falta zaizkizu arazterko txosten bat bidaltzeko. @@ -1792,6 +1830,16 @@ Deitu Egiaztapen-kodea Bidali kodea berriro + + Erregistratzeko arazoak dituzu? + + • Ziurtatu telefonoak seinalea duela SMSa edo deia jasotzeko.\n • Egiaztatu zure zenbakian telefono-deiak jaso ditzakezula.\n • Egiaztatu telefono-zenbakia zuzen idatzi duzula. + + Informazio gehiago lortzeko, jarraitu arazoak konpontzeko urrats hauei edo jarri laguntza-zerbitzuarekin harremanetan + + arazoak konpontzeko urrats hauek + + Jarri harremanetan laguntza-zerbitzuarekin Aktibatu Erregistratzeko Blokeoa? @@ -1951,13 +1999,17 @@ Bereizgarri bat aktibatu duzu - %1$s emojiarekin erreakzionatu du zure istorioa ikustean + Zure istorioari bidalitako erreakzioa: %1$s - %1$s emojiarekin erreakzionatu duzu haren istorioa ikustean + Haren istorioari bidalitako erreakzioa: %1$s Ordainketa Programatutako mezua + + Bateratu da mezuen historia + + %1$s zenbakia %2$s erabiltzailearena da Molly eguneratu @@ -2087,14 +2139,16 @@ SMS ez segurua %1$s%2$s Kontaktua - %1$s izan da \"%2$s\"rekiko erreakzioa. - %1$s izan da zure bideoarekiko erreakzioa. - %1$s izan da zure irudiarekiko erreakzioa. - %1$s(e)k zure GIFari buruzko erreakzio bat bidali du. - %1$s izan da zure fitxategiarekiko erreakzioa. - %1$s izan da zure audioarekiko erreakzioa. - %1$s izan da zure behin ikusteko mediarekiko erreakzioa. - %1$s izan da zure eranskailuarekiko erreakzioa. + \"%2$s\" mezuari bidalitako erreakzioa: %1$s. + Zure bideoari bidalitako erreakzioa: %1$s. + Zure irudiari bidalitako erreakzioa: %1$s. + Zure GIFari bidalitako erreakzioa: %1$s. + Zure fitxategiari bidalitako erreakzioa: %1$s. + Zure audioari bidalitako erreakzioa: %1$s. + Zure behin ikusteko multimedia-edukiari bidalitako erreakzioa: %1$s. + + Reacted %1$s to your payment. + Zure eranskailuari bidalitako erreakzioa: %1$s. Mezu hau ezabatu egin da. Desaktibatu nahi duzu kontaktu bat batzen denean bidaltzen diren jakinarazpenak? Berriro aktiba ditzakezu Signal > Ezarpenak > Jakinarazpenak atalean. @@ -2487,8 +2541,8 @@ Entregarekin erlazionatutako arazoa - Ezin izan zaizu entregatu %1$s(e)k bidalitako mezu, sticker, erreakzio edo irakurragiri bat. Agian zuzenean bidali nahi izan dizu, edo talde batean. - Ezin izan zaizu entregatu %1$s(e)k bidalitako mezu, sticker, erreakzio edo irakurragiri bat. + Ezin izan dizugu bidali honen mezu, eranskailu, erreakzio edo irakurragiririk: %1$s. Baliteke zuzenean edo talde batean bidaltzen saiatu izatea. + Ezin izan dizugu bidali honen mezu, eranskailu, erreakzio edo irakurragiririk: %1$s. Izena (beharrezkoa) @@ -2555,7 +2609,7 @@ Falta dira - Honi bidalia + Honi bidali zaio: Honengandik bidalia Honi banatua Honek irakurria @@ -2635,10 +2689,10 @@ Erabili lehenetsia Pertsonalizatua - Isildu ordu baterako - Mututu 8 orduz - Isildu egun baterako - Isildu 7 egunetarako + Desaktibatu jakinarazpenak ordubetez + Desaktibatu jakinarazpenak 8 orduz + Desaktibatu jakinarazpenak egun batez + Desaktibatu jakinarazpenak 7 egunez Beti Lehenetsia @@ -2675,9 +2729,9 @@ Erabili kontaktuen-zerrendako irudiak Erakutsi sistemaren kontaktuen irudiak, baldin badaude - Mantendu txat mututuak artxibatuta + Mantendu jakinarazpenak desaktibatuta dauzkaten txatak artxibatuta - Mezu berriak jasotzean, artxibatutako txat mututuek artxibatuta jarraituko dute. + Mezu berriak jasotzean, artxibatuta dauden eta jakinarazpenak desaktibatuta dauzkaten txatek artxibatuta jarraituko dute. Sortu esteken aurrebistak Eskuratu esteken aurrebistak zuzenean webguneetatik eta erabili bidaltzen dituzun mezuetan. Aldatu pasaesaldia @@ -2971,7 +3025,7 @@ Bidalitako ordainketa Jasotako ordainketa %1$sordainketa burututa - Blokeo zenbakia + Blokeatu zenbakia Transferentzia @@ -3295,6 +3349,8 @@ Idatzi zure PINa Idatzi konturako sortu duzun PIN-a. PIN hau ez da zure SMS berifikazio gakoa. + + Idatzi zure konturako sortu duzun PINa. Idatzi PIN alfanumerikoa Idatzi zenbakizko PINa PIN okerra. Saia zaitez berriro. @@ -3398,7 +3454,10 @@ Babeskopiak oso handia den fitxategi bat dauka, eta ezin da egin haren babeskopia. Ezaba ezazu eta sortu beste babeskopia bat. Sakatu babeskopiak kudeatzeko. Zenbaki okerra? + Dei iezadazu (%1$02d:%2$02d) + + Bidali berriro kodea (%1$02d:%2$02d) Harremanetan jarri Signal Laguntza Taldearekin Signal Errejistratzea - Androiderako Egiaztatze Kodea Kodea ez da zuzena @@ -3406,6 +3465,18 @@ Ezezaguna Ikusi nire telefono zenbakia Topa nazazu telefono zenbakia erabiliz + + Telefono-zenbakia + + Aukeratu nork ikus dezakeen zure telefono-zenbakia eta nor jar daitekeen zurekin harremanetan Molly-en. + + Nork ikus dezake nire zenbakia? + + Ez du inork ikusiko zure telefonoa Molly-en. + + Nork aurki nazake zenbakia erabiliz? + + Zure mezuak jasotzen dituzten pertsonek zure telefono-zenbakia ikusi ahalko dute. Telefonoko kontaktuetan zure zenbakia duten pertsonek Molly-en ere ikusiko dute. Guztiak Nire kontaktuak Inor ez @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" blokeatua izan da. - Ezin izan da \"%1$s\" blokeatu - \"%1$s\" desblokeatua izan da. + \"%1$s\" blokeatu da. + Ezin izan da blokeatu \"%1$s\" + \"%1$s\" desblokeatu da. Berrikusi Kideak @@ -3667,17 +3738,17 @@ Wifi-konexio ahula. Datu-konexiora aldatu da. - Zure kontua ezabatzean zera gertatuko da: + Kontua ezabatuz gero: Idatzi zure telefono zenbakia Ezabatu kontua - Ezabatu kontuaren informazioa eta profileko argazkia + Ezabatu kontuko informazioa eta profileko argazkia Ezabatu mezu guztiak Ezabatu %1$s ordainketa-kontuan Ez duzu herrialde-kodea idatzi Ez duzu zenbakia idatzi Idatzi duzun telefono zenbakia eta zure kontuarena ez datoz bat. Ziur zaude kontua ezabatu nahi duzula? - Signal-eko kontua ezabatu, eta aplikazioa berrezarri egingo da. Prozesua amaitzean, aplikazioa itxi egingo da. + Signal-eko kontua ezabatuko da, eta aplikazioa berrezarriko. Aplikazioa itxi egingo da prozesua amaitu ondoren. Ezin izan dira ezabatu datu lokalak. Sistemaren aplikazioen ezarpenetan ezabatu beharko dituzu. Aplikazioaren Ezarpenak Ireki @@ -3784,12 +3855,12 @@ Diru-zorroa deskatibatu Zure balantzea - Ordainketak desaktibatu aurretik, zure fondoak beste diru-zorro helbide batera transferitzea gomendatzen da. Zure funtsak orain ez transferitzea aukeratzen baduzu, zure diru-zorroan geratuko dira Molly-i lotuta, ordainketak berriro aktibatzen badituzu. + Ordainketak desaktibatu aurretik, funtsak beste zorro baten helbidera transferitzea gomendatzen da. Dirua orain ez transferitzea aukeratzen baduzu, ordainketak berriro aktibatzen badituzu, Molly-ekin lotutako zorroan jarraituko du. Transferitu gainerako balantzea Desaktibatu transferitu gabe Desaktibatu Desaktibatu transferitu gabe? - Zure saldoa Molly-i lotutako diru-zorroan geratuko da ordainketak berriro aktibatzea aukeratzen baduzu. + Ordainketak berriro aktibatzea aukeratzen baduzu, saldoa Molly-ekin lotutako zorroan geratuko da. Errorea diru-zorroa desaktibatzean. @@ -4008,7 +4079,7 @@ Mezularitza Mezuen desagerpena App segurtasuna - Blokeatu pantaila-argazkiak arestikoen zerrendan eta aplikazioaren barruan + Blokeatu pantaila-argazkiak azkenaldikoen zerrendan eta aplikazioaren barruan Signal mezuak eta deiak, beti deiak transmititu, eta igorle zigilatua Txat berrietarako tenporizadore lehenetsia Ezarri lehenetsitako desagerpen mezuen tenporizadore bat zuk hasitako txat berri guztientzat. @@ -4165,9 +4236,9 @@ Deitu - Isildu + Desaktibatu jakinarazpenak - Isildu + Jakinarazpenak desaktibatuta Bilatu Mezuen desagerpena @@ -4186,9 +4257,9 @@ Eskaerak & gonbidapenak Talderako esteka Gehitu kontaktu gisa - Soinua aktibatu - Elkarrizketa isilarazi %1$s arte - Elkarrizketa isilduta betirako + Aktibatu jakinarazpenak + Desaktibatu elkarrizketaren jakinarazpenak %1$s arte + Elkarrizketaren jakinarazpenak betiko desaktibatu dira Telefono-zenbakia arbelean kopiatu da. Telefono zenbakia Lortu zure profilerako bereizgarriak Signal-i lagunduz. Sakatu bereizgarri bat gehiago jakiteko. @@ -4205,7 +4276,7 @@ Desaktibatu jakinarazpenak - Ez mututua + Jakinarazpenak ez daude desaktibatuta Aipamenak Jakinarazi beti Ez jakinarazi @@ -4242,7 +4313,7 @@ Kendu da %1$s - Blokeatu da %1$s + %1$s blokeatu da Ezin da kendu %1$s @@ -4330,7 +4401,7 @@ Gehitu istorioan Gehitu mezu bat Gehitu erantzun bat - Honi bidali + Bidali honi: Mezua behin ikusi Elementu bat edo gehiago handiegiak dira Elementu bat edo gehiago handiegiak dira @@ -4453,7 +4524,7 @@ Hileko dohaintza bertan behera utzi da Emaile-bereizgarria iraungi egin da, eta jada ez dago ikusgai profilean. - Ekarpen puntual bat eginda, beste 30 egunez aktiba dezakezu emaile-bereizgarria. + Ekarpen bat eginda, bultzada-bereizgarria beste 30 egunez berraktiba dezakezu. Signal erabiltzen jarrai dezakezu, baina, zuretzat eraikitako teknologiari laguntzeko, hilero dohaintza bat eginez dohaintza-emaile bihurtzea eskertuko genizuke. Bilaka zaitez sostengatzaile @@ -4465,7 +4536,7 @@ Hileroko dohaintza bertan behera utzi da, ezin izan dugulako prozesatu ordainketa. Bereizgarria jada ez dago ikusgai profilean. Hileroko dohaintza bertan behera utzi da. %1$s Aurrerantzean, %2$s bereizgarria ez da ikusgai egongo profilean. - Signal erabiltzen jarrai dezakezu, baina aplikazioari laguntzeko eta bereizgarria berriro aktibatzeko, berritu harpidetza. + Signal erabiltzen jarrai dezakezu, baina, aplikazioari laguntzeko eta bereizgarria berriro aktibatzeko, berritu orain. Berritu harpidetza Joan Google Play-ra @@ -4508,7 +4579,7 @@ Sareko errore bat dela eta, ezin izan da bidali dohaintza. Egiaztatu Internetera konektatuta zaudela eta saiatu berriro. - %1$s erabiltzailearentzako dohaintza + Dohaintza honen izenean: %1$s %1$s erabiltzaileak dohaitza bat egin dio Signal-i zure izenean @@ -4853,13 +4924,13 @@ Ezin diozu erantzun istorio honi, jada ez zarelako talde honetako kidea. - Istorioari buruzko erreakzio bat bidali du + Erreakzio bat bidali dio istorioari Ikustaldiak Erantzunak - Bidali istorio honi buruzko erreakzio bat + Bidali erreakzio bat istorio honi %1$sri modu pribatuan erantzuten @@ -4905,11 +4976,11 @@ Aukeratu nork ikus dezakeen istorioa. Aldaketek ez dute eraginik izango lehenago bidalitako istorioetan. - Erantzunak & erreakzioak + Erantzunak eta erreakzioak - Baimendu erantzunak & erreakzioak + Baimendu erantzunak eta erreakzioak - Eman istorioa ikus dezaketenei erreakzioak bidaltzeko aukera + Utzi zure istorioa ikus dezakeen jendeari erreakzioak bidaltzen eta erantzuten Signal-eko konexioak @@ -5057,11 +5128,11 @@ - %1$s(r)en istorioari buruzko erreakzio bat bidali duzu + %1$s erabiltzailearen istorioari erreakzio bat bidali diozu - Zure istorioari buruzko erreakzio bat bidali du + Zure istorioari erreakzio bat bidali dio - Istorio bati buruzko erreakzio bat bidali du + Istorio bati erreakzio bat bidali dio @@ -5087,7 +5158,7 @@ Berretsi dohaintza - Honi bidali + Bidali honi: Mezu pribatu baten bidez emango zaio hartzaileari dohaintzaren berri. Gehitu mezua behean. @@ -5601,5 +5672,15 @@ Ezabatu erabiltzaile-izena + + + h + + min + + Ezarri + + Pantaila blokeatzeko gutxieneko denbora minutu bat da. + diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 095c9f41fe..d0bb8eed7d 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -14,13 +14,14 @@ + بله خیر پاک کردن لطفاً صبر کنید… ذخیره - یادداشت برای خود + یادداشت به خود @@ -98,9 +99,9 @@ کاربران مسدود شده افزودن کاربر مسدود شده - کاربری که مسدود شده است، امکان برقراری تماس یا ارسال پیام به شما را ندارد. - هیچ کاربر مسدود شده‌ای وجود ندارد - مسدود کردن کاربر؟ + کاربران مسدودشده امکان برقراری تماس یا ارسال پیام به شما را نخواهند داشت. + هیچ کاربر مسدودشده‌ای وجود ندارد + کاربر مسدود شود؟ «%1$s» امکان برقراری تماس یا ارسال پیام به شما را نخواهد داشت. مسدود کردن @@ -138,8 +139,8 @@ ادامه - مسدود کردن و ترک گروه %1$s؟ - مسدود کردن %1$s؟ + گروه %1$s را مسدود و آن را ترک می‌کنید؟ + %1$s مسدود شود؟ شما دیگر از این گروه پیام و به‌روزرسانی دریافت نخواهید کرد و اعضا قادر نخواهند بود دوباره شما را به این گروه اضافه کنند. اعضای گروه قادر نخواهند بود دوباره شما را به این گروه اضافه کنند. اعضای گروه می‌توانند دوباره شما را به این گروه اضافه کنند. @@ -147,16 +148,16 @@ شما می‌توانید به دیگران پیام دهید و با آن‌ها تماس بگیرید و نام و عکس شما با آن‌ها به اشتراک گذاشته خواهد شد. شما قادر خواهید بود به یکدیگر پیام دهید. - افراد مسدود شده قادر به برقراری تماس با شما یا ارسال پیام به شما نخواهند بود. - افراد مسدود شده قادر به ارسال پیام به شما نخواهند بود. + افراد مسدودشده قادر به برقراری تماس با شما یا ارسال پیام به شما نخواهند بود. + افراد مسدودشده قادر به ارسال پیام به شما نخواهند بود. دریافت به‌روزرسانی‌ها و اخبار سیگنال را مسدود کنید. دریافت به‌روزرسانی‌ها و اخبار سیگنال را از سر بگیرید. - رفع مسدودیت %1$s؟ + %1$s رفع مسدودیت شود؟ مسدود کردن - مسدود کردن و ترک گروه - گزارش اسپم و مسدود کردن + مسدود کردن و ترک + گزارش هرزنامه و مسدود کردن امروز @@ -366,7 +367,7 @@ خطا در ارسال رسانه - اسپم گزارش داده و مسدود شد. + به‌عنوان هرزنامه گزارش و مسدود شد. در حال حاضر ارسال پیامک غیرفعال است. می‌توانید پیام‌های خود را به برنامه دیگری در تلفنتان صادر کنید. @@ -447,7 +448,7 @@ %1$s روشن - مسدود کردن درخواست؟ + درخواست پیوستن مسدود شود؟ %1$s قادر نخواهد بود به این گروه بپیوندد یا از طریق پیوند گروه درخواست پیوستن به این گروه را بدهد. آن‌ها هنوز می‌توانند به صورت دستی به گروه اضافه شوند. @@ -455,7 +456,7 @@ لغو - مسدود شده + مسدود شد برداشتن فیلتر @@ -480,41 +481,41 @@ %1$d مکالمه به صندوق ورودی انتقال یافت - خوانده - خوانده + خوانده‌شده + خوانده‌شده نخوانده نخوانده - سنجاق - سنجاق + سنجاق شود + سنجاق شود برداشتن سنجاق برداشتن سنجاق - بی‌صدا - بی‌صدا + بی‌‌صدا شود + بی‌صدا شود - باصدا - باصدا + صدادار شود + صدادار شود انتخاب - بایگانی - بایگانی + بایگانی شود + بایگانی شود - خروج از بایگانی - خروج از بایگانی + از بایگانی خارج شود + از بایگانی خارج شود - پاک کردن - پاک کردن + پاک شود + پاک شود انتخاب همه @@ -542,6 +543,15 @@ +%1$d + + دستگاه‌هایتان را دوباره پیوند دهید + + با لغو ثبت‌نام دستگاهتان، پیوند دستگاه‌هایی که اضافه کردید لغو شد. برای پیوند دادن مجدد هریک از دستگاه‌هایتان، به «تنظیمات» بروید. + + بازکردن تنظیمات + + بعداً + اعضا را انتخاب کنید @@ -935,7 +945,7 @@ برای اشاره‌ها من را آگاه کن - دریافت اعلان وقتی به شما در گفتگو‌ها با اعلان‌های بسته شده اشاره می‌شود؟ + می‌خواهید وقتی در گفتگوهای بی‌صداشده به شما اشاره می‌شود، اعلان دریافت کنید؟ همیشه من را آگاه کن من را آگاه نکن @@ -953,6 +963,16 @@ نام کاربری ایجاد شد نام کاربری کپی شد + + پاک کردن نام کاربری انجام نشد. بعداً دوباره امتحان کنید. + + نام کاربری پاک شد + + + + خطایی در مورد نام کاربری شما رخ داد، این نام دیگر به حساب شما تعلق ندارد. می‌توانید سعی کنید و آن را دوباره تنظیم کنید یا نام کاربری دیگری انتخاب کنید. + + اکنون اصلاح شود @@ -1156,8 +1176,8 @@ گروه جدید دعوت دوستان استفاده از پیامک - ظاهر برنامه - افزودن عکس + رنگ‌های گفتگو + یک عکس نمایه اضافه کنید پاسخ‌ها @@ -1472,10 +1492,10 @@ رفع مسدودیت آیا به %1$s اجازه می‌دهید که به شما پیام دهد و نام و عکس خود را با او به اشتراک می‌گذارید؟ تا زمانی که نپذیرید او متوجه نمی‌شود که پیامش را خوانده‌اید. - آیا به %1$s اجازه می‌دهید که به شما پیام دهد و نام و عکس خود را با او به اشتراک می‌گذارید؟ تا زمانی که او را رفع مسدودیت نکنید هیچ پیامی دریافت نخواهید کرد. + می‌خواهید به %1$s اجازه دهید به شما پیام دهد و نام و عکستان را با او به اشتراک بگذارید؟ تا وقتی او را رفع مسدودیت نکنید هیچ پیامی دریافت نخواهید کرد. - اجازه می‌دهید %1$s به شما پیام دهد؟ تا زمانی که آنها را رفع مسدودیت نکنید، هیچ پیامی دریافت نخواهید کرد. - دریافت به‌روزرسانی‌ها و اخبار از %1$s؟ تا مسدودیت آن‌ها را رفع نکنید هیچ به‌روزرسانی‌ای دریافت نخواهید کرد. + می‌خواهید اجازه دهید %1$s به شما پیام دهد؟ تا وقتی او را رفع مسدودیت نکنید، هیچ پیامی دریافت نخواهید کرد. + می‌خواهید به‌روزرسانی‌ها و تازه‌های %1$s را دریافت کنید؟ تا مسدودیت آن‌ها را رفع نکنید به‌روزرسانی دریافت نخواهید کرد. آیا به مکالمهٔ خود این گروه ادامه می‌دهید و نام و عکس خود را با اعضای آن به اشتراک می‌گذارید؟ این گروه را برای فعال کردن قابلیت‌ها‌ی جدید همانند اشاره‌ها@ و مدیران ارتقا دهید. اعضایی که که نام یا عکس خود را در این گروه به اشتراک نگذاشته‌اند برای پیوستن دعوت خواهند شد. این گروه قدیمی دیگر نمی‌تواند استفاده شود چون بسیار بزرگ است. حداکثر اندازهٔ گروه %1$d است. @@ -1483,7 +1503,7 @@ آیا می‌خواهید به این گروه بپیوندید و نام و عکس خود را با اعضای آن به اشتراک بگذارید؟ تا زمانی که نپذیرید آن‌ها متوجه نمی‌شوند که پیام‌شان را خوانده‌اید. به این گروه می‌پیوندید و نام و عکس خود را با اعضای آن به اشتراک می‌گذارید؟ تا زمانی که نپذیرید پیام‌های آنها را نخواهید دید. به این گروه می‌پیوندید؟ تا زمانی که نپذیرید آن‌ها متوجه نخواهند شد که پیام‌شان را خوانده‌اید. - این گروه را رفع مسدودیت می‌کنید و نام و عکس‌ خود را با اعضای آن به اشتراک می‌گذارید؟ تا زمانی که آن‌ها را رفع مسدودیت نکنید هیچ پیامی دریافت نخواهید کرد. + می‌خواهید این گروه را رفع مسدودیت کنید و نام و عکستان را با اعضای آن به اشتراک بگذارید؟ تا وقتی آن را رفع مسدودیت نکنید هیچ پیامی دریافت نخواهید کرد. مشاهده عضو %1$s @@ -1584,9 +1604,20 @@ ایجاد پین جدید + + ارسال کد پیامکی + + ثبت‌نام سیگنال - نیازمند کمک برای ثبت مجدد پین برای اندروید + + پین شما یک عبارت %1$d+‎ رقمی است که شما ساخته‌اید و می‌تواند عدد یا حروف باشد.\n\nاگر پین خود را به یاد نمی‌آورید، می‌توانید پین جدیدی بسازید. + + اگر پین خود را به یاد نمی‌آورید، می‌توانید پین جدیدی بسازید. + + دفعات مجاز حدس زدن پین شما تمام شده است، اما همچنان می‌توانید با ایجاد پین جدید، به حساب سیگنال خود دسترسی پیدا کنید. + هشدار - اگر پین را غیرفعال کنید، هنگامی که دوباره در سیگنال ثبت‌نام می‌کنید، تمامی داده‌ها را از دست خواهید داد، مگر اینکه به صورت دستی پشتیبان‌گیری و بازگردانی کنید. هنگامی که پین غیرفعال است نمی‌توانید قفل ثبت‌نام را روشن کنید. + اگر پین را غیرفعال کنید، وقتی دوباره در سیگنال ثبت‌نام می‌کنید، همه داده‌ها را از دست خواهید داد، مگر اینکه به‌صورت دستی پشتیبان‌گیری و بازگردانی کنید. وقتی پین غیرفعال است نمی‌توانید قفل ثبت‌نام را روشن کنید. غیرفعال کردن پین @@ -1713,7 +1744,7 @@ باصدا - بستن + بی‌صدا زنگ زدن @@ -1731,7 +1762,7 @@ شما صدا و تصویر آ‌ن‌ها را دریافت نمی‌کنید و آن‌ها هم صدا و تصویر شما را دریافت نخواهند کرد. دریافت صدا و تصویر از %1$s ممکن نیست دریافت صدا و تصویر از %1$s ممکن نیست - این می‌تواند به این خاطر باشد که آن‌ها تغییر شمارهٔ ایمنی شما را وارسی نکرده‌اند، مشکلی برای دستگاه‌شان پیش آمده یا شما را مسدود کرده‌اند. + این می‌تواند به این دلیل باشد که او تغییر شماره ایمنی شما را تأیید نکرده، مشکلی برای دستگاهش پیش آمده یا شما را مسدود کرده است. برای مشاهدهٔ اشتراک صفحه به انگشت‌تان را به کناره‌ها بکشید @@ -1768,11 +1799,18 @@ سیگنال به مجوزها‌ی مخاطبین و رسانه‌ها برای کمک به متصل کردن شما به دوستان و ارسال پیام‌ها نیاز دارد. مخاطبین شما با استفاده از اکتشاف خصوصی مخاطب سیگنال بارگذاری می‌شوند، که این یعنی آن‌ها سرتاسر رمزگذاری شده هستند و هیچوقت برای سرویس سیگنال قابل رؤیت نیستند. سیگنال به مجوز مخاطبین برای کمک به متصل کردن شما به دوستان نیاز دارد. مخاطبین شما با استفاده از اکتشاف خصوصی مخاطب سیگنال بارگذاری می‌شوند، که این یعنی آن‌ها سرتاسر رمزگذاری شده هستند و هیچوقت برای سرویس سیگنال قابل رؤیت نیستند. شما تلاش‌های بسیاری برای ثبت‌نام این شماره انجام داده‌اید. لطفاً دوباره امتحان کنید. + + دفعات تلاش‌تان برای ثبت این شماره بیش از حد مجاز بوده است. لطفاً بعد از %1$s دوباره تلاش کنید. اتصال به سرویس ممکن نیست. لطفاً اتصال شبکه را بررسی کرده و دوباره امتحان کنید. قالب شمارهٔ غیر-استاندارد به نظر می‌رسد شماره‌ای که وارد کردید (%1$s) در قالبی غیر-استاندارد باشد.\n\nآیا منظورتان %2$s بود؟ سیگنال اندروید - قالب شماره‌تلفن + درخواست تماس داده شد + + پیامک درخواست شد + + کد تأیید درخواست شد شما %1$d قدم با ارسال گزارش عیب‌یابی فاصله دارید. شما%1$d قدم با ارسال گزارش عیب‌یابی فاصله دارید. @@ -1792,6 +1830,16 @@ تماس کد تآیید ارسال مجدد کد + + مشکلی در ثبت‌نام دارید؟ + + • مطمئن شوید که تلفنتان برای دریافت پیامک یا تماس آنتن دارد\n • تأیید کنید که می‌توانید با این شماره تماس تلفنی دریافت کنید\n • بررسی کنید که شماره تلفن خود را به‌درستی وارد کرده‌اید. + + برای اطلاعات بیشتر، لطفاً این مراحل عیب‌یابی را دنبال کنید یا با پشتیبانی تماس بگیرید + + این مراحل عیب‌یابی + + تماس با پشتیبانی روشن کردن قفل ثبت‌نام؟ @@ -1953,11 +2001,15 @@ با %1$s به استوری شما واکنش نشان داد - با %1$s به استوری او واکنش نشان داد + با %1$s به استوری او واکنش نشان دادید پرداخت پیام زمان‌بندی‌شده + + تاریخچه پیام شما ادغام شده است + + %1$s متعلق به %2$s است به‌روزرسانی سیگنال @@ -2030,7 +2082,7 @@ پیام چندرسانه‌ای برای نشست ناموجود رمزگذاری شده است - بستن اعلان‌‌ها + بی‌صدا کردن اعلان‌‌ها در حال وارد کردن @@ -2087,14 +2139,16 @@ پیامک ناامن %1$s %2$s مخاطب - %1$s به «%2$s» واکنش نشان داد. + با %1$s به «%2$s» واکنش نشان داد. %1$s به ویدئوی شما واکنش نشان داد. - %1$s به تصویر شما واکنش نشان داد. - به گیف شما واکنش %1$s داد. - %1$s به فایل شما واکنش نشان داد. - %1$s به فایل صوتی شما واکنش نشان داد. - %1$s به رسانه‌ٔ یکبار مصرف شما واکنش نشان داد. - %1$s به استیکر شما واکنش نشان داد. + با %1$s به عکس شما واکنش نشان داد. + با %1$s به گیف شما واکنش داد. + با %1$s به فایل شما واکنش نشان داد. + با %1$s به فایل صوتی شما واکنش نشان داد. + با %1$s به رسانه یک‌بارمصرف شما واکنش نشان داد. + + با %1$s به پرداخت شما واکنش نشان داد. + با %1$s به استیکر شما واکنش نشان داد. این پیام پاک شده بود. می‌خواهید اعلان‌های پیوستن مخاطبان به سیگنال را خاموش کنید؟ می‌توانید آن‌ها را دوباره در سیگنال > تنظیمات > اعلان‌ها فعال کنید. @@ -2487,8 +2541,8 @@ اشکال تحویل - یک پیام، استیکر، واکنش، یا رسید خوانده شدن از %1$s نتوانست به شما تحویل داده شود. آن‌ها ممکن است آن را به طور مستقیم، یا در یک گروه به شما ارسال کرده باشند. - یک پیام، استیکر، واکنش، یا رسید خوانده شدن از %1$s نتوانست به شما تحویل داده شود. + یک پیام، استیکر، واکنش، یا رسید خوانده شدن از %1$s به شما قابل تحویل نبود. ممکن است سعی کرده باشند آن را به‌طور مستقیم یا در گروه به شما ارسال کنند. + یک پیام، استیکر، واکنش، یا رسید خوانده شدن از %1$s به شما قابل تحویل نبود. نام کوچک (الزامی) @@ -2635,10 +2689,10 @@ استفاده از پیش‌فرض سیستم استفاده از سفارشی - بستن برای ۱ ساعت - بستن برای ۸ ساعت - بستن برای ۱ روز - بستن برای ۷ روز + بی‌صدا برای ۱ ساعت + بی‌صدا برای ۸ ساعت + بی‌صدا برای ۱ روز + بی‌صدا برای ۷ روز همیشه تنظیمات پیش‌فرض سیستم @@ -2675,9 +2729,9 @@ استفاده از عکس‌های دفترچهٔ تلفن نمایش عکس‌های مخاطبین از دفترچهٔ تلفن در صورت وجود - گفتگوهای بی‌صدا را در بایگانی نگه دارید + نگه داشتن گفتگوهای بی‌صدا در بایگانی - گفتگوهای بی‌صدا که بایگانی می‌شوند، پس از دریافت پیام جدید نیز در بایگانی می‌مانند. + گفتگوهای بی‌صدا که بایگانی می‌شوند، پس از دریافت پیام جدید همچنان در بایگانی می‌مانند. تولید پیش‌نمایش‌‌های پیوند پیش‌نمایش‌های پیوند را به صورت مستقیم از وب‌سایت‌ها برای پیام‌هایی که ارسال می‌کنید، دریافت کنید. تغییر گذرواژه @@ -2757,7 +2811,7 @@ تنظیمات پیشرفتهٔ پین تماس‌ها و پیام‌های خصوصی رایگان برای کاربران سیگنال ارسال گزارش عیب‌یابی - پاک کردن حساب کاربری + پاک کردن حساب حالت سازگاری «تماس WiFi» اگر دستگاه شما از تحویل پیامک/پیام چندرسانه‌ای بر روی شبکهٔ WiFi استفاده می‌کند، این گزینه را فعال کنید (فقط زمانی فعال کنید، که «تماس WiFi» بر روی دستگاه شما فعال است) صفحه‌کلید ناشناس @@ -2971,7 +3025,7 @@ پرداخت ارسال شد پرداخت دریافت شد پرداخت کامل شد %1$s - مسدود کردن شماره + شماره بلاک انتقال @@ -3128,10 +3182,10 @@ - باز کردن + صدادار کردن - بستن اعلان‌ها + بی‌صدا کردن اعلان‌ها تنظیمات گروه @@ -3295,6 +3349,8 @@ پین خود را وارد کنید پین ایجاد شده برای حساب کاربری خود را وارد کنید. این کد با کد وارسی پیامکی شما تفاوت دارد. + + پینی که برای حسابتان ساخته‌اید را وارد کنید. وارد کردن پین حرفی‌عددی پین عددی را وارد کنید پین نادرست. دوباره تلاش کنید. @@ -3398,7 +3454,10 @@ نسخه پشتیبان شما دارای یک فایل بسیار حجیم است که نمی‌توان از آن نسخه پشتیبان تهیه کرد. لطفا آن را پاک کنید و یک نسخه پشتیبان جدید ایجاد کنید. برای مدیریت پشتیبان‌ها ضربه بزنید. شماره اشتباه است؟ + با من تماس بگیر (%1$02d:%2$02d) + + ارسال مجدد کد (%1$02d:%2$02d) تماس با پشتیبانی سیگنال ثبت‌نام سیگنال - کد وارسی برای اندروید کد نادرست @@ -3406,6 +3465,18 @@ ناشناخته دیدن شماره‌تلفن من پیدا کردن من با شماره‌تلفن + + شماره تلفن + + انتخاب کنید که چه‌کسی بتواند شماره تلفن شما را ببیند و از طریق شماره تلفن‌تان در سیگنال با شما تماس بگیرد. + + چه‌کسی می‌تواند شماره من را ببیند + + هیچ‌کسی شماره‌تلفن شما را در سیگنال نخواهد دید + + چه‌کسی می‌تواند من را با شماره تلفنم پیدا کند + + شماره‌تلفن شما برای افراد و گروه‌هایی که به آن‌ها پیام می‌دهید قابل مشاهده خواهد بود. افرادی که شماره شما را در مخاطبان تلفن خود دارند، آن را در سیگنال نیز خواهند دید. همه مخاطبین من هیچ‌کس @@ -3615,9 +3686,9 @@ %1$s/%2$s - «%1$s» مسدود شد. - مسدود کردن «%1$s» ناموفق بود - «%1$s» رفع مسدودیت شد. + «%1$s» مسدود شده است. + «%1$s» مسدود نشد + «%1$s» رفع مسدودیت شده است. بازبینی اعضا @@ -3667,17 +3738,17 @@ وای‌فای ضعیف است. به داده تلفن همراه تغییر کرد. - حذف حساب کاربری شما: + پاک کردن حساب‌تان: شماره‌تلفن خود را وارد کنید - پاک کردن حساب کاربری - اطلاعات حساب کاربری و عکس پروفایل خود را پاک کنید - تمام پیام‌های خود را پاک کنید + پاک کردن حساب + اطلاعات حساب و عکس نمایه‌تان را پاک خواهد کرد + همه پیام‌هایتان را پاک خواهد کرد %1$s را در حساب پرداخت‌های خود پاک کنید هیچ کد کشوری وارد نشده است هیچ شماره‌ای وارد نشده است شماره‌تلفنی که وارد کردید با حساب کاربری شما همخوانی ندارد. آیا از پاک کردن حساب کاربری خود اطمینان دارید؟ - این گزینه، حساب کاربری سیگنال شما را پاک کرده و برنامه را بازنشانی می‌کند. برنامه پس از تکمیل فرایند بسته می‌شود. + این گزینه، حساب کاربری سیگنال شما را پاک کرده و برنامه را بازنشانی می‌کند. برنامه پس از تکمیل فرایند بسته خواهد شد. پاک کردن داده‌های محلی موفق نبود. شما می‌توانید آن را به صورت دستی در بخش تنظیمات برنامهٔ سیستم پاک کنید. باز کردن تنظیمات برنامه @@ -3784,12 +3855,12 @@ غیرفعال کردن کیف پول موجودی شما - توصیه می‌شود که قبل از اینکه پرداخت‌ها را غیرفعال کنید، اعتبار خود را به نشانی کیف پول دیگری انتقال دهید. اگر اکنون تصمیم بگیرید که اعتبار‌های خود را حالا انتقال ندهید، آن‌ها در صورتی که پرداخت‌ها را دوباره فعال کنید، در کیف پول پیوند داده شده به سیگنال شما باقی خواهند ماند. + توصیه می‌شود قبل از اینکه پرداخت‌ها را غیرفعال کنید، وجوه خود را به نشانی کیف پول دیگری انتقال دهید. اگر تصمیم بگیرید وجوه خود را اکنون انتقال ندهید، چنانچه پرداخت‌ها را دوباره فعال کنید، این وجوه در کیف پول پیوندداده‌شده به سیگنال شما باقی خواهند ماند. انتقال موجودی باقی‌مانده غیرفعال کردن بدون انتقال غیرفعال کردن غیرفعال کردن بدون انتقال؟ - موجودی شما، اگر تصمیم به فعال کردن دوبارهٔ پرداخت‌ها بگیرید، در کیف پول پیوند داده شده به سیگنال شما باقی خواهد ماند. + اگر تصمیم به فعال کردن دوباره پرداخت‌ها بگیرید، موجودی شما در کیف پول پیوندداده‌شده به سیگنال شما باقی خواهد ماند. خطا در غیرفعال کردن کیف پول. @@ -4003,12 +4074,12 @@ ایجاد پروفایل - مسدود شده + مسدودشده %1$d مخاطبین ‌پیام‌رسانی پیام‌های ناپدید شونده امنیت برنامه - مسدود کردن عکس از صفحه در فهرست آخرین‌ها و در درون برنامه + مسدود کردن عکس از صفحه در فهرست موارد اخیر و در برنامه پیام‌ها و تماس‌های سیگنال، عبور همیشگی تماس‌ها از سرور‌های سیگنال، و فرستندهٔ ناشناس زمان‌سنج پیش‌فرض برای گفتگوهای جدید یک زمان‌سنج پیام ناپدید شونده پیش‌فرض برای همهٔ گفتگوهایی که شما آغاز می‌کنید تنظیم کنید. @@ -4165,9 +4236,9 @@ تماس - بستن + بی‌صدا - بسته شد + بی‌صداشده جستجو پیام‌های ناپدید شونده @@ -4186,9 +4257,9 @@ درخواست‌ها و دعوت‌ها پیوند گروه افزودن به صورت یک مخاطب - باز کردن - مکالمه تا %1$s بسته شد - مکالمه برای همیشه بسته شد + صدادار کردن + مکالمه تا %1$s بی‌صدا شد + مکالمه برای همیشه بی‌صدا شد شماره‌تلفن در کلیپ‌بورد کپی شد. شماره‌تلفن با حمایت از سیگنال نشان‌هایی برای نمایه‌تان دریافت کنید. برای اطلاعات بیشتر روی نشان ضربه بزنید. @@ -4204,8 +4275,8 @@ چه کسی ‌می‌تواند پیام ارسال کنند؟ - بستن اعلان‌ها - بسته شده نیست + بی‌صدا کردن اعلان‌ها + بی‌صدا نیست اشاره‌ها همیشه آگاه کن آگاه نکن @@ -4242,7 +4313,7 @@ %1$s حذف شده است - %1$s مسدود شد + %1$s مسدود شده است حذف %1$s ممکن نیست @@ -4453,7 +4524,7 @@ کمک مالی ماهانه لغو شد نشان ارتقاء شما منقضی شده است و دیگر در نمایه‌تان قابل مشاهده نیست. - می‌توانید با یک کمک مالی یک‌-باره، نشان ارتقاء خود را برای ۳۰ روز دیگر دوباره فعال کنید. + می‌توانید با یک کمک مالی یک‌باره، نشان ارتقاء خود را برای ۳۰ روز دیگر دوباره فعال کنید. همچنان می‌توانید از سیگنال استفاده کنید، اما در نظر بگیرید که با اهدای کمک مالی ماهانه برای پشتیبانی از این فناوری که برای شما ساخته شده است، می‌توانید یکی از حامیان ما شوید. به یک حامی تبدیل شوید @@ -4508,7 +4579,7 @@ کمک مالی اهدایی شما به دلیل خطای شبکه ارسال نشد. اتصال خود را بررسی و دوباره تلاش کنید. - اهدای کمک مالی به %1$s + کمک مالی از طرف %1$s %1$s از طرف شما به سیگنال کمک مالی اهدا کرد @@ -5601,5 +5672,15 @@ پاک کردن نام کاربری + + + ساعت + + دقیقه + + تنظیم + + حداقل زمان قبل از اعمال شدن قفل صفحه‌نمایش ۱ دقیقه است. + diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index ddcdf8296d..35d9ed301a 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -14,6 +14,7 @@ + Kyllä Ei @@ -98,7 +99,7 @@ Estetyt käyttäjät Lisää estetty käyttäjä - Estetyt käyttäjät eivät pysty soittamaan sinulle tai lähettämään sinulle viestejä. + Estetyt käyttäjät eivät voi soittaa sinulle tai lähettää sinulle viestejä. Ei estettyjä käyttäjiä Estetäänkö käyttäjä? %1$s ei voi soittaa sinulle tai lähettää sinulle viestejä. @@ -147,8 +148,8 @@ Voitte jatkossa lähettää viestejä ja soittaa toisillenne, ja nimesi ja kuvasi jaetaan heidän kanssaan. Voitte lähettää viestejä toisillenne. - Estetyt käyttäjät eivät voi soittaa sinulle tai lähettää sinulle viestejä. - Estetyt käyttäjät eivät voi lähettää sinulle viestejä. + Estetyt henkilöt eivät voi soittaa sinulle tai lähettää sinulle viestejä. + Estetyt henkilöt eivät voi lähettää sinulle viestejä. Estä Signalin päivitysten ja uutisten vastaanotto. @@ -366,7 +367,7 @@ Virhe median lähettämisessä - Ilmoitettu roskapostista ja estetty + Ilmoitettu roskapostista ja estetty. Tekstiviestit on tällä hetkellä poistettu käytöstä. Voit viedä viestit toiseen puhelimessasi olevaan sovellukseen. @@ -481,11 +482,11 @@ Luettu - Luettu + Luettua Lukematon - Lukematon + Lukematonta Kiinnitä @@ -509,8 +510,8 @@ Arkistoi - Pal. arkistosta - Pal. arkistosta + Palauta arkistosta + Palauta arkistosta Poista @@ -542,6 +543,15 @@ +%1$d + + Linkitä laitteet uudelleen + + Lisäämiesi laitteiden linkitys poistettiin, kun laitteesi rekisteröinti poistettiin. Siirry asetuksiin linkittääksesi laitteita. + + Avaa asetukset + + Myöhemmin + Valitse jäsenet @@ -935,7 +945,7 @@ Ilmoita minulle maininnoista - Vastaanotetaanko ilmoituksia maininnoista mykistetyissä keskusteluissa? + Haluatko vastaanottaa ilmoituksia, kun sinut mainitaan mykistetyissä keskusteluissa? Ilmoita aina Älä ilmoita @@ -953,6 +963,16 @@ Käyttäjänimi luotu Käyttäjänimi kopioitu + + Käyttäjänimeä ei voitu poistaa. Yritä myöhemmin uudelleen. + + Käyttäjänimi poistettu + + + + Jokin meni vikaan käyttäjänimessä. Se ei ole enää liitetty tiliisi. Voit yrittää asettaa sen uudelleen tai valita uuden käyttäjänimen. + + Korjaa nyt @@ -1156,8 +1176,8 @@ Uusi ryhmä Kutsu ystäviä Käytä tekstiviestiä - Ulkonäkö - Lisää kuva + Keskustelun värit + Lisää profiilikuva Vastaukset @@ -1472,10 +1492,10 @@ Poista esto Sallitaanko henkilön %1$s lähettää sinulle viestejä ja nähdä nimesi ja profiilikuvasi? Hän ei tiedä, että olet nähnyt hänen viestinsä ennen hyväksymistäsi. - Sallitaanko henkilön %1$s lähettää sinulle viestejä ja nähdä nimesi ja profiilikuvasi? Et saa viestejä ennen eston poistamista. + Sallitaanko henkilön %1$s lähettää sinulle viestejä ja nähdä nimesi ja profiilikuvasi? Et saa viestejä, ennen kuin poistat eston. - Annetaanko käyttäjän %1$s lähettää sinulle viestejä? Et saa häneltä ollenkaan viestejä, ennen kuin annat luvan. - Haluatko päivityksiä ja uutisia lähteestä %1$s? Et saa päivityksiä, ennen kuin poistat eston. + Sallitaanko henkilön %1$s lähettää sinulle viestejä? Et saa viestejä, ennen kuin poistat eston. + Haluatko päivityksiä ja uutisia henkilöltä %1$s? Et saa päivityksiä, ennen kuin poistat eston. Jatketaanko keskustelua tämän ryhmän kanssa ja näytetäänkö nimesi ja kuvasi sen jäsenille? Päivitä tämä ryhmä ottaaksesi käyttöön uudet ominaisuudet kuten @maininnat ja ylläpitäjät. Jäsenille, jotka eivät ole jakaneet nimeään tai kuvaansa tässä ryhmässä, lähetetään kutsu. Tätä vanhan tyyppistä ryhmää ei voi enää käyttää, koska se on liian suuri. Ryhmässä voi olla enintään %1$d jäsentä. @@ -1483,7 +1503,7 @@ Liitytäänkö tähän ryhmään ja näytetäänkö nimesi ja profiilikuvasi sen jäsenille? He eivät tiedä, että olet nähnyt heidän viestinsä ennen hyväksymistä. Haluatko liittyä ryhmään ja jakaa nimesi ja profiilikuvasi ryhmän jäsenten kanssa? Et näe ryhmän viestejä, ennen kuin hyväksyt liittymisen. Haluatko liittyä tähän ryhmään? He eivät tiedä, että olet nähnyt heidän viestinsä ennen hyväksymistä. - Poistetaanko tämän ryhmän esto ja näytetäänkö nimesi ja profiilikuvasi sen jäsenille? Et saa viestejä ennen eston poistamista. + Poistetaanko tämän ryhmän esto ja näytetäänkö nimesi ja profiilikuvasi sen jäsenille? Et saa viestejä, ennen kuin poistat eston. Näytä Jäsen ryhmässä %1$s @@ -1584,9 +1604,20 @@ Luo uusi tunnusluku + + Lähetä tekstiviestikoodi + + Signalin rekisteröinti – Tarvitsen apua PIN-koodin uudelleenrekisteröintiin Android-laitteella + + Luomasi PIN-koodi on vähintään %1$d-numeroinen koodi, ja se voi olla numeerinen tai aakkosnumeerinen.\n\nJos olet unohtanut PIN-koodin, voit luoda uuden. + + Jos olet unohtanut PIN-koodin, voit luoda uuden. + + Olet syöttänyt PIN-koodin liian monta kertaa väärin, mutta voit edelleen käyttää Signal-tiliäsi luomalla uuden PIN-koodin. + Varoitus - Jos poistat tunnusluvun käytöstä, menetät kaikki tietosi rekisteröityessäsi Signaliin uudelleen, ellet siirrä varmuuskopioi tietojasi käsin. Et voi myöskään ottaa rekisteröintiestoa käyttöön. + Jos poistat PIN-koodin käytöstä, menetät kaikki tietosi rekisteröityessäsi Signaliin uudelleen, ellet varmuuskopioi ja palauta tietojasi manuaalisesti. Et voi myöskään ottaa rekisteröintiestoa käyttöön, jos PIN-koodi on poistettu käytöstä. Poista tunnusluku käytöstä @@ -1731,7 +1762,7 @@ Et voi enää käydä ääni- tai videoviestinvaihtoa henkilön kanssa. Ääni- ja videoviestin vastaanottaminen käyttäjältä %1$sepäonnistui Ääni- ja videoviestin vastaanottaminen käyttäjältä %1$sepäonnistui - Tämä saattaa johtua siitä että he eivät ole vahvistaneet uutta turvanumeroasi, laitteiden teknisistä ongelmista, tai siitä, että sinut on estetty. + Tämä saattaa johtua siitä, että he eivät ole vahvistaneet uutta turvanumeroasi, laitteiden teknisistä ongelmista, tai siitä, että sinut on estetty. Pyyhkäise nähdäksesi näytönjaon @@ -1768,11 +1799,18 @@ Signal tarvitsee yhteystietojen ja median käyttöoikeudet, jotta voit olla yhteydessä kavereihisi ja lähettää viestejä. Yhteystietosi lähetetään käyttäen Signalin yksityistä yhteystietohakua ja ne on suojattu päästä päähän -salauksella. Ne eivät missään vaiheessa paljastu Signal-palvelulle. Signal tarvitsee yhteystietojen käyttöoikeuden, jotta voit olla yhteydessä kavereihisi. Yhteystietosi lähetetään käyttäen Signalin yksityistä yhteystietohakua ja ne on suojattu päästä päähän -salauksella. Ne eivät missään vaiheessa paljastu Signal-palvelulle. Olet yrittänyt rekisteröidä tämän numeron liian monta kertaa. Yritä myöhemmin uudelleen. + + Olet yrittänyt rekisteröidä tämän numeron liian monta kertaa. Yritä uudelleen, kun on kulunut %1$s. Yhteyttä ei voitu muodostaa palveluun. Tarkista verkkoyhteytesi ja yritä uudelleen. Epästandardi numeron muoto Syöttämäsi numero (%1$s) ei näytä olevan vakiomuotoinen.\n\nTarkoititko %2$s? Molly Androidille - Puhelinnumeron muoto + Puhelu pyydetty + + Tekstiviesti pyydetty + + Vahvistuskoodi pyydetty Olet tällä hetkellä %1$d askeleen päässä virheenkorjauslokin lähettämisestä. Vielä %1$d vaihetta virheenkorjauslokin lähettämiseen. @@ -1792,6 +1830,16 @@ Soita Vahvistuskoodi Lähetä koodi uudelleen + + Onko sinulla ongelmia rekisteröitymisessä? + + • Varmista, että puhelimesi on yhteydessä matkapuhelinverkkoon ja voi vastaanottaa tekstiviestin tai puhelun\n • Varmista, että voit vastaanottaa puhelun numeroon\n • Tarkista, että olet syöttänyt puhelinnumerosi oikein. + + Jos haluat lisätietoja, noudata näitä vianmääritysvaiheita tai ota yhteyttä tukeen + + näitä vianmääritysvaiheita + + Ota yhteyttä tukeen Otetaanko rekisteröintiesto käyttöön? @@ -1958,6 +2006,10 @@ Maksu Ajoitettu viesti + + Viestihistoriasi on yhdistetty + + %1$s kuuluu henkilölle %2$s Molly-päivitys @@ -2087,14 +2139,16 @@ Salaamaton tekstiviesti %1$s %2$s Yhteystieto - lähetti reaktion %1$s viestiisi %2$s. - lähetti reaktion %1$s videoosi. - lähetti reaktion %1$s kuvaasi. - lähetti reaktion %1$s GIF:iisi. - lähetti reaktion %1$s tiedostoosi. - lähetti reaktion %1$s ääniviestiisi. - lähetti reaktion %1$s kerran katsottavaan mediaasi. - lähetti reaktion %1$s tarraasi. + Lähetti reaktion %1$s viestiin: %2$s. + Lähetti reaktion %1$s videoosi. + Lähetti reaktion %1$s kuvaasi. + Lähetti reaktion %1$s GIF:iisi. + Lähetti reaktion %1$s tiedostoosi. + Lähetti reaktion %1$s ääniviestiisi. + Lähetti reaktion %1$s kerran katsottavaan mediaasi. + + Lähetti reaktion %1$s maksuusi. + Lähetti reaktion %1$s tarraasi. Viesti poistettiin. Poistetaanko Yhteystietosi liittyi Signaliin -ilmoitukset käytöstä? Voit ottaa ne uudelleen käyttöön avaamalla Signal > Asetukset > Ilmoitukset. @@ -2487,7 +2541,7 @@ Ongelma viestin toimituksessa - Lähettäjän %1$s viestiä, tarraa, reaktiota, lukukuittausta tai mediaa ei voitu toimittaa sinulle. Se on ehkä yritetty lähettää sinulle suoraan tai ryhmässä. + Lähettäjän %1$s viestiä, tarraa, reaktiota tai lukukuittausta ei voitu toimittaa sinulle. Se on ehkä yritetty lähettää sinulle suoraan tai ryhmässä. Lähettäjän %1$s viestiä, tarraa, reaktiota tai lukukuittausta ei voitu toimittaa sinulle. @@ -2555,7 +2609,7 @@ Käsiteltävänä - Lähetetty käyttäjälle + Lähetetty seuraaville: Lähettäjä Toimitettu käyttäjälle Luettu @@ -2637,8 +2691,8 @@ Mykistä 1 tunniksi Mykistä 8 tunniksi - Mykistä 1 vuorokaudeksi - Mykistä 7 vuorokaudeksi + Mykistä 1 päiväksi + Mykistä 7 päiväksi Aina Oletus @@ -2675,9 +2729,9 @@ Käytä osoitekirjan valokuvia Näytä yhteystietojen valokuvat osoitekirjasta, mikäli saatavilla - Pidä mykistetyt chatit arkistossa + Pidä mykistetyt keskustelut arkistoituina - Arkistoon siirretyt mykistetyt chatit pysyvät mykistettynä, vaikka uusia viestejä saapuu. + Arkistoidut mykistetyt keskustelut pysyvät arkistoituina, kun uusi viesti saapuu. Luo esikatselukuvia linkeistä Näytä lähettämiesi linkkien esikatselu verkkosivustoilta. Salalauseen vaihto @@ -3295,6 +3349,8 @@ Syötä tunnusluku Syötä tilille valitsemasi tunnusluku. Se ei ole sama kuin saamasi tekstiviestivahvistuskoodi. + + Kirjoita PIN-koodi, jonka loit tiliäsi varten. Syötä aakkosnumeerinen tunnusluku Syötä numeerinen tunnusluku Väärä tunnusluku, yritä uudelleen. @@ -3398,7 +3454,10 @@ Varmuuskopiosi sisältää erittäin suuren tiedoston, jota ei voi varmuuskopioida. Poista se ja luo uusi varmuuskopio. Hallitse varmuuskopioita napauttamalla. Väärä numero? + Soita minulle (%1$02d.%2$02d) + + Lähetä koodi uudelleen (%1$02d.%2$02d) Ota yhteyttä Signalin tukeen Signalin rekisteröinti – vahvistuskoodi Androidille Väärä koodi @@ -3406,6 +3465,18 @@ Tuntematon Nähdä puhelinnumeroni Löytää minut puhelinnumeron perusteella + + Puhelinnumero + + Valitse, kuka voi nähdä puhelinnumerosi ja kuka voi ottaa sinuun yhteyttä Mollyissa sen avulla. + + Kuka voi nähdä numeroni + + Kukaan ei näe puhelinnumeroasi Mollyissa + + Kuka voi löytää minut puhelinnumerolla + + Puhelinnumerosi näkyy henkilöille ja ryhmille, joille lähetät viestejä. Henkilöt, joilla on puhelinnumerosi puhelimensa yhteystiedoissa, näkevät sen myös Mollyissa. Kaikki Yhteystietoni Ei kukaan @@ -3615,9 +3686,9 @@ %1$s/%2$s - %1$s on nyt estetty. - Käyttäjän %1$s estäminen epäonnistui - Käyttäjän %1$s esto on poistettu. + %1$s on estetty. + Henkilön %1$s estäminen epäonnistui + Henkilön %1$s esto on poistettu. Tarkista jäsenet @@ -3669,7 +3740,7 @@ Tilin poistamisesta seuraa: Syötä puhelinnumerosi - Tili poistetaan + Poista tili Tilisi tiedot ja profiilikuva poistetaan Kaikki viestit poistetaan %1$s maksutililtäsi poistetaan @@ -4008,7 +4079,7 @@ Viestintä Katoavat viestit Turvallisuus - Estä näyttökuvat Viimeksi käytetyt sovellukset -näkymästä sekä sovelluksen sisältä + Estä näyttökuvat Viimeisimmät-luettelosta ja sovelluksen sisältä Signal-viestit ja -puhelut, välitä puhelut aina ja lähettäjäsinetti Uusien keskustelujen oletusajastin Valitse oletusajastin katoaville viesteille kaikissa uusissa aloittamissasi keskusteluissa. @@ -4187,8 +4258,8 @@ Ryhmälinkki Lisää uudeksi kontaktiksi Poista mykistys - Keskustelu mykistetty, mykistyksen kesto %1$s - Keskustelu mykistetty lopullisesti + Keskustelu mykistetty %1$s asti + Keskustelu mykistetty pysyvästi Puhelinnumero kopioitu leikepöydälle. Puhelinnumero Hanki profiiliisi merkkejä tukemalla Signalia. Lue lisää napauttamalla merkkiä. @@ -4330,7 +4401,7 @@ Lisää tarinaan Lisää viesti Lisää vastaus - Lähetä yhteystiedolle + Lähetä henkilölle Kerran katsottava viesti Yksi tai useampi valinnoista olivat liian suuria Yksi tai useampi valinnoista olivat virheellisiä @@ -4465,7 +4536,7 @@ Toistuva kuukausittainen lahjoituksesi peruutettiin automaattisesti, koska maksuasi ei voitu käsitellä. Merkki ei näy enää profiilissasi. Toistuva kuukausittainen lahjoituksesi peruutettiin. %1$s %2$s-merkki ei näy enää profiilissasi. - Voit jatkaa Signalin käyttöä, mutta tukeaksesi sovellusta ja aktivoidaksesi merkin uudelleen, uusi lahjoitus nyt. + Voit jatkaa Signalin käyttöä, mutta jos haluat tukea sovellusta ja aktivoida merkin uudelleen, uusi lahjoitus nyt. Uusi tilaus Siirry Google Payhin @@ -4508,7 +4579,7 @@ Lahjoitustasi ei voitu lähettää verkkovirheen takia. Tarkista yhteytesi ja yritä uudelleen. - Lahjoitus henkilölle %1$s + Lahjoitus henkilön %1$s puolesta %1$s lahjoitti Signalille puolestasi @@ -4909,7 +4980,7 @@ Salli vastaukset ja reaktiot - Anna tarinasi katsoneiden ihmisten reagoida ja vastata + Anna henkilöiden, jotka voivat nähdä tarinasi, reagoida ja vastata Signal-kontaktit @@ -5057,7 +5128,7 @@ - Reagoit käyttäjän %1$s tarinaan + Reagoit henkilön %1$s tarinaan Lähetti reaktion tarinaasi @@ -5087,7 +5158,7 @@ Vahvista lahjoitus - Lähetä yhteystiedolle + Lähetä henkilölle Vastaanottaja saa ilmoituksen lahjoituksesta kahdenkeskisessä viestissä. Lisää oma viestisi alle. @@ -5601,5 +5672,15 @@ Poista käyttäjänimi + + + t + + min + + Aseta + + Vähimmäisaika ennen näytön lukitusta on 1 minuutti. + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 94986e86b4..fd4965c340 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -14,13 +14,14 @@ + Oui Non Supprimer Veuillez patienter… Enregistrer - Note à mon intention + Note à mon attention @@ -100,7 +101,7 @@ Ajouter un utilisateur bloqué Les utilisateurs bloqués ne pourront ni vous appeler ni vous envoyer des messages. Aucun utilisateur bloqué - Bloquer cet utilisateur ? + Bloquer cet utilisateur ? « %1$s » ne pourra ni vous appeler ni vous envoyer des messages. Bloquer @@ -138,8 +139,8 @@ Poursuivre - Bloquer et quitter %1$s ? - Bloquer %1$s ? + Bloquer et quitter %1$s ? + Bloquer %1$s ? Vous ne recevrez plus ni message ni mise à jour de ce groupe et les membres ne pourront plus vous rajouter à ce groupe. Les membres du groupe ne pourront plus vous rajouter à ce groupe. Les membres du groupe pourront vous rajouter à ce groupe. @@ -153,7 +154,7 @@ Bloquer la réception des mises à jour et des actualités de Signal. Reprendre la réception des mises à jour et des actualités de Signal. - Débloquer %1$s ? + Débloquer %1$s ? Bloquer Bloquer et quitter Signaler comme indésirable et bloquer @@ -398,21 +399,21 @@ Enregistrer dans la mémoire ? - L’enregistrement de ce contenu dans la mémoire permettra à n’importe quelle autre appli de votre appareil d’y accéder.\n\nPoursuivre ? - L’enregistrement des %1$d médias dans la mémoire permettra à n’importe quelle autre appli de votre appareil d’y accéder.\n\nPoursuivre ? + L’enregistrement de ce contenu dans l\'espace de stockage permettra à n’importe quelle autre appli de votre appareil d’y accéder.\n\nPoursuivre ? + L’enregistrement des %1$d médias dans l\'espace de stockage permettra à n’importe quelle autre appli de votre appareil d’y accéder.\n\nPoursuivre ? - Erreur d’enregistrement du fichier joint dans la mémoire ! - Erreur d’enregistrement des fichiers joints dans la mémoire ! + Erreur d’enregistrement du fichier joint dans l\'espace de stockage ! + Erreur d’enregistrement des fichiers joints dans l\'espace de stockage ! - Impossible d’écrire dans la mémoire. + Impossible d’écrire dans l\'espace de stockage Enregistrement du fichier joint Enregistrement de %1$d fichiers joints - Enregistrement du fichier joint dans la mémoire… - Enregistrement de %1$d fichiers joints dans la mémoire… + Enregistrement du fichier joint dans l\'espace de stockage… + Enregistrement de %1$d fichiers joints dans l\'espace de stockage… En attente… Données (Signal) @@ -423,7 +424,7 @@ Supprimer pour moi Supprimer pour tout le monde - Supprimer cet appareil + Supprimer de cet appareil Supprimer de tous les appareils Ce message sera supprimé pour tout le monde dans la conversation s’ils utilisent une version récente de Signal. Ils pourront voir que vous avez supprimé un message. @@ -447,7 +448,7 @@ %1$s activé - Bloquer la demande ? + Bloquer la demande ? « %1$s » ne pourra ni se joindre ni demander à se joindre à ce groupe grâce au lien de groupe. Cette personne pourra toujours être ajoutée au groupe manuellement. @@ -455,7 +456,7 @@ Annuler - Bloqués + Bloqué Supprimer le filtre @@ -496,8 +497,8 @@ Désépingler - Sourdine - Sourdine + Mettre en sourdine + Mettre en sourdine Réactiver le son @@ -542,6 +543,15 @@ +%1$d + + Liez de nouveau vos appareils + + Les appareils que vous avez ajoutés ont été dissociés lorsque votre appareil a été supprimé. Rendez-vous dans les paramètres pour relier vos appareils. + + Ouvrir les paramètres + + Plus tard + Sélectionnez des membres @@ -613,7 +623,7 @@ %1$d jusqu’à présent… %1$s%% jusqu’à présent… - Molly exige l’autorisation d’accès à la mémoire externe afin de créer des sauvegardes, mais elle a été refusée définitivement. Veuillez accéder au menu des paramètres des applis, sélectionner « Autorisations » et activer « Stockage ». + Molly exige l’autorisation d’accès à l\'espace de stockage externe afin de créer des sauvegardes, mais elle a été refusée définitivement. Veuillez accéder au menu des paramètres des applis, sélectionner « Autorisations » et activer « Stockage ». @@ -935,7 +945,7 @@ Me signaler les mentions - Recevoir des notifications si vous êtes mentionné dans des conversations en sourdine ? + Recevoir des notifications si vous êtes mentionné dans des conversations en sourdine ? Toujours me prévenir Ne pas me prévenir @@ -953,6 +963,16 @@ Pseudo créé Pseudo copié + + Impossible de supprimer le nom d’utilisateur. Réessayez plus tard. + + Nom d’utilisateur supprimé + + + + Une erreur s’est produite et votre nom d’utilisateur n’est plus associé à votre compte. Vous pouvez tenter de le reconfigurer ou en choisir un autre. + + Résoudre @@ -1116,8 +1136,8 @@ Trier par Le plus récent Le plus ancien - Mémoire utilisée - Utilisation totale de la mémoire + Espace de stockage utilisé + Utilisation totale de l\'espace de stockage Vue en grille Vue en liste Sélectionné @@ -1156,8 +1176,8 @@ Nouveau groupe Inviter des amis Utiliser les textos - Apparence - Ajouter une photo + Thème de la conversation + Ajouter une photo de profil Réponses @@ -1472,10 +1492,10 @@ Débloquer Autoriser %1$s à échanger des messages et partager votre nom et votre photo avec ce contact ? Ce contact ne saura pas que vous avez vu ses messages tant que vous n’aurez pas accepté. - Autoriser %1$s à échanger des messages et partager votre nom et votre photo avec ce contact ? Vous ne recevrez aucun message tant que vous ne l’aurez pas débloqué. + Autoriser %1$s à vous envoyer des messages et partager vos nom et photo avec ce contact ? Vous ne recevrez aucun message tant que vous ne l’aurez pas débloqué. - Autoriser %1$s à vous écrire ? Vous ne recevrez aucun message tant que vous ne l’aurez pas débloqué. - Recevoir voir des mises à jour et des nouvelles de%1$s ? Vous ne recevrez aucune mise à jour tant que vous ne l’aurez pas débloqué. + Autoriser %1$s à vous envoyer un message ? Vous ne recevrez aucun message tant que vous ne l’aurez pas débloqué. + Recevoir voir des nouvelles de%1$s ? Vous ne recevrez aucune nouvelle tant que vous ne l’aurez pas débloqué. Poursuivre votre conversation avec ce groupe et partager vos nom et photo avec ses membres ? Convertissez ce groupe pour activer de nouvelles fonctions telles que les @mentions et l’administration. Les membres qui n’ont pas partagé leur nom ou leur photo dans ce groupe seront invités à s’y joindre. Ce groupe hérité ne peut plus être utilisé, car il comporte trop de membres. Le nombre maximal de membres d’un groupe est %1$d. @@ -1483,7 +1503,7 @@ Vous joindre à ce groupe et partager votre nom et votre photo avec ses membres ? Ils ne sauront pas que vous avez vu leurs messages tant que vous n’aurez pas accepté. Vous joindre à ce groupe et partager votre nom et votre photo avec ses membres ? Vous ne verrez pas leurs messages tant que vous n’aurez pas accepté. Vous joindre à ce groupe ? Ses membres ne sauront pas que vous avez lu leurs messages tant que vous n’aurez pas accepté. - Débloquer ce groupe et partager votre nom et votre photo avec ses membres ? Vous ne recevrez aucun message tant que vous ne l’aurez pas débloqué. + Débloquer ce groupe et partager vos nom et photo avec ses membres ? Vous ne recevrez aucun message tant que vous ne l’aurez pas débloqué. Afficher Membre de %1$s @@ -1584,9 +1604,20 @@ Créer un nouveau NIP + + Envoyer un code par SMS + + Inscription à Signal - Besoin d’aide pour inscrire un PIN sur un appareil Android + + Votre PIN est un code numérique ou alphanumérique d’au moins %1$d caractères que vous avez créé.\n\nSi vous avez oublié votre PIN, vous pouvez en créer un autre. + + Si vous avez oublié votre PIN, vous pouvez en créer un autre. + + Vous avez atteint le nombre maximal de tentatives autorisées. Cependant, vous pouvez toujours accéder à votre compte Signal en créant un nouveau PIN. + Avertissement - Si vous désactivez le NIP, vous perdrez toutes vos données lors de votre réinscription sur Signal, à moins que vous ne les sauvegardiez et les restauriez manuellement. Vous ne pouvez pas activer le blocage de l’inscription si le NIP est désactivé. + Si vous désactivez le PIN, vous perdrez toutes vos données lors de votre réinscription sur Signal, à moins que vous ne les sauvegardiez et les restauriez manuellement. Vous ne pouvez pas activer le blocage de l’inscription si le PIN est désactivé. Désactiver le NIP @@ -1711,7 +1742,7 @@ Caméra - Réactiver + Réactiver le son Sourdine @@ -1768,11 +1799,18 @@ Signal a besoin des autorisations Contacts et Médias pour vous aider à vous connecter avec vos amis et à envoyer des messages. Vos contacts sont téléversés en utilisant la découverte confidentielle des contacts de Signal, ce qui signifie qu’ils sont chiffrés de bout en bout et ne sont jamais visibles par le service Signal. Signal a besoin de l’autorisation Contacts pour vous aider à vous connecter avec vos amis. Vos contacts sont téléversés en utilisant la découverte confidentielle des contacts de Signal, ce qui signifie qu’ils sont chiffrés de bout en bout et ne sont jamais visibles par le service Signal. Vous avez fait trop d’essais pour inscrire ce numéro. Veuillez réessayer plus tard. + + Vous avez atteint le nombre maximal de tentatives d’inscription de ce numéro. Veuillez réessayer dans %1$s. Impossible de se connecter au service. Veuillez vérifier la connexion réseau et réessayer. Format de numéro non standard Le numéro que vous avez saisi (%1$s) semble avoir un format atypique.\n\nSerait-ce plutôt %2$s ? Molly pour Android – format de numéro de téléphone + Appel demandé + + SMS demandé + + Code de vérification demandé Vous êtes maintenant à %1$d étape d’envoyer un journal de débogage. Vous êtes maintenant à %1$d étapes d’envoyer un journal de débogage. @@ -1792,6 +1830,16 @@ Appeler Code de vérification Renvoyer le code + + Un problème d’inscription ? + + • Assurez-vous que votre téléphone dispose d’un signal cellulaire pour recevoir votre SMS ou votre appel.\n • Confirmez que vous pouvez recevoir un appel à ce numéro.\n • Vérifiez que vous avez renseigné votre numéro de téléphone correctement. + + Pour plus d’informations, veuillez suivre ces étapes de dépannage ou contacter le service d’assistance. + + ces étapes de dépannage + + Contacter l’assistance Activer le blocage de l’inscription ? @@ -1951,13 +1999,17 @@ Vous avez récupéré un macaron - A réagi à votre Story par %1$s + A réagi par %1$s à votre Story A réagi par %1$s à leur Story Paiement Message programmé + + L’historique de vos messages a fusionné + + %1$s appartient à %2$s Mise à jour de Molly @@ -2047,8 +2099,8 @@ Vous Type de média non pris en charge Brouillon - Molly a besoin de l’autorisation Stockage afin d’enregistrer sur la mémoire externe, mais elle a été refusée définitivement. Veuillez accéder aux paramètres de l’appli, sélectionner Autorisations et activer Stockage. - Impossible d’enregistrer sur la mémoire externe sans autorisation + Molly a besoin de l’autorisation Stockage afin d’enregistrer sur l\'espace de stockage externe, mais elle a été refusée définitivement. Veuillez accéder aux paramètres de l’appli, sélectionner Autorisations et activer Stockage. + Impossible d’enregistrer sur l\'espace de stockage sans autorisation Supprimer le message ? Ce message sera irrémédiablement supprimé. %1$s à %2$s @@ -2087,14 +2139,16 @@ Texto non sécurisé %1$s %2$s Contact - A réagi %1$s à : « %2$s ». - A réagi %1$s à votre vidéo. - A réagi %1$s à votre image. - A réagi avec %1$s à votre GIF. - A réagi %1$s à votre fichier. - A réagi %1$s à votre son. - A réagi %1$s à votre média éphémère. - A réagi %1$s à votre autocollant. + A réagi par %1$s à : « %2$s ». + A réagi par %1$s à votre vidéo. + A réagi par %1$s à votre image. + A réagi par %1$s à votre GIF. + A réagi par %1$s à votre fichier. + A réagi par %1$s à votre message vocal. + A réagi par %1$s à votre média éphémère. + + A réagi par %1$s à votre paiement. + A réagi par %1$s à votre autocollant. Ce message a été supprimé. Désactiver les notifications pour les contacts qui se sont joints à Signal ? Vous pouvez les réactiver dans Signal > Paramètres > Notifications @@ -2635,10 +2689,10 @@ Utiliser la valeur par défaut Personnalisé - Sourdine pendant 1 heure + Sourdine pendant 1 heure Sourdine pendant 8 heures - Sourdine pendant 1 jour - Sourdine pendant 7 jours + Sourdine pendant 1 jour + Sourdine pendant 7 jours Toujours Paramètre par défaut @@ -2677,7 +2731,7 @@ Garder les conversations en mode silencieux archivées - Les conversations en mode silencieux sont archivées, y compris après réception d\'un nouveau message. + Les conversations en mode silencieux sont archivées, y compris après réception d’un nouveau message. Générer des aperçus de lien Pour les messages que vous envoyez, récupérez des aperçus de lien directement des sites Web. Changer la phrase de passe @@ -2732,7 +2786,7 @@ Mot de passe MMSC Relevés de remise des textos Demander un relevé de remise pour chaque texto envoyé - Données et mémoire + Données et espace de stockage Mémoire Paiements @@ -2773,12 +2827,12 @@ En itinérance Téléchargement automatique des médias Historique des messages - Utilisation de la mémoire + Utilisation de l\'espace de stockage Photos Vidéos Fichiers Son - État de la mémoire + État de l\'espace de stockage Supprimer les anciens messages ? Effacer l’historique des messages ? L’historique et les contenus multimédias des messages de plus de %1$s seront irrémédiablement supprimés de votre appareil. @@ -2801,7 +2855,7 @@ Paiements Conversations - Gérer la mémoire + Gérer l\'espace de stockage Utiliser moins de données pour les appels Jamais Wi-Fi et données mobiles @@ -2971,7 +3025,7 @@ Paiement envoyé Paiement reçu Le paiement est finalisé %1$s - Bloquer le numéro + Numéro de bloc Transférer @@ -3128,7 +3182,7 @@ - Réactiver les notifications + Réactiver le son Notifications en sourdine @@ -3295,6 +3349,8 @@ Saisissez votre NIP Saisissez le NIP que vous avez créé pour votre compte. Il est différent de votre code de confirmation reçu par texto. + + Saisissez le PIN choisi pour votre compte. Saisissez un NIP alphanumérique Saisissez un NIP numérique Le NIP est erroné. Veuillez réessayer. @@ -3350,7 +3406,7 @@ Nouveau message verrouillé Déverrouiller pour visualiser les messages en attente Phrase de passe de la sauvegarde - Les sauvegardes sont enregistrées dans la mémoire externe et chiffrées avec la phrase de passe ci-dessous. Vous devez avoir cette phrase de passe afin de restaurer la sauvegarde. + Les sauvegardes sont enregistrées dans l\'espace de stockage externe et chiffrées avec la phrase de passe ci-dessous. Vous devez avoir cette phrase de passe afin de restaurer la sauvegarde. Cette phrase de passe est requise afin de restaurer une sauvegarde. Dossier J’ai noté cette phrase de passe. Sans elle, je ne pourrai pas restaurer une sauvegarde. @@ -3398,7 +3454,10 @@ Votre sauvegarde contient un fichier trop volumineux. Veuillez le supprimer et créer une nouvelle sauvegarde. Toucher pour gérer les sauvegardes Mauvais numéro ? + Appelez-moi dans (%1$02d:%2$02d) + + Renvoyer un code (%1$02d:%2$02d) Contacter l’assistance de Signal Inscription à Signal – Code de confirmation pour Android Code incorrect @@ -3406,6 +3465,18 @@ Inconnu Voir mon numéro de téléphone Me trouver d’après mon numéro de téléphone + + Numéro de téléphone + + Choisissez qui peut voir votre numéro de téléphone et qui peut vous contacter sur Molly avec celui-ci. + + Qui peut voir mon numéro de téléphone + + Personne ne verra votre numéro de téléphone sur Molly. + + Qui peut me retrouver grâce à mon numéro de téléphone + + Votre numéro de téléphone sera visible par les personnes et les groupes avec lesquels vous échangez des messages. Les personnes qui ont votre numéro dans les contacts de leur téléphone le verront aussi sur Molly. Tout le monde Mes contacts Personne @@ -3616,7 +3687,7 @@ %1$s/%2$s « %1$s » a été bloqué. - Échec de blocage pour « %1$s » + Échec de blocage de « %1$s » « %1$s » a été débloqué. @@ -3670,14 +3741,14 @@ Supprimer votre compte implique : Saisir votre numéro de téléphone Supprimer le compte - Supprimer les informations de votre compte et votre photo de profil - Supprimer tous vos messages + la suppression des informations de votre compte et de votre photo de profil + La suppression de tous vos messages Supprimer %1$s de votre compte de paiements Aucun indicatif de pays n’a été indiqué Aucun numéro n’a été indiqué Le numéro de téléphone que vous avez saisi ne correspond pas à celui de votre compte. Voulez-vous vraiment supprimer votre compte ? - Cela supprimera votre compte Signal et réinitialisera l’application. L’appli se fermera une fois le processus terminé. + Cela supprimera votre compte Signal et réinitialisera l’application. L’appli se fermera une fois le processus terminé. Échec de suppression des données locales. Vous pouvez les effacer manuellement dans les paramètres des applications du système. Lancer les paramètres des applis @@ -4003,7 +4074,7 @@ Créer le profil - Bloqués + Bloqué %1$d contacts Messagerie Messages éphémères @@ -4186,7 +4257,7 @@ Demandes d’ajout et invitations Lien de groupe Ajouter comme contact - Réactiver les notifications + Réactiver le son Conversation en sourdine jusqu’à %1$s Conversation en sourdine pour toujours Numéro de téléphone copié dans le presse-papier. @@ -4205,7 +4276,7 @@ Mettre les notifications en sourdine - Désactivée + Sourdine désactivée Mentions Toujours m’avertir Ne pas m’avertir @@ -4242,7 +4313,7 @@ %1$s a été supprimé - %1$s a été bloqué + %1$s a été bloqué Impossible de supprimer %1$s @@ -4508,7 +4579,7 @@ Votre don ne peut pas être envoyé à cause d\'une erreur de réseau. Vérifiez votre connexion et réessayez. - Don à %1$s + Don de la part de %1$s %1$s a effectué un don de votre part @@ -5601,5 +5672,15 @@ Supprimer le nom d’utilisateur + + + h + + min + + Définir + + Le temps minimum avant que le verrouillage de l’écran ne s’applique est de 1 minute. + diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index 041d389c68..a853c730ee 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -14,13 +14,14 @@ + Níl Scrios Fan le do thoil… Cuir i dtaisce é - Nóta Pearsanta + Nóta Chugam Féin @@ -96,13 +97,13 @@ Seiceáil le haghaidh teachtaireachtaí… - Úsáideoirí Bactha - Cuir Úsáideoir Bactha Leis + Úsáideoirí bactha + Cuir úsáideoir bactha leis Ní bheidh úsáideoirí bactha in ann glaonna a chur ort ná teachtaireachtaí a sheoladh chugat. - Níl aon úsáideoirí bactha - Cuir Bac ar an Úsáideoir Seo? + Níl aon úsáideoirí bactha ann + Cuir bac ar an úsáideoir? Ní bheidh \"%1$s\" in ann glaoch ort ná teachtaireachtaí a sheoladh chugat. - Bac Nua + Cuir bac @@ -138,8 +139,8 @@ Ar Aghaidh - Cuir bac ar ⁊ fág %1$s? - An bhfuil fonn ort bac a chur ar %1$s? + Cuir bac ar %1$s agus imigh as? + Cuir bac ar %1$s? Ní bhfaighidh tú teachtaireachtaí ón ngrúpa seo a thuilleadh, agus ní bheidh baill den ghrúpa in ann tusa a chur leis arís. Ní bheidh baill den ghrúpa in ann tusa a chur leis arís. Beidh baill den ghrúpa in ann tusa a chur leis an ngrúpa seo arís. @@ -148,15 +149,15 @@ Beidh sibh in ann teachtaireachtaí a sheoladh idir a chéile. Ní bheidh daoine bactha in ann glaonna a chur ort ná teachtaireachtaí a sheoladh chugat. - Ní bheidh daoine ar cuireadh bac orthu in ann teachtaireachtaí a sheoladh chugat. + Ní bheidh daoine bactha in ann teachtaireachtaí a sheoladh chugat. - Cuir bac ar nuashonruithe agus nuacht Signal a fháil. + Cuir bac ar nuashonruithe agus nuacht Signal. Tosaigh ar nuashonruithe agus nuacht Signal a fháil arís. - An bhfuil fonn ort an bac a bhaint de %1$s? - Bac Nua - Cuir bac air ⁊ Fág - Report Spam and Block + Bain bac de %1$s? + Cuir bac + Cuir bac agus imigh as + Tuairiscigh turscar agus cuir bac air Inniu @@ -366,7 +367,7 @@ Tharla botún fad agus a bhí an meán á sheoladh - Reported as spam and blocked. + Tuairiscithe mar thurscar agus bac curtha air. Tá teachtaireachtaí SMS díchumasaithe faoi láthair. Is féidir leat do theachtaireachtaí a easpórtáil chuig aip eile ar do ghuthán. @@ -469,11 +470,11 @@ Ní bheidh %1$s in ann dul isteach nó iarraidh ar dhul isteach sa ghrúpa seo tríd an nasc grúpa. Is féidir an duine sin a chur leis an ngrúpa de láimh fós. - Cuir Bac ar an Iarratas + Cuir bac ar an iarratas Cealaigh - Bactha + Bac curtha Glan an scagaire @@ -602,6 +603,15 @@ +%1$d + + Athnasc do chuid gléasanna + + Dínascadh na gléasanna a chuir tú leis nuair a díchláraíodh do ghléas. Téigh chuig Socruithe chun aon ghléas a athnascadh. + + Oscail socruithe + + Níos déanaí + Baill a Roghnú @@ -1049,7 +1059,7 @@ Cuir Tráchtanna in iúl dom - An bhfuil fonn ort fógraí a fháil nuair a luaitear i gcomhrá balbhaithe thú? + Faigh fógraí nuair a luaitear i gcomhráite balbhaithe thú? Cuir scéal chugam i gcónaí Ná fógraigh dom é @@ -1067,6 +1077,16 @@ Cruthaíodh ainm úsáideora Cóipeáladh ainm úsáideora + + Níorbh fhéidir an t-ainm úsáideora a scriosadh. Triail arís níos déanaí. + + Ainm úsáideora scriosta + + + + Bhí fadhb le d\'ainm úsáideora, níl sé sannta do do chuntas a thuilleadh. Is féidir leat athshocrú a thriail nó ceann nua a roghnú. + + Deisigh anois @@ -1300,8 +1320,8 @@ Baicle nua Tabhair cuirí do chairde Bain feidhm as SMS - Cuma - Cuir grianghraf leis + Dathanna comhráite + Cuir grianghraf próifíle leis Freagraí @@ -1642,14 +1662,14 @@ Glac leis Ar Aghaidh Scrios - Bac Nua + Cuir bac Bain an bac Lig do %1$s teachtaireachtaí a sheoladh chugat agus comhroinn d\'ainm agus grianghraf leis/léi? Ní bheidh a fhios ag an duine sin go bhfaca tú an teachtaireacht go dtí go nglacfaidh tú. - An bhfuil fonn ort d\'ainm agus do phictiúr a roinnt le %1$s, agus cead a thabhairt dó/di teachtaireachtaí a sheoladh chugat? Ní bhfaighidh tú aon teachtaireacht go dtí go mbainfidh tú an bac de/di. + Lig do %1$s teachtaireachtaí a sheoladh chugat agus comhroinn d\'ainm agus grianghraf leis an duine sin? Ní bhfaighidh tú aon teachtaireacht go dtí go mbainfidh tú an bac de/di. - Lig do %1$s teachtaireachtaí a sheoladh chugat? Ní bhfaighidh tú aon teachtaireachtaí go dtí go mbainfidh tú an bac. - Faigh nuashonruithe agus nuacht ó %1$s? Ní bhfaighidh tú aon nuashonruithe go dtí go mbainfidh tú an bac. + Lig do %1$s teachtaireachtaí a sheoladh chugat? Ní bhfaighidh tú aon teachtaireachtaí go dtí go mbainfidh tú an bac de/di. + Faigh nuashonruithe agus nuacht ó %1$s? Ní bhfaighidh tú aon nuashonruithe go dtí go mbainfidh tú an bac de/di. Lean le do chomhrá leis an ngrúpa seo agus comhroinn d\'ainm agus grianghraf lena bhaill? Uasghrádaigh an grúpa seo chun gnéithe nua amhail @tráchtanna agus riarthóirí a ghníomhachtú. Baill nár chomhroinn a n-ainm nó a ngrianghraf sa ghrúpa seo, tabharfar cuireadh dóibh dul isteach ann. Ní féidir an seanleagan seo den Ghrúpa a úsáid a thuilleadh toisc go bhfuil sé rómhór. Is í %1$d uasmhéid an ghrúpa. @@ -1657,7 +1677,7 @@ Téigh isteach sa ghrúpa seo agus comhroinn d\'ainm agus grianghraf lena bhaill? Ní bheidh a fhios acu go bhfaca tú na teachtaireachtaí go dtí go nglacfaidh tú. Téigh isteach sa ghrúpa seo agus comhroinn d\'ainm agus grianghraf lena bhaill? Ní fheicfidh tú na teachtaireachtaí go dtí go nglacfaidh tú. An bhfuil fonn ort páirt a ghlacadh sa ghrúpa seo? Ní bheidh a fhios acu go bhfaca tú a gcuid teachtaireachtaí go dtí go nglacfaidh tú leis. - An bhfuil fonn ort d\'ainm agus do phictiúr a roinnt leis an ngrúpa seo agus an bac a bhaint de? Ní bhfaighidh tú aon teachtaireacht go dtí go mbainfidh tú an bac de. + Bain an bac den ghrúpa seo agus comhroinn d\'ainm agus grianghraf lena bhaill? Ní bhfaighidh tú aon teachtaireachtaí go dtí go mbainfidh tú an bac de. Amharc Ball de %1$s @@ -1776,6 +1796,17 @@ Cruthaigh UAP Nua + + Seol cód SMS + + Clárú Signal — Cabhair Uait le UAP a athchlárú le haghaidh Android + + Cód %1$d+ digit a chruthaigh tú is ea d\'UAP, idir uimhriúil nó alfa-uimhriúil.\n\nMura féidir leat cuimhneamh ar d\'UAP, is féidir leat ceann nua a chruthú. + + Mura féidir leat cuimhneamh ar d\'UAP, is féidir leat ceann nua a chruthú. + + Tá an líon tomhas ar UAP úsáidte agat; is féidir leat do chuntas Signal a rochtain fós, áfach, ach UAP nua a chruthú. + Rabhadh Má dhíchumasaíonn tú an UAP, caillfidh tú na sonraí go léir nuair a athchláraíonn tú Signal mura ndéanann tú cúltacú agus aischur de láimh. Ní féidir leat Glasáil Clárúcháin a chasadh air leis an UAP díchumasaithe. @@ -1808,7 +1839,7 @@ Mo Scéal - Cuir bac air + Cuir bac Bain an bac @@ -1920,7 +1951,7 @@ Díbhalbhaigh - Balbhú + Balbhaigh Glaoigh @@ -1936,12 +1967,12 @@ - %1$s is blocked + Tá bac curtha ar %1$s Tuilleadh Eolais Ní gheobhaidh tú an fhuaim nó físeán uaidh/uaithi agus ní gheobhaidh sé/sí iad uait Can\'t receive audio & video from %1$s Can\'t receive audio and video from %1$s - This may be because they have not verified your safety number change, there\'s a problem with their device, or they have blocked you. + Seans nár fhíoraigh an duine sin athrú ar d\'uimhir sábháilteachta, go bhfuil fadhb leis an ngléas aige/aici, nó gur chuir sé/sí bac ort. Svaidhpeáil le féachaint ar chomhroinnt scáileáin @@ -1978,11 +2009,18 @@ Tá gá ag Signal riochtain ar do theagmhálaithe agus do mheáin chun nasc a dhéanamh le cairde, teachtaireachtaí a mhalairt agus glaonna slán a dhéanamh. Teastaíonn cead teagmhálaithe ó Signal chun cabhrú leat nascadh le cairde. Uaslódáiltear do theagmhálaithe trí aimsiú príobháideach teagmhálaithe de chuid Signal, rud a chiallaíonn go bhfuil siad criptithe ó cheann ceann agus dofheicthe sa tseirbhís Signal i gcónaí. Rinne tú an iomarca iarrachtaí ar an uimhir seo a chlárú. Bain triail eile as ar ball. + + Tá an iomarca iarrachtaí déanta agat leis an uimhir sin a chlárú. Triail arís i gceann %1$s. Ní féidir ceangal a bhunú leis an tseirbhís. Deimhnigh go bhfuil tú ceangailte leis an líonra agus bain triail eile as. Formáid uimhreacha neamhchaighdeánach Is cosúil go bhfuil formáid neamhchaighdeánach ar an uimhir a chuir tú isteach (%1$s).\n\nAn raibh %2$s i gceist agat? Molly Android — Formáid don Uimhir Ghutháin + Glao iarrtha + + SMS iarrtha + + Cód fíoraithe iarrtha Tá %1$d chéim fágtha anois roimh loga dífhabhtaithe a chur isteach. Tá %1$d chéim fágtha anois roimh loga dífhabhtaithe a chur isteach. @@ -2005,6 +2043,16 @@ Glao Cód Fíoraithe Athsheol Cód + + Deacracht agat le clárú? + + • Cinntigh go bhfuil comhartha móibíleach ag do ghuthán chun do SMS nó glao a fháil\n • Deimhnigh gur féidir leat glao gutháin a fháil ar an uimhir\n • Cinntigh gur chuir tú d\'uimhir ghutháin isteach i gceart. + + Le tuilleadh faisnéise a fháil, lean na céimeanna fabhtcheartaithe seo nó déan teagmháil leis an bhFoireann Tacaíochta + + na céimeanna fabhtcheartaithe seo + + Déan Teagmháil leis an bhFoireann Tacaíochta An bhfuil fonn ort Glas Clárúcháin a chur ar siúl? @@ -2171,6 +2219,10 @@ Íocaíocht Teachtaireacht sceidealaithe + + Cumascadh do stair teachtaireachtaí + + Is le %2$s %1$s Nuashonrú Molly @@ -2303,14 +2355,16 @@ SMS nach bhfuil slán %1$s %2$s Teagmhálaí - Freagraíodh %1$s do: \"%2$s\". - Freagraíodh %1$s do d\'fhíseán. - Freagraíodh %1$s do d\'íomhá. - Freagraíodh %1$s do do GIF. - Freagraíodh %1$s do do chomhad. - D\'fhreagair %1$s do mhír fuaime. - Freagraíodh %1$s do do mheán amhairc aonuaire. - Freagraíodh %1$s do do ghreamán. + Tugadh freagairt %1$s do: \"%2$s\". + Tugadh freagairt %1$s do d\'fhíseán. + Tugadh freagairt %1$s do d\'íomhá. + Tugadh freagairt %1$s do do GIF. + Tugadh freagairt %1$s do do chomhad. + Tugadh freagairt %1$s do do mhír fuaime. + Tugadh freagairt %1$s do do mheán amhairc aonuaire. + + Reacted %1$s to your payment. + Tugadh freagairt %1$s do do ghreamán. Scriosadh an teachtaireacht seo. Ar mhúch tú na fógraí gur thosaigh teagmhálaithe ag baint feidhm as Signal? Is féidir leat iad a chumasú arís i Signal > Socruithe > Fógraí. @@ -2526,7 +2580,7 @@ Cumasaigh fógraí do ghlaonna Nuashonraigh an teagmhálaí - Cuir Bac ar an Iarratas + Cuir bac ar an iarratas No groups in common. Review requests carefully. No contacts in this group. Review requests carefully. Amharc @@ -2884,9 +2938,9 @@ Bain feidhm as an réamhshocrú Bain feidhm as saincheapú - Balbhaigh ar feadh 1 uair - Balbhaigh ar feadh 8 n-uair a chloig - Balbhaigh ar feadh 1 lá + Balbhaigh ar feadh 1 uair an chloig + Balbhaigh ar feadh 8 n-uair an chloig + Balbhaigh ar feadh 1 lae Balbhaigh ar feadh 7 lá I gcónaí @@ -2927,9 +2981,9 @@ Úsáid grianghraif sa leabhar seoltaí Taispeáin grianghraif na dteagmhálacha ó do leabhar seoltaí má tá siad ar fáil - Coinnigh Comhráite Balbhaithe sa Chartlann + Coinnigh comhráite balbhaithe sa chartlann - Fanfaidh comhráite balbhaithe atá curtha i gcartlann sa chartlann fós nuair a thagann teachtaireacht nua isteach. + Fanfaidh comhráite balbhaithe sa chartlann fós nuair a thagann teachtaireacht nua isteach. Generate Link Previews Retrieve link previews directly from websites for messages you send. Athraigh an nath faire @@ -3223,7 +3277,7 @@ Sent Payment Received Payment Íocaíocht críochnaithe %1$s - Block Number + Cuir bac ar an uimhir Aistrigh @@ -3308,7 +3362,7 @@ Teachtaireacht nua chuig… - Cuir Bac ar an Úsáideoir Seo + Cuir bac ar an úsáideoir Cuir leis an nGrúpa é/í @@ -3559,6 +3613,8 @@ Cuir isteach d\'UAP Cuir isteach an UAP a chruthaigh tú don chuntas seo. Ní ionann é seo agus an cód deimhniúcháin SMS. + + Cuir isteach an UAP a chruthaigh tú do do chuntas. Cuir isteach UAP alfa-uimhriúil Cuir isteach UAP uimhriúil UAP mhícheart. Bain triail eile as. @@ -3677,7 +3733,10 @@ Tá comhad an-mhór i do chúltaca nach féidir a chúltacú. Scrios é agus cruthaigh cúltaca nua. Tapáil chun cúltacaí a bhainistiú. Uimhir mhícheart? + Glaoigh orm (%1$02d:%2$02d) + + Athsheol Cód (%1$02d:%2$02d) Déan teagmháil le foireann tacaíochta Signal Clárúchán Signal — Cód Deimhniúcháin ar Android Cód mícheart @@ -3685,6 +3744,18 @@ Anaithnid See My Phone Number Find Me by Phone Number + + Uimhir ghutháin + + Roghnaigh na daoine atá in ann d\'uimhir ghutháin a fheiceáil agus teagmháil a dhéanamh leat ar Molly léi. + + Na daoine atá in ann m\'uimhir a fheiceáil + + Ní fheicfidh aon duine d\'uimhir ghutháin ar Molly + + Na daoine atá in ann mé a aimsiú le m\'uimhir + + Beidh d\'uimhir ghutháin infheicthe ag daoine agus grúpaí a seolann tú teachtaireachtaí chucu. Daoine a bhfuil d\'uimhir ina gcuid teagmhálaithe gutháin acu, feicfidh siad an uimhir ar Molly freisin. Gach duine My Contacts Duine ar bith @@ -3841,7 +3912,7 @@ - Bac Nua + Cuir bac Bain an bac Cuir lena teagmhálaithe @@ -3894,9 +3965,9 @@ %1$s / %2$s - Cuireadh bac ar \"%1$s\". - Theip ar bhac a chur ar \"%1$s\" - Baineadh an bac de \"%1$s\". + Bac curtha ar \"%1$s\". + Theip ar chur baic ar \"%1$s\" + Bac bainte de \"%1$s\". Review Members @@ -3929,7 +4000,7 @@ Do theagmhálaí Bain den ghrúpa Nuashonraigh an teagmhálaí - Bac Nua + Cuir bac Scrios D\'athraigh sé/sí a ainm/hainm próifíle ó %1$s go %2$s. @@ -3952,11 +4023,11 @@ Wi-Fi lag. Athraíodh go comhartha móibíleach. - Má dhéanann tú do chuntas a scriosadh, is é an toradh a bheidh air ná: + Tarlóidh an méid seo a leanas le scriosadh do chuntais: Cuir isteach d\'uimhir ghuthàn Scrios cuntas - Scrios faisnéis do chuntais agus an grianghraf próifíle - Scrios do theachtaireachtaí go léir + Déanfar faisnéis do chuntais agus an grianghraf próifíle a scriosadh + Déanfar do theachtaireachtaí go léir a scriosadh Scrios %1$s i do chuntas íocaíochtaí Níl aon chód tíre sonraithe Níl aon uimhir sonraithe @@ -4077,7 +4148,7 @@ Deactivate Without Transferring Díchumasaigh Deactivate Without Transferring? - Your balance will remain in your wallet linked to Molly if you choose to reactivate payments. + Fanfaidh d\'iarmhéid i do sparán atá nasctha le Molly má roghnaíonn tú íocaíochtaí a athghníomhachtú. Tharla earráid le díghníomhachtú an sparáin. @@ -4294,7 +4365,7 @@ Cruthaigh próifíl - Bactha + Bac curtha Líon teagmhálaithe: %1$d Ag seoladh teachtaireachtaí Teachtaireachtaí a imíonn as amharc @@ -4462,7 +4533,7 @@ Glaoigh - Balbhú + Balbhaigh Balbhaithe @@ -4472,10 +4543,10 @@ Contact Details Taispeáin an Uimhir Shábháilteachta - Bac Nua - Cuir bac ar an ngrúpa seo + Cuir bac + Cuir bac ar an ngrúpa Bain an bac - Bain an bac den Ghrúpa + Bain an bac den ghrúpa Add to a Group See All Cuir baill leis @@ -4502,7 +4573,7 @@ Balbhaigh fógraí - Gan balbhú + Gan bhalbhú Nuair a luaitear mé Cuir scéal chugam i gcónaí Ná cuir ar an eolas @@ -4531,7 +4602,7 @@ Bain - Cuir bac air/uirthi + Cuir bac Bain %1$s? @@ -4774,7 +4845,7 @@ Cuireadh do thabhartas míosúil athfhillteach ar ceal mar níorbh fhéidir linn d\'íocaíocht a phróiseáil. Níl do shuaitheantas infheicthe ar do phróifíl a thuilleadh. Cuireadh do thabhartas míosúil athfhillteach ar ceal. %1$s Níl do shuaitheantas %2$s infheicthe ar do phróifíl a thuilleadh. - Is féidir leanúint le húsáid Signal ach chun tacú leis an aip agus do shuaitheantas a athghníomhachtú, athnuaigh anois. + Is féidir leat leanúint le húsáid Signal ach chun tacú leis an aip agus do shuaitheantas a athghníomhachtú, athnuaigh anois. Athnuaigh síntiús Téigh chuig Google Pay @@ -4817,7 +4888,7 @@ Níorbh fhéidir do thabhartas a sheoladh de dheasca earráid líonra. Seiceáil do nasc agus triail arís. - Tabhartas do %1$s + Tabhartas ar son %1$s Thug %1$s tabhartas do Signal ar do shon @@ -5232,11 +5303,11 @@ Roghnaigh an lucht féachana ar do scéal. Ní bheidh tionchar ag athruithe ar scéalta a sheol tú cheana. - Freagraí agus Freagairtí + Freagraí agus freagairtí - Ceadaigh Freagraí agus Freagairtí + Ceadaigh freagraí agus freagairtí - Lig don lucht féachana freagair agus freagairt do do scéal + Lig don lucht féachana freagra agus freagairt a thabhairt ar do scéal Teagmhálaithe Signal @@ -5976,5 +6047,15 @@ Scrios ainm úsáideora + + + u + + n + + Socraigh + + Is é 1 nóiméad an t-íoslíon ama roimh an glas scáileáin a chur i bhfeidhm. + diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 1ce7adb0af..ec71bbe821 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -14,13 +14,14 @@ + Si Non Borrar Por favor, agarda… Gardar - Notificacións propias + Notas privadas @@ -480,20 +481,20 @@ %1$d conversas movéronse á caixa de entrada - Lidos - Lido + Lido + Lidos - Sen ler - Sen ler + Non lido + Non lidos - Ancorar - Ancorar + Fixar + Fixar - Desancorar - Desancorar + Soltar + Soltar Silenciar @@ -542,6 +543,15 @@ +%1$d + + Volve vincular os dispositivos + + Os dispositivos que conectaches desvinculáronse cando o teu dispositivo quedou sen rexistrar. Vai a Configuración e volve vincular todos os dispositivos. + + Abrir configuración + + Máis tarde + Elexir membros @@ -935,7 +945,7 @@ Notificarme as mencións - Recibir notificacións cando o mencionen en conversas silenciadas? + Queres recibir notificacións cando te mencionen en conversas silenciadas? Notificarme sempre Non notificarme @@ -953,6 +963,16 @@ Nome de usuario creado Nome de usuario copiado + + Erro ao borrar o nome de usuario. Volver tentar máis tarde. + + Nome de usuario borrado + + + + Algo saíu mal co teu nome de usuario e xa non está asignado á túa conta. Podes probar e configuralo de novo ou elixir un novo. + + Corrixir @@ -1156,8 +1176,8 @@ Novo grupo Convidar amizades Utilizar SMS - Aparencia - Engadir foto + Cor da conversa + Engadir unha foto de perfil Respostas @@ -1472,9 +1492,9 @@ Desbloquear Queres permitir que %1$s che envíe mensaxes e comparta o teu nome e foto con eles? Non poderán saber que viches a súas mensaxes ata que aceptes. - Queres permitir que %1$s che envíe mensaxes e compartir o teu nome e foto con eles? Non recibirás ningunha mensaxe ata que os desbloquees. + Queres compartir o teu nome e foto con %1$s e que che envíe mensaxes? Non recibirás ningunha mensaxe ata que o desbloquees. - Permitir que %1$s che envíe mensaxes? Non poderás recibir ningunha mensaxe ata que desbloquees o contacto. + Deixar que %1$s che envíe mensaxes? Non recibirás ningunha mensaxe ata que desbloquees o contacto. Queres recibir novidades de %1$s? Non recibirás ningunha noticia ata que desbloquees o contacto. Continuar a conversa con este grupo e compartir o teu nome e fotografía cos seus membros? Actualizar este grupo para activar novas funcionalidades como son as @mencións e os administradores. Aqueles membros que non compartiran o seu nome e fotografía co grupo serán convidados a unirse. @@ -1483,7 +1503,7 @@ Unirse a este grupo e compartir cos seus membros o teu nome e fotografía? Non saberán quen viu as súas mensaxes ata que aceptes. Queres unirte a este grupo e compartir o teu nome e a túa foto cos seus membros? Non verás as súas mensaxes ata que aceptes. Unirse a este grupo? Non saberán quen viu as súas mensaxes ata que aceptes. - Desbloquear este grupo e compartir cos seus membros o teu nome e fotografía? Non recibirás ningunha mensaxe ata que desbloquees. + Desbloquear este grupo e compartir cos seus membros o teu nome e foto? Non recibirás ningunha mensaxe ata que o desbloquees. Ver Membro de %1$s @@ -1584,9 +1604,20 @@ Crear novo PIN + + Enviar código SMS + + Rexistro en Signal - Preciso axuda para volver rexistrar o PIN en Android + + O teu PIN é un código de máis de %1$d díxitos creado por ti e que pode ser numérico ou alfanumérico.\n\nSe non lembras o teu PIN, podes crear un novo. + + Se non lembras o teu PIN, podes crear un novo. + + Esgotaches os intentos para adiviñar o PIN, mais aínda podes acceder á túa conta de Signal creando un novo PIN. + Aviso - Se desactivas o PIN perderás todos os datos cando voltes a rexistrar Signal a menos que fagas unha copia de apoio e a restaures. Non podes activar Bloqueo do Rexistro se o PIN está desactivado. + Se desactivas o PIN, perderás todos os datos cando volvas rexistrarte en Signal a menos que fagas unha copia de seguranza e a restaures. Non podes activar o Bloqueo do rexistro se o PIN está desactivado. Desactivar PIN @@ -1711,7 +1742,7 @@ Cámara - Activar o son + Desactivar silencio Silenciar @@ -1731,7 +1762,7 @@ Non recibirás o seu audio ou vídeo nin eles o teu. Non se pode recibir o audio e vídeo de %1$s Non se pode recibir o audio e vídeo de %1$s - Esto podería ser debido a que non verificaron o cambio no teu número de seguridade, hai un problema co seu dispositivo ou a que te bloquearon. + Pode ser que non verificasen o cambio do teu número de seguridade, que haxa un problema co seu dispositivo ou que te bloqueasen. Arrastra para ver a pantalla compartida @@ -1768,11 +1799,18 @@ Signal necesita ter os permisos activos para acceder aos teus contactos e multimedia e así axudarche a conectar cos teus amigos e enviarlles mensaxes. Os teus contactos cargaranse a través do modo de detección privado de Signal, o que significa que están encriptados de extremo a extremo e Signal nunca poderá velos. Signal necesita ter o permiso activo para acceder aos teus contactos e axudarche a contectar cos teus amigos. Os teus contactos cargaranse a través do modo de detección privado de Signal, o que significa que están encriptados de extremo a extremo e Signal nunca poderá velos. Levas demasiados intentos para rexistrar este número. Téntao máis tarde. + + Levas demasiados intentos para rexistrar este número. Téntao de novo en %1$s. Non é posible contactar co servizo. Comproba a túa conexión de rede e téntao de novo. Número con formato non-estándar O número escrito (%1$s) non semella estar nun formato estándar.\n\nSerá máis ben %2$s? Molly para Android - Formato de número de teléfono + Chamada solicitada + + Solicitouse unha SMS + + Solicitouse un código de verificación Estás a %1$d pasos de enviar un informe de depuración. Estás a %1$d pasos de enviar un informe de depuración. @@ -1792,6 +1830,16 @@ Chamar Código de verificación Volver enviar código + + Tes problemas para rexistrarte? + + • Asegúrate de que o teu teléfono teña sinal móbil para recibir mensaxes SMS ou chamadas\n • Comproba que podes recibir unha chamada a ese número\n • Revisa se introduciches o número de teléfono correctamente. + + Para obter máis información, segue estes pasos para solucionar o problema ou contacta coa Axuda + + estes pasos para solucionar o problema + + Contactar coa Axuda Activar o bloqueo de rexistro? @@ -1958,6 +2006,10 @@ Pagamento Mensaxe programada + + O teu historial de mensaxes xuntouse + + %1$s pertence a %2$s Actualizar Molly @@ -2087,14 +2139,16 @@ SMS insegura %1$s %2$s Contacto - Reaccionou con %1$s a: \"%2$s\" + Reaccionou con %1$s a: «%2$s». Reaccionou con %1$s ao teu vídeo. Reaccionou con %1$s á túa imaxe. %1$s reaccionou ao teu GIF. - Reaccionou con %1$s ao teu ficheiro. + Reaccionou con %1$s ao teu arquivo. Reaccionou con %1$s ao teu audio. - Reaccionou con %1$s ao teu multimedia dunha soa visualización - Reaccionou con %1$s ao teu adhesivo. + Reaccionou con %1$s ao teu arquivo multimedia dunha soa visualización. + + Reaccionou con %1$s ao teu pagamento. + Reaccionou con %1$s ao teu sticker. Eliminouse a mensaxe. Desactivar as notificacións de quen se une a Signal? Poderás volver activalas en Signal> Axustes > Notificacións. @@ -2487,8 +2541,8 @@ Problema co envío - Non se che puido entregar unha mensaxe, adhesivo, reacción ou informe de lectura de %1$s. Puido ser unha entrega directa ou desde un grupo. - Non se che puido entregar unha mensaxe, adhesivo ou informe de lectura desde %1$s. + Non se che puido entregar unha mensaxe, sticker, reacción ou informe de lectura de %1$s. Puido ser unha entrega directa ou desde un grupo. + Non se che puido entregar unha mensaxe, sticker ou informe de lectura de %1$s. Nome (obrigatorio) @@ -2636,7 +2690,7 @@ Utilizar o personalizado Silenciar durante 1 hora - Acalar durante 8 horas + Silenciar durante 8 horas Silenciar durante 1 día Silenciar durante 7 días Sempre @@ -2675,9 +2729,9 @@ Utilizar fotografías da axenda de enderezos Mostra as fotografías dos contactos a partir da axenda, caso de estaren dispoñibles - Keep Muted Chats Archived + Manter as conversas silenciadas arquivadas - Muted chats that are archived will remain archived when a new message arrives. + As conversas silenciadas que se arquivaron permanecerán así ao recibir unha nova mensaxe. Crear vista previa de ligazóns Obter vistas previas das ligazóns directamente desde os sitios web para as mensaxes que envías. Cambiar frase de acceso @@ -3128,7 +3182,7 @@ - Activar o son + Desactivar silencio Silenciar notificacións @@ -3295,6 +3349,8 @@ Escribe o teu PIN Escribe o PIN creado para a túa conta. Non é o mesmo que o código de verificación por SMS. + + Introduce o PIN que creaches para a túa conta. Escribe PIN alfanumérico Escribe PIN numérico PIN incorrecto. Inténtao outra vez. @@ -3398,7 +3454,10 @@ A túa copia de seguranza contén un arquivo moi grande que non se pode gardar. Elimínao e crea unha nova copia. Toca para administrar as copias de seguranza. Número incorrecto? + Chámame (%1$02d:%2$02d) + + Volver enviar código (%1$02d:%2$02d) Contacta co Centro de Axuda de Signal Rexistrarse en Signal - Código de verificación para Android Código incorrecto @@ -3406,6 +3465,18 @@ Descoñecido Ver o meu número de teléfono Atoparme polo número de teléfono + + Número de teléfono + + Escolle quen pode ver o teu número de teléfono e quen pode contactar contigo en Molly con el. + + Quen pode ver o meu número de teléfono + + Ninguén poderá ver o teu número de teléfono en Molly + + Quen me pode atopar polo meu número + + Poderán ver o teu número de teléfono as persoas ou grupos aos que envíes mensaxes. As persoas que teñan o teu número na súa lista de contactos tamén o poderán ver en Molly. Todos Os meus contactos Ninguén @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" foi bloqueado. - Erro ao bloquear a \"%1$s\" - \"%1$s\" foi desbloqueado. + Bloqueouse a «%1$s». + Erro ao bloquear a «%1$s» + Desbloqueouse a «%1$s». Revisar membros @@ -4008,7 +4079,7 @@ Mensaxería Desaparición das mensaxes Seguranza da app - Bloquea as capturas na listaxe de recentes e no interior da aplicación. + Bloquea as capturas na listaxe de recentes e na aplicación Mensaxes e chamadas Signal, redirección de chamadas e remitente selado Establecer duración das novas conversas Establece a duración das mensaxes temporais nas novas conversas que ti inicies. @@ -4167,7 +4238,7 @@ Silenciar - Acalado + En silencio Buscar Desaparición das mensaxes @@ -4176,9 +4247,9 @@ Detalles do contacto Ver número de seguranza Bloquear - Bloquear un grupo + Bloquear grupo Desbloquear - Desbloquear un grupo + Desbloquear grupo Engadir ao grupo Ver todo Engadir membros @@ -4186,8 +4257,8 @@ Solicitudes e convites Ligazón do grupo Engadir como contacto - Activar o son - Conversa acalada ata %1$s + Desactivar silencio + Conversa silenciada ata %1$s Conversa silenciada para sempre Número de teléfono copiado no portapapeis. Número de teléfono @@ -4453,7 +4524,7 @@ Cancelar donativo mensual A túa insignia de Empurrón caducou e xa non aparece no teu perfil. - Podes reactivar a túa insignia de Empurrón durante 30 días máis cun donativo puntual. + Podes reactivar a túa insignia de doante durante 30 días máis cun donativo puntual. Podes seguir empregando Signal, pero para apoiar as tecnoloxías que se centran en ti, considera formar parte do noso plan de doantes mensuais. Forma parte do noso plan de doantes @@ -4508,7 +4579,7 @@ Non se enviou a túa doazón por un erro de rede. Comproba a túa conexión e inténtao de novo. - Doazón a %1$s + Doazón en nome de %1$s %1$s doou a Signal no teu nome @@ -4905,9 +4976,9 @@ Elixe quen pode ver a túa historia. Os cambios non afectarán as historias que xa compartiches. - Respostas & reaccións + Respostas e reaccións - Permitir respostas & reaccións + Permitir respostas e reaccións Permitir ás persoas que ven a túa historia reaccionar e responder @@ -5601,5 +5672,15 @@ Borrar nome de usuario + + + h + + min + + Fixar + + O tempo mínimo antes de que a pantalla se bloquee é de 1 minuto. + diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml index 019e615620..4dbb5ba38c 100644 --- a/app/src/main/res/values-gu/strings.xml +++ b/app/src/main/res/values-gu/strings.xml @@ -14,13 +14,14 @@ + હા ના ડિલીટ કરો મહેરબાની કરીને રાહ જુઓ… સેવ કરો - પોતાના માટે નોટ + પોતાના માટે નોંધ @@ -96,11 +97,11 @@ મેસેજ માટે ચકાસી રહ્યું છે… - બ્લૉક કરેલ વપરાશકર્તાઓ - બ્લોક કરેલ વપરાશકર્તા ઉમેરો - અવરોધિત વપરાશકર્તાઓ તમને કૉલ કરી શકશે નહીં અથવા તમને મેસેજ મોકલશે નહીં. - બ્લોક કરેલ વપરાશકર્તા નથી - વપરાશકર્તાને બ્લોક કરવા છે? + બ્લૉક કરેલા ઉપયોગકર્તાઓ + બ્લૉક કરેલા ઉપયોગકર્તા ઉમેરો + બ્લૉક કરેલા ઉપયોગકર્તાઓ તમને કૉલ કે મેસેજ કરી શકશે નહીં. + કોઈ બ્લૉક કરેલા ઉપયોગકર્તા નથી + ઉપયોગકર્તાને બ્લૉક કરવા છે? \"%1$s\" તમને કૉલ કરી શકશે નહીં અથવા તમને મેસેજીસ મોકલશે નહીં. બ્લૉક કરો @@ -138,8 +139,8 @@ ચાલુ રાખો - અનબ્લૉક કરવુ છે અને છોડી દેવુ છે %1$s? - %1$s ને બ્લોક કરો + %1$sને બ્લૉક કરીને છોડી દેવા છે? + %1$sને બ્લૉક કરવા છે? તમે હવે આ ગ્રુપમાંથી સંદેશા અથવા અપડેટ્સ પ્રાપ્ત કરશો નહીં, અને સભ્યો તમને ફરીથી આ ગ્રુપમાં ઉમેરવા માટે સમર્થ હશે નહીં. ગ્રુપના સભ્યો તમને આ જૂથમાં ફરીથી ઉમેરવા માટે સમર્થ હશે નહીં. ગ્રુપના સભ્યો તમને આ ગ્રુપમાં ફરીથી ઉમેરવામાં સમર્થ હશે. @@ -147,16 +148,16 @@ તમે એકબીજાને મેસેજ અને કૉલ કરી શકશો અને તમારું નામ અને ફોટો તેમની સાથે શેર કરવામાં આવશે. તમે એકબીજાને મેસેજ કરી શકશો. - અવરોધિત લોકો તમને કૉલ કરી શકશે નહીં અથવા તમને મેસેજ મોકલશે નહીં. - બ્લોક કરેલા લોકો તમને મેસેજ મોકલી શકશે નહીં. + બ્લૉક કરેલા લોકો તમને કૉલ કે મેસેજ કરી શકશે નહીં. + બ્લૉક કરેલા લોકો તમને મેસેજ મોકલી શકશે નહીં. - Signal અપડેટ્સ અને સમાચારો મેળવવાનું બ્લોક કરો. + Signal અપડેટ અને સમાચારો મેળવવાનું બ્લૉક કરો. Signal અપડેટ્સ અને સમાચારો મોકલવાનું ફરી શરૂ કરો. - અન બ્લોક કરવું છે%1$s? - અવરોધિત કરો - બ્લોક કરો અને નીકળો - સ્પામ અને બ્લૉક રિપોર્ટ કરો + %1$sને અનબ્લૉક કરવા છે? + બ્લૉક કરો + બ્લૉક કરો અને નીકળો + સ્પામ તરીકે રિપોર્ટ કરો અને બ્લૉક કરો આજે @@ -318,7 +319,7 @@ Signal મેસેજ ચાલો સિગ્નલ પર સ્વિચ કરીએ %1$s કૃપા કરીને સંપર્ક પસંદ કરો - અનાવરોધિત કરો + અનબ્લૉક કરો તમે મોકલો છો તે મેસેજ ના પ્રકાર માટે જોડાણ કદની મર્યાદાથી વધુ છે. ઓડિયો રેકોર્ડ કરવામાં અસમર્થ તમે આ જૂથને મેસેજ મોકલી શકતા નથી કારણ કે તમે હવે સભ્ય નથી. @@ -366,7 +367,7 @@ મીડિયા મોકલવામાં ભૂલ - સ્પામ તરીકે નોંધાયેલ છે અને બ્લૉક કરેલ છે. + સ્પામ તરીકે રિપોર્ટ કર્યું અને બ્લૉક કર્યા. SMS મેસેજિંગ હાલમાં બંધ છે. તમે તમારા મેસેજને તમારા ફોન પરની અન્ય ઍપમાં એક્સપોર્ટ કરી શકો છો. @@ -492,16 +493,16 @@ પિન કરો - પિન દૂર કરો - પિન દૂર કરો + અનપિન કરો + અનપિન કરો - મ્યુટ કરો - મ્યુટ કરો + મ્યૂટ કરો + મ્યૂટ કરો - અનમ્યુટ કરો - અનમ્યુટ કરો + અનમ્યૂટ કરો + અનમ્યૂટ કરો પસંદ કરો @@ -542,6 +543,15 @@ +%1$d + + તમારા ડિવાઇસને ફરીથી લિંક કરો + + જ્યારે તમારું ડિવાઇસ નોંધાયેલ ન હતું ત્યારે તમે ઉમેરેલા ડિવાઇસને અનલિંક કરવામાં આવ્યા હતા. કોઈ પણ ડિવાઇસને ફરીથી લિંક કરવા માટે સેટિંગ્સ પર જાઓ. + + સેટિંગ્સ ખોલો + + પછી + સભ્યો પસંદ કરો @@ -935,7 +945,7 @@ મને ઉલ્લેખો માટે સૂચિત કરો - જ્યારે તમારો મ્યુટ ચેટમાં ઉલ્લેખ થાય છે ત્યારે સૂચનાઓ પ્રાપ્ત કરવી છે? + જ્યારે મ્યૂટ કરેલી ચેટમાં તમારો ઉલ્લેખ કરવામાં આવે ત્યારે નોટિફિકેશન મેળવવા માગો છે? હંમેશા મને સૂચિત કરો મને સૂચિત કરશો નહીં @@ -953,6 +963,16 @@ યુઝરનેમ બનાવ્યું યુઝરનેમ કૉપી કર્યું + + યુઝરનેમ ડિલીટ કરી શક્યા નહીં. પછી ફરી પ્રયાસ કરો. + + યુઝરનેમ ડિલીટ કર્યું + + + + તમારા યુઝરનેમ સાથે કંઈક ખોટું થયું છે, તે હવે તમારા એકાઉન્ટને સોંપાયેલું નથી. તમે તેને ફરીથી સેટ કરવાનો પ્રયાસ કરી શકો છો અથવા નવું પસંદ કરી શકો છો. + + અત્યારે ઠીક કરો @@ -1156,8 +1176,8 @@ નવું ગ્રુપ મિત્રોને આમંત્રિત કરો SMS નો ઉપયોગ કરો - દેખાવ - ફોટો ઉમેરો + ચેટ કલર + કોઈ પ્રોફાઇલ ફોટો ઉમેરો જવાબો @@ -1468,14 +1488,14 @@ સ્વીકાર ચાલુ રાખો ડિલીટ કરો - અવરોધિત કરો - અનાવરોધિત કરો + બ્લૉક કરો + અનબ્લૉક કરો શું તમે %1$s ને મેસેજ અને તેમની સાથે તમારું નામ અને ફોટો કરવા શેર માંગો છો ? જ્યાં સુધી તમે સ્વીકારશો નહીં ત્યાં સુધી તેઓ જાણશે નહીં કે તમે તેમનો મેસેજ જોયો છે. - %1$s ને તમને મેસેજ કરવા દો અને તેમની સાથે તમારું નામ અને ફોટો શેર કરવા છે? જ્યાં સુધી તમે તેમને અનબ્લોક નહીં કરો ત્યાં સુધી તમને કોઈ મેસેજ પ્રાપ્ત થશે નહીં. + %1$sને તમને મેસેજ કરવા અને તેમની સાથે તમારું નામ અને ફોટો શેર કરવા દેવા છે? જ્યાં સુધી તમે તેમને અનબ્લૉક નહીં કરો ત્યાં સુધી તમને કોઈ મેસેજ પ્રાપ્ત થશે નહીં. - %1$sને તમને મેસેજ કરવા દેવા માંગો છો? તમે તેમને અનબ્લૉક ન કરો ત્યાં સુધી તમે કોઈ મેસેજ પ્રાપ્ત નહીં કરો. - %1$s તરફથી અપડેટ્સ અને સમાચાર પ્રાપ્ત કરવા છે? તમે તેમને અનબ્લૉક ન કરો ત્યાં સુધી કોઈ અપડેટ્સ મેળવશો નહીં. + %1$sને તમને મેસેજ કરવા દેવા છે? તમે તેમને અનબ્લૉક નહીં કરો ત્યાં સુધી તમને કોઈ મેસેજ પ્રાપ્ત થશે નહીં. + %1$s તરફથી અપડેટ અને સમાચાર મેળવવા માગો છે? તમે તેમને અનબ્લૉક નહીં કરો ત્યાં સુધી કોઈ અપડેટ મેળવશો નહીં. આ ગ્રુપ સાથે તમારી વાતચીત ચાલુ રાખવી છે અને તેના સભ્યો સાથે તમારું નામ અને ફોટો શેર કરવા છે? \@ઉલ્લેખો અને એડમિન જેવા નવા ફીચર્સ સક્રિય કરવા માટે આ ગ્રુપને અપગ્રેડ કરો. જે સભ્યો આ ગ્રુપમાં પોતાનું નામ અથવા ફોટો શેર કર્યો નથી તેમને જોડાવા માટે આમંત્રણ આપવામાં આવશે. આ લેગસી ગ્રુપને નવા ગ્રુપમાં અપગ્રેડ કરી શકાતું નથી કારણ કે તે ખૂબ મોટું છે. ગ્રુપનું મહત્તમ કદ %1$d છે. @@ -1483,7 +1503,7 @@ શું આ ગ્રુપમાં જોડાવું છે અને તેના સભ્યો સાથે તમારું નામ અને ફોટો શેર કરવા છે? જ્યાં સુધી તમે સ્વીકારશો નહીં ત્યાં સુધી તેઓ જાણશે નહીં કે તમે તેમના મેસેજ જોયા છે. શું આ ગ્રુપમાં જોડાવું છે અને તેના સભ્યો સાથે તમારું નામ અને ફોટો શેર કરવા છે? તમે સ્વીકારશો નહીં ત્યાં સુધી તમે તેમના મેસેજ જોશો નહીં. આ ગ્રુપને રદ કરવું છે? જ્યાં સુધી તમે સ્વીકારશો નહીં ત્યાં સુધી તેઓ જાણશે નહીં કે તમે તેમના મેસેજ જોયા છે. - આ ગ્રુપને અનબ્લોક કરવું છે અને તેના સભ્યો સાથે તમારું નામ અને ફોટો શેર કરવા? જ્યાં સુધી તમે તેમને અનબ્લોક નહીં કરો ત્યાં સુધી તમને કોઈ મેસેજ પ્રાપ્ત થશે નહીં. + આ ગ્રૂપને અનબ્લૉક કરી અને તેના સભ્યો સાથે તમારું નામ અને ફોટો શેર કરવા છે? તમે તેમને અનબ્લૉક નહીં કરો ત્યાં સુધી તમને કોઈ મેસેજ પ્રાપ્ત થશે નહીં. વ્યૂ %1$s ના સભ્ય @@ -1584,9 +1604,20 @@ નવો PIN બનાવો + + SMS કોડ મોકલો + + Signal રજિસ્ટ્રેશન- Android માટે પિન રજિસ્ટર કરવા બાબતે સહાયની જરૂર છે + + તમારો પિન એ તમે બનાવેલ %1$d+ અંકનો કોડ છે જે આંકડાકીય અથવા આલ્ફાન્યૂમેરિક હોઈ શકે છે.\n\nજો તમને તમારો પિન યાદ ન હોય, તો તમે નવો બનાવી શકો છો. + + જો તમને તમારો પિન યાદ ન હોય, તો તમે નવો બનાવી શકો છો. + + તમે તમારા પિનના બહુ બધા અનુમાન લગાવી લીધા છે, પરંતુ તમે હજી પણ નવો પિન બનાવીને તમારા Signal એકાઉન્ટને ઍક્સેસ કરી શકો છો. + ચેતવણી - જો તમે PIN ને અક્ષમ કરો છો, તો જ્યારે તમે Signal ને ફરીથી રજીસ્ટર કરશો ત્યારે તમે તમામ ડેટા ગુમાવશો જ્યાં સુધી તમે તેને મેન્યુઅલી સ્થાનાંતરિત ન કરો. જ્યારે PIN અક્ષમ હોય ત્યારે તમે રજીસ્ટ્રેશન લૉક ચાલુ કરી શકતા નથી. + જો તમે પિનને અક્ષમ કરો છો, તો જ્યારે તમે Signalને ફરીથી રજીસ્ટર કરશો ત્યારે તમે તમામ ડેટા ગુમાવશો જો તમે તેને મેન્યુઅલી બેકઅપ અને રિસ્ટોર ન કરો. જ્યારે પિન અક્ષમ હોય ત્યારે તમે રજીસ્ટ્રેશન લૉક ચાલુ કરી શકતા નથી. PIN અક્ષમ કરો @@ -1616,8 +1647,8 @@ મારી સ્ટોરી - અવરોધિત કરો - અનાવરોધિત કરો + બ્લૉક કરો + અનબ્લૉક કરો @@ -1711,9 +1742,9 @@ કૅમેરા - અનમ્યૂટ + અનમ્યૂટ કરો - મ્યુટ + મ્યૂટ કરો રિંગ @@ -1726,12 +1757,12 @@ - %1$s ને બ્લૉક કરેલ છે + %1$sને બ્લૉક કર્યા છે વધુ માહિતી તમને તેમનો ઓડિયો કે વીડિયો પ્રાપ્ત થશે નહીં અને તેઓ તમારો પ્રાપ્ત નહીં કરે. %1$s તરફથી ઓડિયો & વિડિયો પ્રાપ્ત કરી શકતા નથી %1$s તરફથી ઓડિયો અને વિડિયો પ્રાપ્ત કરી શકતા નથી - આ એટલા માટે હોઈ શકે છે કારણ કે તેઓએ તમારા સલામતી નંબર પરિવર્તનની ચકાસણી કરી નથી, તેમના ડિવાઇસમાં સમસ્યા છે, અથવા તેઓએ તમને બ્લૉક કર્યા છે. + આ એટલા માટે હોઈ શકે છે કારણ કે તેઓએ તમારા સલામતી નંબર બદલાવની ચકાસણી કરી નથી, તેમના ડિવાઇસમાં સમસ્યા છે, અથવા તેઓએ તમને બ્લૉક કર્યા છે. સ્ક્રીન શેર જોવા માટે સ્વાઇપ કરો @@ -1768,11 +1799,18 @@ Signalને તમને મિત્રો સાથે કનેક્ટ થવા અને મેસેજ મોકલવામાં સહાયતા કરવા સંપર્કો અને મીડિયા પરવાનગીઓની જરૂર છે. તમારા સંપર્કો Signalની ખાનગી સંપર્ક શોધનો ઉપયોગ કરીને અપલોડ કરવામાં આવશે, જેનો અર્થ એમ કે તેઓ એન્ડ-ટુ-એન્ડ એન્ક્રિપ્ટ થયેલ છે અને Signal સર્વિસને ક્યારેય દેખાઈ શકશે નહીં. Signalને તમને મિત્રો સાથે કનેક્ટ થવામાં સહાયતા કરવા સંપર્કોની પરવાનગીઓની જરૂર છે. તમારા સંપર્કો Signalની ખાનગી સંપર્ક શોધનો ઉપયોગ કરીને અપલોડ કરવામાં આવશે, જેનો અર્થ એમ કે તેઓ એન્ડ-ટુ-એન્ડ એન્ક્રિપ્ટ થયેલ છે અને Signal સર્વિસને ક્યારેય દેખાઈ શકશે નહીં. તમે આ નંબરને રજીસ્ટર કરવા માટે ઘણા પ્રયત્નો કર્યા છે. પછીથી ફરી પ્રયત્ન કરો. + + આ નંબરને રજિસ્ટર કરવા માટે તમે ઘણા બધા પ્રયત્નો કરી લીધા છે. કૃપા કરીને %1$sમાં ફરી પ્રયાસ કરો. સેવાથી કનેક્ટ કરવામાં અસમર્થ. કૃપા કરીને નેટવર્ક કનેક્શન તપાસો અને ફરીથી પ્રયાસ કરો. બિન-પ્રમાણભૂત નંબર ફોર્મેટ તમે દાખલ કરેલ નંબર (%1$s) બિન-પ્રમાણભૂત ફોર્મેટ હોય તેમ લાગે છે.\n\n શું તમારો અર્થ %2$s હતો? Molly Android - ફોન નંબર ફોર્મેટ + કૉલની વિનંતી કરી + + SMSની વિનંતી કરી + + ચકાસણી કોડની વિનંતી કરી તમે હવે ડિબગ લૉગ સબમિટ કરવાથી %1$d પગથિયુ દૂર છો. તમે હવે ડિબગ લૉગ સબમિટ કરવાથી %1$d પગથિયા દૂર છો. @@ -1792,6 +1830,16 @@ કૉલ ચકાસણી કોડ કોડ ફરીથી મોકલો + + રજિસ્ટર કરવામાં મુશ્કેલી આવી રહી છે? + + • ખાતરી કરો કે તમારા ફોનમાં તમારો SMS અથવા કૉલ મેળવવા માટે સેલ્યુલર સિગ્નલ હોય છે\n • ખાતરી કરો કે તમે નંબર પર ફોન કૉલ મેળવી શકો છો\n • તપાસો કે તમે તમારો ફોન નંબર યોગ્ય રીતે દાખલ કર્યો છે. + + વધુ માહિતી માટે, કૃપા કરીને આ મુશ્કેલી નિવારણના પગલાં અનુસરો અથવા સપોર્ટનો સંપર્ક કરો + + આ મુશ્કેલી નિવારણ પગલાં + + સપોર્ટનો સંપર્ક કરો રજીસ્ટ્રેશન લૉક ચાલુ કરો @@ -1951,13 +1999,17 @@ તમે એક બૅજ રિડિમ કર્યો - તમારી સ્ટોરી પર %1$s એ પ્રતિક્રિયા આપી + %1$s એ તમારી સ્ટોરી પર પ્રતિક્રિયા આપી - તેમની સ્ટોરી પર %1$s એ પ્રતિક્રિયા આપી + %1$s એ તેમની સ્ટોરી પર પ્રતિક્રિયા આપી પેમેન્ટ મેસેજ શેડ્યૂલ કરો + + તમારી મેસેજ હિસ્ટ્રી મર્જ કરવામાં આવી છે + + %1$sએ %2$s નો નંબર છે Molly અપડેટ @@ -2030,7 +2082,7 @@ અસ્તિત્વમાં નથી તે સત્ર માટે MMS મેસેજ એન્ક્રિપ્ટ થયેલ - સૂચનાઓ મ્યૂટ કરો + નોટિફિકેશન મ્યૂટ કરો ઇમ્પોર્ટ ચાલુ છે @@ -2087,13 +2139,15 @@ અસુરક્ષિત SMS %1$s %2$s સંપર્ક - %1$s પ્રતિક્રિયા આપી: \"%2$s\". - તમારી વિડિયો પર %1$s પ્રતિક્રિયા આપી. - તમારી છબી પર %1$s પ્રતિક્રિયા આપી. + \"%2$s\" પર %1$s પ્રતિક્રિયા આપી. + તમારા વીડિયો પર %1$s પ્રતિક્રિયા આપી. + તમારા ફોટો પર %1$s પ્રતિક્રિયા આપી. તમારી GIF પર %1$s પ્રતિક્રિયા આપી. - તમારી ફાઇલ પર %1$s ની પ્રતિક્રિયા આપી. - તમારા ઓડિયો પર %1$sપર પ્રતિક્રિયા આપી. - તમારા વ્યુ-વન્સ મીડિયા પર %1$s એ પ્રતિક્રિયા આપી. + તમારી ફાઈલ પર %1$s પ્રતિક્રિયા આપી. + તમારા ઓડિયો પર %1$s પ્રતિક્રિયા આપી. + તમારા વ્યૂ-વન્સ મીડિયા પર %1$s પ્રતિક્રિયા આપી. + + તમારી ચુકવણી પર %1$s પ્રતિક્રિયા આપી. તમારા સ્ટીકર પર %1$s પ્રતિક્રિયા આપી. આ મેસેજ ડિલીટ કર્યો. @@ -2487,8 +2541,8 @@ ડિલિવરી મુદ્દો - %1$s તરફથી તમને મેસેજ, સ્ટીકર, પ્રતિક્રિયા અથવા વાંચવાની રસીદ આપી શકાઈ નથી. તેઓએ તેને સીધા તમને અથવા ગ્રુપમાં મોકલવાનો પ્રયત્ન કર્યો હશે. - એક મેસેજ, સ્ટીકર, પ્રતિક્રિયા અથવા વાંચવાની રસીદ %1$s તરફથી તમને પહોંચાડી શકાઈ નથી. + %1$s તરફથી તમને મેસેજ, સ્ટિકર, પ્રતિક્રિયા અથવા વાંચી લીધાનું નિશાન મોકલી શકાયું નથી. તેઓએ તેને સીધા તમને અથવા ગ્રૂપમાં મોકલવાનો પ્રયત્ન કર્યો હશે. + %1$s તરફથી તમને એક મેસેજ, સ્ટિકર, પ્રતિક્રિયા અથવા વાંચી લીધાનું નિશાન મોકલી શકાયું નથી. પ્રથમ નામ (જરૂરી) @@ -2555,7 +2609,7 @@ બાકી - આને મોકલવામાં આવેલ છે + આમને મોકલ્યું તરફથી મોકલવામાં આવેલ છે આને મેસેજ પહોંચાડ્યો દ્વારા વંચાયેલ @@ -2675,9 +2729,9 @@ એડ્રેસ બુકના ફોટા વાપરો જો ઉપલબ્ધ હોય તો તમારી એડ્રેસ બુકમાંથી સંપર્ક ફોટા ડિસ્પ્લે કરો - મ્યૂટ કરેલ ચેટને આર્કાઇવ રાખો + મ્યૂટ કરેલી ચેટને આર્કાઇવ રાખો - મ્યૂટ કરેલ ચેટ જેને આર્કાઇવ કરેલ છે તે નવો મેસેજ આવે ત્યારે પણ આર્કાઇવ રહેશે. + મ્યૂટ કરેલી ચેટ જેને આર્કાઇવ કરેલ છે તે નવો મેસેજ આવે ત્યારે પણ આર્કાઇવ જ રહેશે. લિંક પ્રિવ્યૂ જનરેટ કરો તમે મોકલેલા મેસેજ માટે વેબસાઇટમાંથી સીધા લિંક પ્રિવ્યૂ પ્રાપ્ત કરો. પાસફ્રેઝ બદલો @@ -3128,10 +3182,10 @@ - અનમ્યૂટ + અનમ્યૂટ કરો - સૂચનાઓ મ્યૂટ કરો + નોટિફિકેશન મ્યૂટ કરો ગ્રુપ સેટિંગ્સ @@ -3295,6 +3349,8 @@ તમારો પિન દાખલ કરો તમારા એકાઉન્ટ માટે તમે બનાવેલો PIN દાખલ કરો. આ તમારા SMS ચકાસણી કોડથી અલગ છે. + + તમે તમારા એકાઉન્ટ માટે બનાવેલ પિન દાખલ કરો. આલ્ફાન્યુમેરિક PIN દાખલ કરો સંખ્યાત્મક PIN દાખલ કરો ખોટો પિન. ફરીથી પ્રયત્ન કરો. @@ -3398,7 +3454,10 @@ તમારા બૅકઅપમાં એક બહુ મોટી ફાઇલ છે જેનું બૅકઅપ લઈ શકાતું નથી. કૃપા કરીને તેને ડિલીટ કરો અને નવું બૅકઅપ બનાવો. બેકઅપ મેનેજ કરવા માટે ટેપ કરો. ખોટો નંબર? + મને કૉલ કરો (%1$02d:%2$02d) + + કોડ ફરીથી મોકલો (%1$02d:%2$02d) Signal સપોર્ટનો સંપર્ક કરો Signal રજીસ્ટ્રેશન - Android માટે ચકાસણી કોડ ખોટો કોડ @@ -3406,6 +3465,18 @@ અજાણ્યું મારો ફોન નંબર જુઓ ફોન નંબર દ્વારા મને શોધો + + ફોન નંબર + + તમારો ફોન નંબર કોણ જોઈ શકે અને Molly પર તેના વડે કોણ તમારો સંપર્ક કરી શકે તે પસંદ કરો. + + મારો નંબર કોણ જોઈ શકે + + Molly પર કોઈ પણ તમારો ફોન નંબર જોશે નહીં + + મને નંબરથી કોણ શોધી શકે + + તમારો ફોન નંબર એ લોકો અને ગ્રૂપને દેખાશે જેમને તમે મેસેજ કરો છો. જે લોકોના ફોન સંપર્કોમાં તમારો નંબર હશે તેઓ પણ તેને Molly પર જોશે. બધા મારા સંપર્કો કોઈ નથી @@ -3562,8 +3633,8 @@ - અવરોધિત કરો - અનાવરોધિત કરો + બ્લૉક કરો + અનબ્લૉક કરો સંપર્કોમાં ઉમેરો સંપર્કો ખોલવા માટે કોઈ એપ્લિકેશન મળતી નથી. @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" બ્લૉક કરવામાં આવ્યો છે - \"%1$s\" ને બ્લૉક કરવામાં નિષ્ફળ - \"%1$s\" અનબ્લૉક કરવામાં આવ્યો છે + \"%1$s\"ને બ્લૉક કરવામાં આવ્યા છે. + \"%1$s\"ને બ્લૉક કરવાનું નિષ્ફળ રહ્યું + \"%1$s\"ને અનબ્લૉક કરવામાં આવ્યા છે. મેમ્બરની સમીક્ષા કરો @@ -3644,7 +3715,7 @@ તમારા સંપર્ક ગ્રુપમાંથી દૂર કરો સંપર્કને અપડેટ કરો - અવરોધિત કરો + બ્લૉક કરો ડિલીટ કરો તાજેતરમાં જ તેમનું પ્રોફાઇલ નામ %1$s માંથી %2$s માં બદલ્યું. @@ -3667,17 +3738,17 @@ નબળું વાઇ-ફાઇ. સેલ્યુલર પર સ્વિચ કર્યું. - તમારું એકાઉન્ટ કાઢી નાખવાથી: + તમારું એકાઉન્ટ ડિલીટ કરવાથી: તમારો ફોન નંબર દાખલ કરો એકાઉન્ટ ડિલીટ કરો - તમારી એકાઉન્ટ માહિતી અને પ્રોફાઇલ ફોટો ડિલીટ કરો - બધા મેસેજ ડિલીટ કરો + તમારી એકાઉન્ટ માહિતી અને પ્રોફાઇલ ફોટો ડિલીટ થશે + તમારા બધા મેસેજ ડિલીટ કરો તમારા ચુકવણી એકાઉન્ટમાંથી %1$s ડિલીટ કરો કોઈ દેશ કોડ ઉલ્લેખિત નથી કોઈ નંબર ઉલ્લેખિત નથી તમે દાખલ કરેલા ફોન નંબર તમારા એકાઉન્ટ સાથે મેળ ખાતા નથી. શું તમે ખરેખર તમારું એકાઉન્ટ ડિલીટ કરવા માગો છો? - આ તમારું Signalનું એકાઉન્ટ ડિલીટ થઈ જશે અને એપ્લિકેશનને રિસેટ કરશે. આ પ્રક્રિયા પૂર્ણ થયા બાદ એપ્લિકેશન બંધ થઈ જશે. + આનાથી તમારું Signal એકાઉન્ટ ડિલીટ થશે અને ઍપ્લિકેશન રિસેટ થઈ જશે. આ પ્રક્રિયા પૂર્ણ થયા બાદ ઍપ બંધ થઈ જશે. સ્થાનિક ડેટા ડિલીટ કરવામાં નિષ્ફળ. તમે તેને સિસ્ટમ એપ્લિકેશન સેટિંગ્સમાં જાતે દૂર કરી શકો છો. એપ્લિકેશન સેટિંગ્સ લોંચ કરો @@ -3784,12 +3855,12 @@ વોલેટને નિષ્ક્રિય કરો તમારું બેલેન્સ - એવી ભલામણ કરવામાં આવે છે કે તમે પેમેન્ટને નિષ્ક્રિય કરતા પહેલા તમારા ફંડને બીજા વૉલેટ એડ્રેસ પર સ્થાનાંતરિત કરો. જો તમે હવે તમારા ફંડને સ્થાનાંતરિત ન કરવાનું પસંદ કરો છો, તો જો તમે પેમેન્ટ્સને ફરીથી સક્રિય કરો છો તો તે Molly સાથે જોડાયેલા તમારા વૉલેટમાં રહેશે. + એવી ભલામણ કરવામાં આવે છે કે તમે પેમેન્ટને નિષ્ક્રિય કરતા પહેલા તમારા ફંડને બીજા વૉલેટ એડ્રેસ પર ટ્રાન્સફર કરો. જો તમે અત્યારે તમારા ફંડને ટ્રાન્સફર ન કરવાનું પસંદ કરો છો, તો જો તમે પેમેન્ટને ફરીથી સક્રિય કરો તો તે તમારા Molly સાથે જોડાયેલા વૉલેટમાં રહેશે. બાકીનું બેલેન્સ સ્થાનાંતરિત કરો સ્થાનાંતરિત કર્યા વિના નિષ્ક્રિય કરો નિષ્ક્રિય કરો સ્થાનાંતરિત કર્યા વિના નિષ્ક્રિય કરવું છે? - જો તમે પેમેન્ટને ફરીથી સક્રિય કરવાનું પસંદ કરો છો તો તમારું બેલેન્સ Molly સાથે જોડાયેલા તમારા વૉલેટમાં રહેશે. + જો તમે પેમેન્ટને ફરીથી સક્રિય કરવાનું પસંદ કરો છો તો તમારું બેલેન્સ તમારા Molly સાથે જોડાયેલા વૉલેટમાં રહેશે. વોલેટ નિષ્ક્રિય કરવામાં ભૂલ. @@ -4003,12 +4074,12 @@ પ્રોફાઇલ બનાવો - બ્લૉક કરેલ + બ્લૉક કર્યા %1$d સંપર્કો મેસેજ કરી રહ્યા છીએ અદૃશ્ય થઈ રહેલા મેસેજ એપ્લિકેશનની સુરક્ષા - તાજેતરની સૂચિમાં અને એપ્લિકેશનની અંદર સ્ક્રીનશોટ્સ ને અવરોધિત કરો + તાજેતરની સૂચિમાં અને ઍપમાંના સ્ક્રીનશોટને બ્લૉક કરો Signal મેસેજ અને કૉલ્સ, હંમેશા રિલે કૉલ્સ, અને સીલ કરેલ મોકલનાર નવી ચેટ માટે ડિફોલ્ટ ટાઈમર તમે શરૂ કરેલી બધી નવી ચેટ માટે ડિફોલ્ટ અદ્રશ્ય મેસેજ ટાઈમર સેટ કરો. @@ -4106,7 +4177,7 @@ અત્યારે નહીં - કસ્ટમાઇઝ પ્રતિક્રિયાઓ + પ્રતિક્રિયાઓ કસ્ટમાઇઝ કરો ઇમોજી બદલવા માટે ટેપ કરો ફરીથી સેટ કરો સેવ કરો @@ -4165,9 +4236,9 @@ કૉલ - મ્યુટ + મ્યૂટ કરો - મ્યુટ કરેલ + મ્યૂટ કર્યા શોધો અદૃશ્ય થઈ રહેલા મેસેજ @@ -4175,10 +4246,10 @@ સંપર્કની વિગતો સલામતી નંબર જુઓ - અવરોધિત કરો - ગ્રુપ બ્લૉક - અનાવરોધિત કરો - ગ્રૂપને અનબલોક કરો + બ્લૉક કરો + ગ્રૂપને બ્લૉક કરો + અનબ્લૉક કરો + ગ્રૂપને અનબ્લૉક કરો ગ્રુપમાં ઉમેરો બધા જુઓ સભ્યો ઉમેરો @@ -4186,9 +4257,9 @@ વિનંતીઓ & આમંત્રણ ગ્રુપ લિંક સંપર્કોમાં ઉમેરો - અનમ્યૂટ + અનમ્યૂટ કરો %1$s સુધી વાતચીત મ્યૂટ કરી - હમેશા માટે વાતચીત મ્યૂટ કરી + હંમેશા માટે વાતચીત મ્યૂટ કરી ક્લિપબોર્ડ પર ફોન નંબરની નકલ કરી. ફોન નંબર Signalને સમર્થન આપીને તમારી પ્રોફાઇલ માટે બૅજ મેળવો. વધુ જાણવા માટે બૅજ પર ટૅપ કરો. @@ -4204,8 +4275,8 @@ કોણ મેસેજ મોકલી શકે છે? - સૂચનાઓ મ્યૂટ કરો - મ્યૂટ નહીં + નોટિફિકેશન મ્યૂટ કરો + મ્યૂટ કર્યું નથી ઉલ્લેખો હંમેશાં સૂચિત કરો મને સૂચિત કરશો નહીં @@ -4234,7 +4305,7 @@ દૂર કરો - અવરોધિત કરો + બ્લૉક કરો %1$sને દૂર કરવા છે? @@ -4242,7 +4313,7 @@ %1$sને દૂર કરવામાં આવ્યા છે - %1$s ને બ્લૉક કરવામાં આવ્યા છે + %1$sને બ્લૉક કરવામાં આવ્યા છે %1$sને દૂર ન કરી શક્યા @@ -4465,7 +4536,7 @@ તમારું પુનરાવર્તિત માસિક યોગદાન રદ થઈ ગયું હતું કારણ કે અમે તમારી ચુકવણીની પ્રક્રિયા ન કરી શક્યા. તમારું બૅજ હવે તમારી પ્રોફાઇલ પર દેખાશે નહીં. તમારું પુનરાવર્તિત માસિક દાન રદ થઈ ગયું હતું. %1$s તમારું %2$s બૅજ હવે તમારી પ્રોફાઇલ પર દેખાશે નહીં. - તમે Signalનો ઉપયોગ કરવાનું ચાલુ રાખી શકો છો પરંતુ એપ્લિકેશનને સહયોગ આપવા અને તમારા બૅજને ફરીથી સક્રિય કરવા, હમણાં જ રિન્યૂ કરો. + તમે Signalનો ઉપયોગ કરવાનું ચાલુ રાખી શકો છો પરંતુ ઍપને સહયોગ આપવા અને તમારા બૅજને ફરીથી સક્રિય કરવા, હમણાં જ રિન્યૂ કરો. સબ્સ્ક્રિપ્શન રિન્યૂ કરો Google Pay પર જાઓ @@ -4508,7 +4579,7 @@ નેટવર્ક ભૂલને કારણે તમારું દાન મોકલી ન શકાયું. તમારું કનેક્શન તપાસો અને ફરીથી પ્રયત્ન કરો. - %1$sને દાન + %1$s વતી દાન %1$sએ તમારા વતી Signalને દાન આપ્યું @@ -5087,7 +5158,7 @@ દાનની પુષ્ટિ કરો - આમને moklo + આમને મોકલો પ્રાપ્તકર્તાને 1-1 કરીને મેસેજમાં દાનની જાણ કરવામાં આવશે. નીચે તમારો પોતાનો મેસેજ ઉમેરો. @@ -5601,5 +5672,15 @@ યુઝરનેમ ડિલીટ કરો + + + + + મિ + + સેટ કરો + + સ્ક્રીન લૉક લાગુ થાય તે પહેલાંનો ન્યૂનતમ સમય 1 મિનિટનો છે. + diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 2e391e8424..57905ccd46 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -14,6 +14,7 @@ + हाँ नहीं @@ -96,13 +97,13 @@ संदेशों का पता लगा रहे हैं… - ब्लॉक किए गए उपयोगकर्ता - ब्लॉक किए गए उपयोगकर्ता से जुड़े + ब्लॉक किए गए यूज़र + ब्लॉक किए गए यूज़र से जुड़े ब्लॉक संपर्क अब आपको मेसेज भेजने या आपको कॉल करने में सक्षम नहीं होंगे। - कोई ब्लॉक किए गए उपयोगकर्ता नहीं हैं - इस उपयोगकर्ता को ब्लॉक करें? + कोई ब्लॉक किए गए यूज़र नहीं हैं + इस यूज़र को ब्लॉक करें? \"%1$s\" आपको कॉल नहीं कर पाएंगे या आपको संदेश नहीं भेज पाएंगे । - ब्लॉक + ब्लॉक करें @@ -139,7 +140,7 @@ %1$s को ब्लॉक करें और छोड़ दें? - %1$sको ब्लॉक करें ? + %1$s को ब्लॉक करें? अब आपको इस समूह से संदेश या अपडेट प्राप्त नहीं होंगे, और अन्य सदस्य आपको इस समूह में फिर से नहीं जोड़ पाएंगे । समूह के सदस्य फिर से आपको इस समूह में नहीं जोड़ पाएंगे। ग्रूप के सदस्य आपको इस ग्रूप में फ़िर शामिल कर पाएँगे। @@ -153,8 +154,8 @@ Signal से मिलनेवाले अपडेट और खबरों को ब्लॉक करें। Signal से अपडेट और खबरों को पाना जारी रखें। - %1$sको अनब्लॉक करें ? - ब्लॉक + %1$s को अनब्लॉक करें? + ब्लॉक करें ब्लॉक करें और छोड़ें स्पैम रिपोर्ट करें और ब्लॉक करें @@ -455,7 +456,7 @@ रद्द करें - ब्लॉक + ब्लॉक किया गया फ़िल्टर हटाएँ @@ -480,28 +481,28 @@ %1$d बातचीत इनबॉक्स में पहुंचाई गई - पढ़ें - पढ़ें + पढ़ा हुआ + पढ़े हुए - नहीं पढ़ा हुआ - नहीं पढ़ा हुआ + अपठित + अपठित - पिन - पिन + पिन करें + पिन करें अनपिन करें अनपिन करें - म्यूट - म्यूट + म्युट करें + म्यूट करें - अनम्यूट - अनम्यूट + अनम्यूट करें + अनम्यूट करें चुनिए @@ -542,6 +543,15 @@ +%1$d + + अपने डिवाइस को फिर से लिंक करें + + जब आपका डिवाइस अपंजीकृत था तब आपके द्वारा जोड़े गए डिवाइस अनलिंक हो गए थे। किसी भी डिवाइस को फिर से लिंक करने के लिए सेटिंग्स में जाएं। + + सेटिंग्स खोलें + + बाद में + सदस्य चुनें @@ -935,7 +945,7 @@ मेंछन किए जाने पर मुझे सूचित करें - म्यूट की हुई चैट में आपको मेंछन किए जाने पर सूचनाएँ प्राप्त करनी हैं? + म्यूट की हुई चैट में आपको मेंछन किए जाने पर नोटिफ़िकेशन प्राप्त करनी हैं? हमेशा मुझे सूचित करें मुझे सूचित ना करें @@ -953,6 +963,16 @@ यूज़रनेम बनाया गया यूज़रनेम कॉपी किया गया + + यूज़रनेम डिलीट नहीं किया जा सका। बाद में पुन: प्रयास करें। + + यूज़रनेम डिलीट कर दिया गया है + + + + आपके यूज़रनेम में कुछ गलत है, यह अब आपके अकाउंट के लिए असाइन नहीं है। आप इसे फिर से सेट करने की कोशिश कर सकते हैं या एक नया चुन सकते हैं। + + अभी ठीक करें @@ -1156,8 +1176,8 @@ नया समूह मित्रों को आमंत्रित करें SMS का उपयोग करें - दिखावट - तस्वीर लगाएं + चैट का रंग + एक प्रोफ़ाइल फ़ोटो जोड़ें जवाब @@ -1468,7 +1488,7 @@ स्वीकृत आगे डिलीट करें - ब्लॉक + ब्लॉक करें अनब्लॉक करें %1$s को आपको मेसेज करने देना है और उनके साथ अपना नाम और तस्वीर शेयर करनी है? उन्हें पता नहीं चलेगा कि आपने उनका मेसेज देख लिया है जब तक आप स्वीकार नहीं करते। @@ -1584,6 +1604,17 @@ नया पिन बनाएँ + + SMS कोड भेजें + + Signal पंजीकरण – एंड्रॉयड के लिए पिन को फिर से पंजीकृत करने में सहायता चाहिए + + आपका पिन आपके द्वारा बनाया गया %1$d+अंकों वाला कोड है, जो संख्यात्मक या अल्फ़ान्यूमेरिक हो सकता है।\n\nयदि आपको अपना पिन याद नहीं है, तो आप नया बना सकते हैं। + + यदि आपको अपना पिन याद नहीं है, तो आप नया बना सकते हैं। + + आप पिन का अनुमान लगाने की अधिकतम सीमा को पार कर गए हैं, लेकिन आप अभी भी एक नया पिन बनाकर अपने Signal अकाउंट तक पहुंच सकते हैं। + चेतावनी यदि आप पिन को डिसेबल कर देते हैं, तो आप Signal के साथ दोबारा रजिस्टर करने पर अपना सारा डेटा खो बैठेंगे यदि आप मैन्युअल तरीके से बैक अप और रीस्टोर नहीं करते हैं। आप पिन के डिसेबल होते हुए रजिस्ट्रेशन लॉक को चालू नहीं कर सकते। @@ -1616,7 +1647,7 @@ मेरी स्टोरी - ब्लॉक + ब्लॉक करें अनब्लॉक करें @@ -1711,9 +1742,9 @@ कैमरा - अनम्यूट + अनम्यूट करें - म्युट + म्युट करें रिंग करें @@ -1726,7 +1757,7 @@ - %1$s ब्लॉक है + %1$s ब्लॉक किया गया है औेर जानकारी आपको उनके ऑडियो या वीडियो प्राप्त नहीं होंगे और उन्हें आपके नहीं। %1$s से ऑडियो और वीडियो नहीं प्राप्त की जा सकती @@ -1768,11 +1799,18 @@ दोस्तों से जुड़ने और मेसेज भेजने में आपकी मदद करने के लिए Signal को कॉन्टैक्ट और मीडिया के अनुमति की ज़रुरत है. आपके कॉन्टैक्ट Signal की निजी कॉन्टैक्ट डिस्कवरी का इस्तेमाल करके अपलोड किए जाते हैं, जिसका अर्थ है कि वे एंड-टू-एंड एन्क्रिप्टेड हैं और Signal सेवा को कभी भी नहीं दिखते हैं. दोस्तों से जुड़ने में आपकी मदद के लिए Signal को कॉन्टैक्ट की अनुमति की ज़रुरत है. आपके कॉन्टैक्ट Signal की निजी कॉन्टैक्ट डिस्कवरी का इस्तेमाल करके अपलोड किए जाते हैं, जिसका अर्थ है कि वे एंड-टू-एंड एन्क्रिप्टेड हैं और Signal सेवा को कभी भी नहीं दिखते हैं. इस नंबर को रजिस्टर करने के लिए आपके द्वारा बहुत बार प्रयास किया जा चुका है। कृपया कुछ देर बाद फिर से कोशिश करें। + + आपने इस नंबर को पंजीकृत करने के बहुत सारे प्रयास किए। कृपया %1$s में पुन: प्रयास करें। सेवा से कनेक्ट करने में असमर्थ। कृपया नेटवर्क कनेक्शन की जांच करें और पुनः प्रयास करें। नॉन-स्टैंडर्ड नंबर फ़ॉर्मैट आपके ज़रिए दर्ज किया गया नंबर (%1$s) एक नॉन-स्टैंडर्ड फ़ॉर्मैट लगता है.\n\nक्या आपका मतलब %2$s था? Molly एंड्रॉइड - फ़ोन नंबर का फ़ॉर्मेट + कॉल की मांग की गई + + SMS का अनुरोध किया गया + + सत्यापन कोड का अनुरोध किया गया debug log भेजने के लिये %1$d चरण और बाचे हैं डीबग लॉग भेजने के लिये %1$d चरण और बाचे हैं @@ -1792,6 +1830,16 @@ कोल सत्यापन कोड कोड फ़िर से भेजें + + पंजीकरण में परेशानी हो रही है? + + • सुनिश्चित करें कि आपके फोन में आपका SMS या कॉल प्राप्त करने के लिए सेल्यूलर सिग्नल है\n • पुष्टि करें कि आप नंबर पर फोन कॉल प्राप्त कर सकते हैं\n • जांचें कि आपने अपना फोन नंबर सही दर्ज किया है। + + अधिक जानकारी के लिए, कृपया इन समस्या निवारण चरणों का पालन करें या सपोर्ट से संपर्क करें + + ये समस्या निवारण चरण + + सपोर्ट से संपर्क करें रजिस्ट्रेशन लॉक चालू करना है? @@ -1958,6 +2006,10 @@ भुगतान शेड्यूल किए गए मैसेज + + आपका संदेश इतिहास मर्ज कर दिया गया है + + %1$s %2$s का है Molly अपडेट @@ -2088,12 +2140,14 @@ %1$s%2$s संपर्क प्रतिक्रिया की गई %1$s से: \"%2$s\". - आपके वीडियो पर %1$s ने प्रतिक्रिया व्यक्त की गई - आपकी तस्वीर पर %1$s ने प्रतिक्रिया व्यक्त की. - आपके GIF पर %1$s ने प्रतिक्रिया दी. - आपकी फ़ाइल पर %1$sने प्रतिक्रिया व्यक्त की| - आपके ऑडियो के लिए %1$sप्रतिक्रिया व्यक्त की। - आपके एक-बार-देखने-योग्य वीडियो पर %1$s प्रतिक्रिया की। + आपके वीडियो पर %1$s ने प्रतिक्रिया दी। + आपकी तस्वीर पर %1$s ने प्रतिक्रिया दी। + आपके GIF पर %1$s ने प्रतिक्रिया दी। + आपकी फ़ाइल पर %1$sने प्रतिक्रिया व्यक्त दी। + आपके ऑडियो के लिए %1$sप्रतिक्रिया व्यक्त दी। + आपके एक-बार-देखने-योग्य वीडियो पर %1$s प्रतिक्रिया दी। + + आपके भुगतान पर %1$s ने प्रतिक्रिया दी। आपके स्टिकर पर %1$sको पुन: सक्रिय किया। यह मेसेज डिलीट कर दिया गया है। @@ -3056,7 +3110,7 @@ नया मेसेज - उपयोगकर्ता को ब्लॉक करें + यूज़र को ब्लॉक करें समूह में शामिल करें @@ -3128,7 +3182,7 @@ - अनम्यूट + अनम्यूट करें नोटिफिकेशन म्यूट करें @@ -3295,6 +3349,8 @@ अपना पिन डालें अपने ख़ाते के लिये बनाया गया पिन डालें। ये पिन आपके SMS सत्यापण कोड से अलग है। + + आपके द्वारा अपने अकाउंट के लिए बनाया गया पिन दर्ज करें। अल्फ़ान्यूमेरिक पिन दर्ज करें संख्यात्मक पिन दर्ज करें गलत पिन। पुनः प्रयास करें। @@ -3398,7 +3454,10 @@ आपके बैकअप में एक बहुत बड़ी फ़ाइल है जिसका बैकअप नहीं किया जा सकता। कृपया उसे डिलीट करके एक नया बैकअप बनाएँ। बैकअप प्रबंधन के लिए टैप करें गलत नंबर? + मुझे कॉल करें (%1$02d:%2$02d) + + पुन: कोड भेजे (%1$02d:%2$02d) Signal समर्थन से संपर्क करें Signal पंजीकरण - Android के लिये वेरीफिकेशन कोड गलत कोड @@ -3406,6 +3465,18 @@ अनजान मेरा फ़ोन नंबर देखना मुझे फ़ोन नंबर के ज़रिये ढूंढना + + फोन नंबर + + चुनें कि कौन आपका फोन नंबर देख सकता है और कौन इसके साथ Molly पर आपसे संपर्क कर सकता है। + + मेरा नंबर कौन देख सकता है + + कोई भी Molly पर आपका फोन नंबर नहीं देखेगा + + मुझे नंबर से कौन ढूंढ सकता है + + आपका फ़ोन नंबर उन लोगों और ग्रुप्स को दिखेगा जिन्हें आप मेसेज भेजेंगे। वे लोग जिनके फोन संपर्कों में आपका नंबर है, वे भी Molly पर इसे देख पाएँगे। सभी मेरे संपर्क कोई नहीं @@ -3562,7 +3633,7 @@ - ब्लॉक + ब्लॉक करें अनब्लॉक करें संपर्क के खाते में जोड़ दे @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" को ब्लॉक किया गया है + \"%1$s\" को ब्लॉक किया गया है। \"%1$s\" को अनब्लॉक करने में असफल - \"%1$s\" को अनब्लॉक किया गया है + \"%1$s\" को अनब्लॉक किया गया है। सदस्यों की समीक्षा करें @@ -3644,7 +3715,7 @@ आपकी संपर्क जानकारी ग्रूप से हटाएँ संपर्क जानकारी अपडेट करें - ब्लॉक + ब्लॉक करें डिलीट करें हाल ही में अपना प्रोफ़ाइल नाम %1$s से बदलकर %2$s रखा @@ -4003,7 +4074,7 @@ प्रोफ़ाइल बनाएं  - ब्लॉक + ब्लॉक किया गया %1$d संपर्क संदेश संवाद गायब होने वाले मेसेज @@ -4165,7 +4236,7 @@ कॉल - म्युट + म्युट करें म्यूट किया हुआ @@ -4175,7 +4246,7 @@ संपर्क विवरण सुरक्षा नंबर देखें - ब्लॉक + ब्लॉक करें समूह को ब्लाॅक करें अनब्लॉक करें समूह को अनब्लॉक करें @@ -4186,7 +4257,7 @@ अनुरोध और आमंत्रण समूह लिंक संपर्क के रूप में जोड़ें - अनम्यूट + अनम्यूट करें बातचीत %1$s तक के लिए म्यूट की गई बातचीत हमेशा के लिए म्यूट की गई फ़ोन नंबर को क्लिपबोर्ड पर कॉपी किया गया। @@ -4234,7 +4305,7 @@ हटा दें - ब्लॉक + ब्लॉक करें %1$s को हटाना है? @@ -4508,7 +4579,7 @@ आपका दान किसी नेटवर्क दोष के कारण नहीं भेजा जा सका। अपना कनेक्शन जाँचें और फिर से प्रयास करें। - %1$s को दान + %1$s की ओर से दान %1$s ने आपकी ओर से Signal को दान दिया @@ -5057,7 +5128,7 @@ - आपने %1$s की स्टोरी पर प्रतिक्रिया दी। + आपने %1$s की स्टोरी पर प्रतिक्रिया दी आपकी स्टोरी पर प्रतिक्रिया दी @@ -5601,5 +5672,15 @@ यूज़र नाम डिलीट करें + + + घंटे + + मिनट + + सेट + + स्क्रीन लॉक लागू होने से पहले न्यूनतम समय 1 मिनट है। + diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 4a50c01e76..d983823739 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -14,6 +14,7 @@ + Da Ne @@ -97,10 +98,10 @@ Blokirani korisnici - Dodaj korisnika za blokiranje - Blokirani korisnici vas neće moći nazvati niti vam slati poruke. + Dodaj korisnika na listu blokiranih + Blokirani korisnici neće vas moći nazvati niti vam slati poruke. Nema blokiranih korisnika - Blokiraj korisnika? + Blokirati korisnika? \"%1$s\" vas neće moći nazvati niti vam slati poruke. Blokiraj @@ -138,8 +139,8 @@ Nastavi - Blokiraj i napusti %1$s? - Blokiraj %1$s? + Blokirati i napustiti %1$s? + Blokirati %1$s? Više nećete primati poruke ili ažuriranja iz ove grupe, a članovi grupe vas neće moći naknadno dodati u grupu. Članovi grupe vas neće moći ponovno dodati u ovu grupu. Članovi grupe će vas moći ponovno dodati u ovu grupu. @@ -147,16 +148,16 @@ Moći će te se razmjenjivati poruke i pozive, a vaše ime i fotografija podijelit će se s njima. Moći ćete razmjenjivati poruke. - Blokirani korisnici vas neće moći nazvati niti vam slati poruke. - Blokirane osobe vam neće moći slati poruke. + Blokirani korisnici neće vas moći nazvati niti vam slati poruke. + Blokirani korisnici neće vam moći slati poruke. Blokiraj primanje ažuriranja i vijesti o Signalu. Ponovno primaj ažuriranja i vijesti o Signalu. - Odblokiraj %1$s? + Odblokirati %1$s? Blokiraj Blokiraj i napusti - Prijavi kao neželjeno i blokiraj + Prijavi neželjenu poruku i blokiraj Danas @@ -467,7 +468,7 @@ Poništi - Blokirani + Korisnik je blokiran Ukloni filtar @@ -530,10 +531,10 @@ Utišaj - Uključi zvuk - Uključi zvuk - Uključi zvuk - Uključi zvuk + Uključi obavijesti + Uključi obavijesti + Uključi obavijesti + Uključi obavijesti Odaberi @@ -582,6 +583,15 @@ +%1$d + + Ponovno povežite svoje uređaje + + Veza s vašim uređajima prekinuta je kada ste uklonili registraciju. Idite na Postavke za ponovno povezivanje svih uređaja. + + Otvori postavke + + Možda kasnije + Izaberite članove @@ -1011,7 +1021,7 @@ Obavijesti me samo za Spominjanja - Primi obavijesti kad vas se spominje u utišanim razgovorima? + Želite li primati obavijesti kad vas se spominje u utišanim razgovorima? Uvijek me obavijesti Ne obavještavaj me @@ -1029,6 +1039,16 @@ Korisničko ime je kreirano Korisničko ime je kopirano + + Brisanje korisničkog imena nije uspjelo. Pokušajte ponovno kasnije. + + Korisničko ime je izbrisano + + + + Došlo je do pogreške s vašim korisničkim imenom – nije više povezano s vašim računom. Možete ga ponovno postaviti ili odabrati novo. + + Ispravi odmah @@ -1252,8 +1272,8 @@ Nova grupa Pozovi prijatelje Koristi SMS - Izgled - Dodajte fotografiju + Boja razgovora + Dodajte sliku profila Odgovori @@ -1588,10 +1608,10 @@ Odblokiraj Želite li dopustiti %1$s da vam pošalje poruku i da dijelite svoje ime i fotografiju s njima? Neće znati da ste vidjeli njihovu poruku dok ne prihvatite. - Želite li dopustiti %1$s da vam pošalje poruku i da dijelite svoje ime i fotografiju s njima? Nećete primati poruke dok ne odblokirate. + Želite li dopustiti da vam %1$s šalje poruke i vidi vaše ime i fotografiju? Nećete primati poruke od tog korisnika dok ga ne odblokirate. - Želite li omogućiti da vam %1$s šalje poruke? Nećete primati poruke od tog kontakta dok ga ne odblokirate. - Želite li primati ažuriranja i novosti od %1$s? Nećete primati ažuriranja dok ih ne odblokirate. + Želite li dopustiti da vam %1$s šalje poruke? Nećete primati poruke od tog korisnika dok ga ne odblokirate. + Želite li primati ažuriranja i novosti od korisnika %1$s? Nećete primati ažuriranja dok ga ne odblokirate. Želite li nastaviti razgovor s ovom grupom i podijeliti svoje ime i fotografiju s članovima? Nadogradite ovu grupu da biste aktivirali nove značajke poput @spominjanja i administratora. Članovi koji nisu podijelili svoje ime ili fotografiju u ovoj grupi bit će pozvani da se pridruže. Ova Naslijeđena grupa ne može se više koristiti jer je prevelika. Maksimalna veličina grupe je %1$d. @@ -1599,7 +1619,7 @@ Želite li se pridružiti ovoj grupi i podijeliti svoje ime i fotografiju sa članovima? Neće znati da ste vidjeli njihove poruke dok ne prihvatite. Želite li se pridružiti ovoj grupi i podijeliti svoje ime i fotografiju s njezinim članovima? Nećete vidjeti njihove poruke dok ne prihvatite. Pridružiti se ovoj grupi? Neće znati da ste vidjeli njihove poruke dok ne prihvatite. - Želite li odblokirati ovu grupu i podijeliti svoje ime i fotografiju sa članovima? Nećete primati poruke dok ih ne odblokirate. + Želite li odblokirati ovu grupu i podijeliti svoje ime i fotografiju sa članovima? Nećete primati poruke dok ju ne odblokirate. Pregledaj Član/ica %1$s @@ -1712,9 +1732,20 @@ Stvori novi PIN + + Pošalji SMS kôd + + Registracija Signala – Trebate pomoć s postavljanjem PIN-a za Android? + + Vaš PIN je numerički ili alfanumerički kôd od %1$d+ znamenki koji ste sami postavili.\n\nU slučaju da ste zaboravili svoj PIN, možete stvoriti novi. + + U slučaju da ste zaboravili svoj PIN, možete stvoriti novi. + + Ponestalo vam je pokušaja za unos PIN-a, no i dalje možete pristupiti svom Signal računu stvaranjem novog PIN-a. + Upozorenje - Ako onemogućite PIN, izgubiti će te sve podatke prilikom ponovne registracije Signala osim ako ručno ne napravite sigurnosnu kopiju i vratite podatke. Ne možete uključiti Zaključavanje registracije dok je PIN onemogućen. + Ako onemogućite PIN, izgubit ćete sve svoje podatke kada se ponovno registrirate na Signal, osim ako ručno ne napravite sigurnosnu kopiju i vratite ih. Ne možete uključiti Zaključavanje registracije dok je PIN onemogućen. Onemogući PIN @@ -1849,7 +1880,7 @@ Kamera - Uključi zvuk + Uključi obavijesti Utišaj @@ -1866,12 +1897,12 @@ - %1$s je blokiran/a + %1$s je blokiran(a) Više informacija Nećete primiti njihov audio ili video, a ni oni neće vaš. Ne možete primiti audio i video od %1$s Ne možete primiti audio i video od %1$s - To je možda zato što nisu potvrdili promjenu vašeg sigurnosnog broja, ili postoji problem s njihovim uređajem ili su vas blokirali. + Mogući razlog tome jest to što korisnik nije potvrdio vaš ažurirani sigurnosni broj, ima problem s uređajem ili vas je blokirao. Prijeđite prstom za pregled dijeljenja zaslona @@ -1908,11 +1939,18 @@ Signal treba pristup vašim kontaktima i medijima za povezivanje s prijateljima i razmjenu poruka. Vaši se kontakti učitavaju korištenjem Signalovog otkrivanja privatnih kontakata, što znači da su sveobuhvatno šifrirani i nikada nisu vidljivi usluzi Signal. Signal treba pristup vašim kontaktima za povezivanje s prijateljima. Vaši se kontakti učitavaju korištenjem Signalovog otkrivanja privatnih kontakata, što znači da su sveobuhvatno šifrirani i nikada nisu vidljivi usluzi Signal. Previše neuspjelih pokušaja registracije ovog broja telefona. Pokušajte ponovo kasnije. + + Previše neuspjelih pokušaja registracije ovog broja telefona. Pokušajte ponovno za %1$s. Povezivanje s uslugom nije moguće. Provjerite mrežnu vezu i pokušajte ponovo. Nestandardni format broja Čini se da je broj koji ste unijeli (%1$s) nestandardnog formata.\n\nJeste li mislili %2$s? Molly Android – format broja telefona + Zatražen poziv + + Zatražili ste SMS + + Zatražili ste potvrdni kôd Još %1$d korak do slanja zapisnika o pogreški. Još %1$d koraka do slanja zapisnika o pogreški. @@ -1934,6 +1972,16 @@ Nazovi Potvrdni kôd Ponovno pošalji kôd + + Imate poteškoća s registriranjem? + + • Provjerite ima li vaš telefon signala za primanje SMS-a ili poziva\n • Potvrdite da možete primati pozive na ovaj broj\n • Provjerite jeste li ispravno unijeli broj telefona. + + Za više informacija, slijedite ove korake za rješavanje problema ili kontaktirajte podršku + + ove korake za rješavanje problema + + Kontaktiraj podršku Omogući Zaključavanje registracije? @@ -2095,11 +2143,15 @@ Reagira s %1$s na vašu priču - Reagira s %1$s na njihovu priču + Reagirali ste s %1$s na priču Plaćanje Zakazana poruka + + Vaša povijest poruka sada je spojena + + Broj %1$s pripada korisniku %2$s Molly ažuriranje @@ -2236,8 +2288,10 @@ Reagira s %1$s na vašu sliku. Reagira s %1$s na vaš GIF. Reagira s %1$s na vašu datoteku. - Reagira s %1$s na vaš zvuk. + Reagira s %1$s na vašu glasovnu poruku. Reagira s %1$s na vaš jednom vidljiv medijski zapis. + + Reagira s %1$s na vašu uplatu. Reagira s %1$s na vašu naljepnicu. Ova poruka je izbrisana. @@ -2653,8 +2707,8 @@ Problem s isporukom - Poruka, naljepnica, reakcija ili potvrda o čitanju nisu vam mogli biti dostavljeni od %1$s. Možda su pokušali poslati vama izravno ili u grupi. - Poruka, naljepnica, reakcija ili potvrda o čitanju nisu vam mogli biti dostavljeni od %1$s. + Isporuka poruke, naljepnice, reakcije ili potvrde o čitanju od strane korisnika %1$s nije uspjela. Moguće je da ju je pokušao/la poslati vama izravno ili u grupi. + Isporuka poruke, naljepnice, reakcije ili potvrde o čitanju od strane korisnika %1$s nije uspjela. Ime (obavezno) @@ -2721,7 +2775,7 @@ U tijeku - Poslano za + Poslano Poslano od Isporučeno za Pročitao/la @@ -3139,7 +3193,7 @@ Pošalji uplatu Zaprimljena uplata Plaćanje je dovršeno %1$s - Blokiraj broj + Broj bloka Prijenos @@ -3298,7 +3352,7 @@ - Uključi zvuk + Uključi obavijesti Utišaj obavijesti @@ -3471,6 +3525,8 @@ Unesite vaš PIN Unesite PIN koji ste stvorili za svoj račun. To se razlikuje od vašeg SMS kôda za provjeru. + + Unesite PIN koji ste stvorili za svoj račun. Unesite alfanumerički PIN Unesite numerički PIN Nevažeći PIN. Pokušajte ponovno. @@ -3584,7 +3640,10 @@ Vaša sigurnosna kopija sadrži vrlo veliku datoteku koja se ne može sigurnosno kopirati. Izbrišite je i napravite novu sigurnosnu kopiju. Pritisnite za upravljanje sigurnosnim kopijama. Pogrešan broj? + Nazovi me (%1$02d:%2$02d) + + Ponovno pošalji kôd (%1$02d:%2$02d) Kontaktirajte Signalovu podršku Registracija Signala - Potvrdni kôd za Android Pogrešan kôd @@ -3592,6 +3651,18 @@ Nepoznato Prikaži moj broj telefona Pronađi me prema broju telefona + + Broj telefona + + Odaberite tko može vidjeti vaš broj telefona i tko vas može kontaktirati putem njega na Mollyu. + + Tko može vidjeti moj broj + + Nitko neće vidjeti vaš broj telefona na Mollyu + + Tko me može pronaći po broju telefona + + Vaš broj telefona bit će vidljiv osobama i grupama kojima pošaljete poruku. Osobe koje imaju vaš broj u kontaktima telefona također će ga vidjeti na Mollyu. Svi Moji kontakti Nitko @@ -3801,9 +3872,9 @@ %1$s/%2$s - \"%1$s\" je blokiran/a. - Blokiranje \"%1$s\" nije uspjelo. - \"%1$s\" je odblokiran/a. + Korisnik \"%1$s\" je blokiran. + Blokiranje korisnika \"%1$s\" nije uspjelo + Korisnik \"%1$s\" je odblokiran. Pregled članova @@ -3857,17 +3928,17 @@ Slab Wi-Fi signal. Prebačeni ste na mobilnu mrežu. - Brisanje vašeg računa će: + Brisanjem vašeg računa, izbrisat ćete: Unesite vaš broj telefona - Izbriši korisnički račun - Izbrišite podatke o računu i fotografiju profila - Izbriši sve poruke + Korisnički račun + Podatke o računu i sliku profila + Izbriši sve vaše poruke Izbriši %1$s u vašem računu za plaćanja Nije naveden kôd države Nije naveden broj Broj telefona koji ste unijeli ne podudara se s vašim računima. Jeste li sigurni da želite izbrisati svoj račun? - Ovo će izbrisati vaš Signal račun i resetirati aplikaciju. Aplikacija će se zatvoriti nakon završetka postupka. + Ovime ćete izbrisati svoj Signal račun i resetirati aplikaciju. Nakon završetka postupka aplikacija će se zatvoriti. Brisanje lokalnih podataka nije uspjelo. Možete ih ručno izbrisati u postavkama aplikacije. Otvori postavke aplikacije @@ -3976,12 +4047,12 @@ Onemogući novčanik Vaše stanje - Prije onemogućavanja plaćanja, preporučuje se da sredstva prebacite na drugu adresu novčanika. Ako odlučite ne prenijeti svoja sredstva sada, ona će ostati u vašem novčaniku povezana sa Mollyom dok ponovno ne aktivirate plaćanja. + Prije onemogućavanja plaćanja, preporučujemo vam da sredstva prebacite na drugu adresu novčanika. Ako odlučite ne prenijeti svoja sredstva sada, ona će ostati u vašem novčaniku povezana sa Mollyom dok ponovno ne aktivirate plaćanja. Prenesi preostalo stanje Onemogući bez prijenosa Onemogući Onemogućiti bez prijenosa? - Vaše će stanje ostati u novčaniku povezanom sa Mollyom ako odlučite ponovno aktivirati plaćanja. + Vaše će stanje ostati u novčaniku povezanim sa Mollyom ako odlučite ponovno aktivirati plaćanja. Pogreška prilikom onemogućavanja novčanika. @@ -4202,7 +4273,7 @@ Razmjena poruka Poruke koje nestaju Sigurnost aplikacije - Blokira snimke zaslona na popisu nedavnih i unutar aplikacije + Blokira mogućnost snimke zaslona u nedavnim razgovorima i unutar aplikacije Signal poruke i pozivi, uvijek preusmjeravanje poziva i zapečaćeni pošiljatelj Zadani vremenski period za nove poruke Postavite zadani vremenski period za poruke koje nestaju za sve nove razgovore koje započnete. @@ -4384,8 +4455,8 @@ Zahtjevi i pozivnice Poveznica grupe Dodaj kao kontakt - Uključi zvuk - Razgovor je utišan do%1$s + Uključi obavijesti + Razgovor je utišan do %1$s Razgovor je utišan zauvijek Telefonski broj je kopiran u međuspremnik. Broj telefona @@ -4536,7 +4607,7 @@ Dodaj u priču Dodaj poruku Dodajte odgovor - Poslati + Pošalji Jednom vidljiva poruka Jedna ili više stavki bile su prevelike Jedna ili više stavki bile su nevažeće @@ -4659,7 +4730,7 @@ Mjesečna donacija je otkazana Značka podrške je istekla i ne prikazuje se više na vašem profilu. - Možete ponovno aktivirati značku podrške na dodatnih 30 dana jednokratnom donacijom. + Jednokratnom donacijom možete ponovno aktivirati značku podrške na dodatnih 30 dana. Možete nastaviti koristiti Signal, ali ako želite podržati aplikaciju koja je napravljena za vas, razmislite o tome da postanete mjesečni pretplatnik. Postanite pretplatnik @@ -4671,7 +4742,7 @@ Vaša mjesečna pretplata je otkazana jer nije bilo moguće obraditi uplatu. Vaša značka više se ne prikazuje na vašem profilu. Vaša mjesečna pretplata je otkazana. %1$s Vaša značka %2$s više se ne prikazuje na vašem profilu. - Možete nastaviti koristiti Signal, ali ako želite podržati aplikaciju i ponovno aktivirati svoju značku, bit će potrebno obnoviti pretplatu. + I dalje možete koristiti Signal, ali ako želite podržati aplikaciju i ponovno aktivirati svoju značku, bit će potrebno obnoviti pretplatu. Obnovi pretplatu Otvori Google Pay @@ -4714,7 +4785,7 @@ Slanje donacije nije uspjelo zbog mrežne pogreške. Provjerite internetsku vezu i pokušajte ponovno. - Donacija u ime korisnika %1$s + Donacija u ime korisnika %1$s Korisnik %1$s donirao je Signalu u vaše ime @@ -5127,7 +5198,7 @@ Dopustite odgovore i reakcije - Dopustite osobama koje mogu vidjeti vašu priču da reagiraju i odgovaraju + Dopustite osobama koje mogu vidjeti vašu priču da reagiraju i odgovaraju na nju Signal kontakti @@ -5311,7 +5382,7 @@ Potvrdite donaciju - Poslati + Pošalji Primatelj će biti obaviješten o donaciji u privatnoj poruci. U nastavku napišite poruku. @@ -5851,5 +5922,15 @@ Izbriši korisničko ime + + + h + + m + + Postavi + + Minimalno vrijeme prije zaključavanja zaslona je 1 minuta. + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 277230078a..6f9832ce61 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -14,6 +14,7 @@ + Igen Nem @@ -97,7 +98,7 @@ Letiltott felhasználók - Felhasználó letiltása + Letiltott felhasználó hozzáadása A letiltott felhasználók nem fognak tudni sem üzenetet küldeni neked, sem pedig felhívni. Nincs letiltott felhasználó Felhasználó letiltása? @@ -139,7 +140,7 @@ %1$s letiltása és kilépés? - Letiltod %1$s-t? + %1$s letiltása? Többé nem kapsz üzeneteket és frissítéseket ebből a csoportból, valamint a tagok nem fognak tudni újra hozzáadni a csoporthoz. A csoport tagjai nem fognak tudni újra hozzáadni ehhez a csoporthoz. A tagok ezután újra hozzáadhatnak majd a csoporthoz. @@ -148,9 +149,9 @@ Újra üzenetet küldhettek egymásnak. A letiltott személyek nem fognak tudni sem felhívni, sem pedig üzenetet küldeni neked. - A blokkolt személyek nem küldhetnek üzeneteket számodra. + A letiltott személyek nem küldhetnek üzeneteket számodra. - Signal újdonságok és hírek blokkolása. + Signal-újdonságok és -hírek letiltása. Signal újdonságok és hírek visszakapcsolása. Feloldod %1$s tiltását? @@ -318,7 +319,7 @@ Signal üzenet Váltsunk Mollyra: %1$s Kérlek válassz egy kontaktot - Blokkolás feloldása + Letiltás feloldása A melléklet mérete meghaladja az adott típushoz tartozó mérethatárt. Nem lehet hangot rögzíteni! Nem küldhetsz ebbe a csoportba üzenetet, mivel nem vagy a tagja. @@ -366,7 +367,7 @@ Hiba történt a médiafájl küldése során - Kéretlen üzenetnek jelölve és tiltva. + Megjelölve kéretlen üzenetként és letiltva. Az SMS-szolgáltatás jelenleg ki van kapcsolva. Üzeneteidet a telefonodon megtalálható másik alkalmazásba exportálhatod. @@ -447,11 +448,11 @@ %1$s bekapcsolva - Elutasítod a kérést? + Letiltod a felkérést? %1$s nem használhatja a csoportlinket a csatlakozáshoz, vagy a csatlakozás kérvényezéséhez. Kézzel továbbra is felvehető a csoport tagjai közé. - Csatlakozási igény + Felkérés letiltása Mégsem @@ -488,12 +489,12 @@ Olvasatlan - Rögzít + Rögzítés Rögzítés - Felold - Feloldás + Rögzítés feloldása + Rögzítés feloldása Némítás @@ -509,8 +510,8 @@ Archiválás - Archívumból ki - Archívumból ki + Archiválás feloldása + Archiválás feloldása Törlés @@ -542,6 +543,15 @@ +%1$d + + Csatlakoztasd újra az eszközeidet + + A hozzáadott eszközök leválasztásra kerültek az eszköz regisztrációjának törlésekor. Lépj a Beállításokba az eszközök újracsatlakoztatásához. + + Beállítok megnyitása + + Később + Tagok kiválasztása @@ -935,7 +945,7 @@ Értesítsen említés esetén - Szeretnél értesítéseket kapni, ha egy lenémított beszélgetésben megemlítenek? + Szeretnél értesítéseket kapni, ha egy némított csevegésben megemlítenek? Mindig értesítsen Sose értesítsen @@ -953,6 +963,16 @@ Felhasználónév létrehozva Felhasználónév másolva + + Nem sikerült törölni a felhasználónevet. Próbáld újra később! + + Felhasználónév törölve + + + + Valami hiba történt a felhasználóneveddel, már nincs hozzárendelve a fiókodhoz. Megpróbálhatod újra beállítani, vagy választhatsz egy újat. + + Javítás most @@ -1156,8 +1176,8 @@ Új csoport Barátok meghívása SMS használata - Megjelenés - Fotó hozzáadása + Csevegés színe + Profilkép hozzáadása Válaszok @@ -1472,10 +1492,10 @@ Tiltás feloldása Engedélyezed, hogy %1$s üzenetet küldjön számodra, és lássa nevedet, valamint profilképedet? Amíg ebbe bele nem egyezel, addig nem értesül arról sem, hogy elolvastad-e az üzeneteit. - Engedélyezed, hogy %1$s üzenetet küldjön számodra, és lássa nevedet, valamint profilképedet? Nem fogsz üzenetet kapni tőle, amíg fel nem oldod a tiltást. + Engedélyezed, hogy %1$s üzenetet küldjön számodra, és lássa a nevedet, valamint a profilképedet? Nem fogsz üzenetet kapni tőle, amíg fel nem oldod a tiltást. - Engedélyezed, hogy %1$s üzenetet küldjön számodra? Amíg nem oldod fel a tiltást, nem kapsz tőle üzeneteket. - Szeretnél híreket és újdonságokat kapni %1$s felhasználótól? Addig nem kapsz értesítéseket tőle, míg fel nem oldod a blokkolását. + Engedélyezed, hogy %1$s üzenetet küldjön számodra? Amíg nem oldod fel a tiltást, nem kapsz tőle üzenetet. + Szeretnél híreket és újdonságokat kapni %1$s felhasználótól? Addig nem kapsz értesítéseket tőle, míg fel nem oldod a letiltását. Folytatod a csoporton belüli csevegést, és megosztod nevedet, valamint profilképed a tagokkal? Frissítsd a csoportot, hogy elérd az új funkciókat, mint az @említés és az admin szerepkör. Azok a tagok, akik nem osztották meg a nevüket vagy képüket, meghívásra kerülnek. Ezt a régi csoportot tovább nem használható, mert túl nagy létszáma. A maximum csoportméret %1$d. @@ -1483,7 +1503,7 @@ Belépsz a csoportba, és megosztod velük neved és profilképedet? Amíg ebbe bele nem egyezel, addig nem értesülnek arról sem, hogy elolvastad-e üzeneteiket. Belépsz a csoportba, és megosztod velük neved és profilképedet? Amíg ebbe bele nem egyezel, addig nem tekintheted meg a csoport üzeneteit. Belépsz ebbe a csoportba? Amíg ezt meg nem erősíted, senki nem értesül róla, hogy láttad az eddigi üzeneteket. - Feloldod a csoport tiltását és megosztod profilodat a tagokkal? Amíg nem oldod fel a tiltást, nem kapsz üzeneteket a csoportból. + Feloldod a csoport tiltását és megosztod a neved és a profilképedet a tagokkal? Amíg nem oldod fel a tiltást, nem kapsz üzeneteket a csoportból. Megtekintés %1$s tagja @@ -1584,9 +1604,20 @@ Új PIN létrehozása + + SMS-kód küldése + + Signal-regisztráció - Segítségkérés a PIN-kód használatához Androidon + + A PIN-kódod egy általad létrehozott %1$d+ számjegyű kód, amely lehet numerikus vagy alfanumerikus.Ha nem emlékszel a PIN-kódodra, létrehozhatsz egy újat. + + Ha nem emlékszel a PIN-kódodra, létrehozhatsz egy újat. + + Elfogyott a PIN-kód megadására rendelkezésre álló kísérletek száma, de továbbra is hozzáférhetsz a Signal-fiókodhoz, ha új PIN-kódot hozol létre. + Figyelmeztetés - PIN kódod letiltásával egy esetleges újraregisztráció során az összes Signal adatod el fog veszni, kivéve ha azokat előtte külön lemented majd visszatöltöd. Letiltott PIN kóddal nem lehetséges a regisztrációs zár bekapcsolása. + PIN-kódod letiltásával egy esetleges újraregisztráció során az összes Signal-adatod el fog veszni, kivéve ha azokat előtte külön lemented, majd helyreállítod. Letiltott PIN-kóddal nem lehetséges a regisztrációs zár bekapcsolása. PIN letiltása @@ -1616,8 +1647,8 @@ Történetem - Blokkolás - Feloldás + Letiltás + Letiltás feloldása @@ -1768,11 +1799,18 @@ A Signalnak hozzá kell férnie telefonkönyvedhez és a médiafájljaidhoz annak érdekében, hogy összeköthessen barátaiddal, és lehetővé váljon az üzenetküldés. Telefonkönyvedet feltöltjük a Signal privát felderítő szolgáltatásába, azonban a végpontok közti titkosításnak köszönhetően annak tartalma még a Signal szolgáltatás üzemeltetői számára sem lesz olvasható. A Signalnak hozzá kell férnie telefonkönyvedhez annak érdekében, hogy összeköthessen barátaiddal, és lehetővé váljon az üzenetküldés. Telefonkönyvedet feltöltjük a Signal privát felderítő szolgáltatásába, azonban a végpontok közti titkosításnak köszönhetően annak tartalma még a Signal szolgáltatás üzemeltetői számára sem lesz olvasható. Túl sokszor próbálkoztál ennek a számnak a regisztrációjával. Kérlek próbáld újra később! + + Túl sokszor próbáltad regisztrálni ezt a számot. Kérjük, próbáld újra %1$s múlva. Nem lehet kapcsolódni szolgáltatáshoz. Kérlek ellenőrizd a hálózati kapcsolatot és próbáld újra! Nem szabványos számformátum A begépelt szám (%1$s) nem tűnik szabványos formátumúnak.\n\nNem erre gondoltál: %2$s ? Molly Android - Telefonszám formátum + Hívás kérve + + SMS kérelmezve + + Ellenőrző kód kérelmezve Még %1$d lépésre vagy a fejlesztői napló elküldésétől. Még %1$d lépésre vagy a hibakeresési napló elküldésétől. @@ -1792,6 +1830,16 @@ Hívás Hitelesítő kód küldése Ellenőrző kód újraküldése + + Problémáid vannak a regisztrációval? + + • Győződj meg róla, hogy telefonod rendelkezik térerővel az SMS vagy a hívás fogadásához\n • Erősítsd meg, hogy fogadhatsz-e hívást a számra\n • Ellenőrizd, hogy helyesen adtad-e meg a telefonszámod. + + További információért kövesd ezeket a hibaelhárítási lépéseket, vagy lépj kapcsolatba az ügyfélszolgálattal + + ezeket a hibaelhárítási lépéseket + + Segítségkérés Bekapcsolod a regisztrációs zárat? @@ -1951,13 +1999,17 @@ Beváltottál egy jelvényt - Reakció a Történetedre: %1$s + Reagált a Történetedre: %1$s - Reakció a Történetére: %1$s + Reagált a Történetére: %1$s Fizetés Ütemezett üzenet + + Az üzenetelőzményeid egyesítésre kerültek + + A(z) %1$s telefonszám %2$s felhasználóhoz tartozik Molly frissítés @@ -2087,14 +2139,16 @@ Titkosítatlan SMS %1$s %2$s Névjegy - %1$s reakció erre: \"%2$s\" - Reakció a videódra: %1$s - Reakció a képedre: %1$s - Reakció a GIF képedre: %1$s - Reakció a fájlodra: %1$s - Reakció a hangfájlodra: %1$s - Reakció az egyszer megtekinthető videódra: %1$s - Reakció a matricádra: %1$s + %1$s reagált erre: „%2$s”. + Reagált a videódra: %1$s. + Reagált a képedre: %1$s. + Reagált a GIF-edre: %1$s. + Reagált a fájlodra: %1$s. + Reagált a hangfájlodra: %1$s. + Reagált az egyszer megtekinthető videódra: %1$s. + + Reagált a fizetésedre: %1$s. + Reagált a matricádra: %1$s. Ez az üzenet ki lett törölve. Kikapcsolod az új kontakt érhető el Signalon értesítést? Újra engedélyezheted a Signal > Beállítások > Értesítések menüpontban. @@ -2298,7 +2352,7 @@ Hívás-értesítések engedélyezése Profil frissítése - Csatlakozási igény + Felkérés letiltása Nincs közös csoportotok. A kérelmek áttekintése során légy óvatos. Nincs ismerősöd ebben a csoportban. A kérelmek áttekintése során légy óvatos. Megtekintés @@ -2487,8 +2541,8 @@ Kézbesítési probléma - Egy üzenet, matrica, reakció emoji, kézbesítési nyugta, vagy média küldése %1$s kontakttól nem érkezett meg hozzád. A küldési kísérlet történhetett egyéni, vagy csoportüzenetben is. - Egy üzenet, matrica, reakció emoji, kézbesítési nyugta, vagy média küldése %1$s kontakttól nem érkezett meg hozzád. + %1$s üzenete, matricája, reakciója vagy olvasási értesítése nem érkezett meg hozzád. A küldési kísérlet történhetett egyéni, vagy csoportüzenetben is. + %1$s üzenete, matricája, reakciója vagy olvasási értesítése nem érkezett meg hozzád. Keresztnév (kötelező) @@ -2635,9 +2689,9 @@ Alapértelmezett használata Egyéni használata - Némítás 1 órára + Némítás egy órára Némítás 8 órára - Némítás 1 napra + Némítás egy napra Némítás 7 napra Végleg @@ -2971,7 +3025,7 @@ Fizetés küldése Beérkezett fizetés Fizetés teljesült %1$s - Blokk szám + Telefonszám letiltása Átutalás @@ -3295,6 +3349,8 @@ Add meg PIN kódodat Gépeld be a fiókodhoz létrehozott PIN kódodat. Ez nem azonos az SMS-ben regisztrációkor kapott ellenőrző kóddal. + + Add meg a fiókodhoz létrehozott PIN-kódot. Add meg az alfanumerikus PIN kódot Add meg a számjegyekből álló PIN kódot Hibás PIN kód. Próbáld újra! @@ -3398,7 +3454,10 @@ A biztonsági mentés egy olyan nagyon nagy fájlt tartalmaz, amelyről nem lehet biztonsági másolatot készíteni. Kérjük, töröld, és hozz létre egy új biztonsági mentést! Koppints a biztonsági mentések kezeléséhez! Helytelen telefonszám? + Hívjon (%1$02d:%2$02d) + + Kód újraküldése (%1$02d:%2$02d) Kapcsolatfelvétel a Signal támogatással Signal regisztráció - megerősítő kód Androidhoz Helytelen kód @@ -3406,6 +3465,18 @@ Ismeretlen Megtekinteni telefonszámom Megtalálni telefonszám alapján + + Telefonszám + + Válaszd ki, hogy ki láthatja a telefonszámod, és ki léphet kapcsolatba veled a Mollyon keresztül. + + Ki láthatja a telefonszámomat? + + A Mollyon senki nem láthatja a telefonszámod + + Ki találhat meg a telefonszámom szerint? + + Telefonszámod látható lesz az ismerősök és csoportok számára, akikkel üzenetet váltasz. Azoknál, akinél szerepelsz készülékük kontaktlistájában, megjelensz Molly kontakjaik között is. Mindenki A kontaktjaim Senki @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" letiltva. - Sikertelen \"%1$s\" tiltása - \"%1$s\" letiltása feloldva. + „%1$s” letiltva. + „%1$s” letiltása sikertelen + „%1$s” letiltása feloldva. Tagok áttekintése @@ -3670,14 +3741,14 @@ A fiók törlése a következőket jelenti: Add meg a telefonszámod Fiók törlése - A fiók információ és profil fotó törlése + Fiókadatok és profilkép törlése Minden üzeneted törlése %1$s törlése a fizetési fiókodban Nincs országkód megadva Nincs szám megadva A megadott telefonszám nem egyezik a fiókodhoz tartozóval. Biztosan törölni akarod a fiókod? - Ez törölni fogja a Signal fiókod és alaphelyzetbe állítja az alkalmazást. Az app bezáródik a folyamat végeztével. + Ez törölni fogja a Signal-fiókod, és alaphelyzetbe állítja az alkalmazást. Az alkalmazás bezárul a folyamat végeztével. Sikertelen a helyi adat törlése. Manuálisan törölheted a rendszer alkalmazás-beállításai alatt. App beállítások indítása @@ -3784,7 +3855,7 @@ Tárca kikapcsolása Egyenleged - A fizetések kikapcsolása előtt javasolt alaptőkédet egy másik tárca címére átutalni. Ha mégis úgy döntesz, hogy most nem utalod át egyenlegedet, akkor az továbbra is a Mollyhoz kapcsolt tárcádban lesz. A fizetések újraaktiválásakor az egyenleg újra elérhető lesz. + A fizetések kikapcsolása előtt javasolt a pénzösszegeidet egy másik tárca címére átutalni. Ha mégis úgy döntesz, hogy most nem utalod át az egyenleged, az továbbra is a Mollyhoz kapcsolt tárcádban lesz. A fizetések újraaktiválásakor az egyenleg újra elérhető lesz. Maradék egyenleg átutalása Kikapcsolás átutalás nélkül Kikapcsolás @@ -4008,7 +4079,7 @@ Üzenetküldés Eltűnő üzenetek App biztonság - Képernyőképek készítésének blokkolása a futó alkalmazások listájában és az alkalmazáson belül + Képernyőképek készítésének letiltása a futó alkalmazások listájában és az alkalmazáson belül Signal üzenetek és hívások, átjátszási beállítások, rejtett feladó Alapértelmezett időzítő új beszélgetésekhez Állítsd be az alapértelmezett időzítőt újonnan indított csevegéseid eltűnő üzeneteihez. @@ -4178,7 +4249,7 @@ Letiltás Csoport letiltása Tiltás feloldása - Csoport blokkolásának feloldása + Csoport letiltásának feloldása Hozzáadás egy csoporthoz Összes megtekintése Tagok hozzáadása @@ -4242,7 +4313,7 @@ %1$s eltávolításra került - %1$s letiltásra került + %1$s letiltva %1$s eltávolítása sikertelen @@ -4508,7 +4579,7 @@ Adományod hálózati hiba miatt nem küldhető el. Ellenőrizd az internetkapcsolatod, és próbáld újra! - Adomány %1$s számára + Adomány %1$s nevében %1$s adományozott a Signalnak a nevedben @@ -4853,13 +4924,13 @@ Nem válaszolhatsz erre a Történetre, mert már nem vagy tagja ennek a csoportnak. - Reakció elküldve a Történetre + Reagált a Történetre Megtekintések Válaszok - Reagálás a Történetre + Reakció a Történetre Privát válasz küldése %1$s részére @@ -5601,5 +5672,15 @@ Felhasználónév törlése + + + ó + + p + + Beállít + + A képernyőzár alkalmazásának minimális ideje 1 perc. + diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index ccf468fa73..86e313be9e 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -14,6 +14,7 @@ + Ya Tidak @@ -97,8 +98,8 @@ Pengguna diblokir - Tambah pengguna diblokir - Pengguna yang diblokir tidak dapat melakukan panggilan atau mengirimkan pesan kepada Anda. + Tambahkan pengguna yang diblokir + Pengguna yang diblokir tidak akan bisa menelepon Anda atau mengirimi Anda pesan. Tidak ada pengguna diblokir Blokir pengguna? \"%1$s\" tidak akan dapat memanggil Anda atau mengirimkan Anda pesan. @@ -138,7 +139,7 @@ Lanjutkan - Blokir dan tinggalkan %1$s? + Blokir dan keluar dari %1$s? Blokir %1$s? Anda tidak lagi dapat menerima pesan atau info terbaru dari grup ini, dan anggota grup tidak dapat menambahkan Anda ke grup ini lagi. Anggota grup tidak dapat lagi menambahkan Anda ke grup ini. @@ -147,15 +148,15 @@ Anda akan bisa memberi pesan dan saling menelepon dan Anda akan membagikan nama dan foto dengan mereka. Anda akan bisa mengirim pesan satu sama lain. - Orang yang Anda blokir tidak akan dapat menelepon atau mengirim pesan pada Anda + Orang yang diblokir tidak akan bisa menelepon Anda atau mengirimi Anda pesan. Orang yang diblokir tidak akan bisa mengirimi Anda pesan. - Berhenti mendapat pembaruan dan berita Signal + Berhenti mendapat pembaruan dan berita Signal. Lanjut mendapatkan pembaruan dan berita Signal. Buka blokir %1$s? Blokir - Blokir dan Tinggalkan + Blokir dan Keluar Laporkan spam dan blokir @@ -449,7 +450,7 @@ Batal - Terblokir + Diblokir Hapus filter @@ -470,26 +471,26 @@ Memindahkan %1$d percakapan ke kotak masuk - Tandai Terbaca + Sudah Dibaca - Belum dibaca + Belum Dibaca Sematkan - Cabut Semat + Lepas Sematan - Senyapkan + Bisukan Bunyikan Pilih - Arispkan + Arsipkan Buka Arsip @@ -522,6 +523,15 @@ +%1$d + + Tautkan kembali perangkat Anda + + Perangkat yang Anda tambahkan telah diputus penautannya saat perangkat batal didaftarkan. Buka Pengaturan untuk menautkan kembali perangkat. + + Buka pengaturan + + Nanti + Pilih anggota @@ -897,7 +907,7 @@ Beritahu saya ketika ada penyebutan - Terima pemberitahuan saat Anda disebut dalam percakapan senyap? + Terima notifikasi saat Anda disebut dalam obrolan yang dibisukan? Selalu beritahu saya Jangan beritahu saya @@ -915,6 +925,16 @@ Nama pengguna dibuat Nama pengguna disalin + + Tidak dapat menghapus nama pengguna. Coba lagi nanti. + + Nama pengguna dihapus + + + + Ada yang salah dengan nama pengguna Anda, nama pengguna tersebut tidak lagi ditetapkan untuk akun Anda. Anda bisa mencoba dan mengaturnya lagi atau memilih yang baru. + + Perbaiki sekarang @@ -1108,8 +1128,8 @@ Grup baru Undang teman Gunakan SMS - Tampilan - Tambahkan foto + Warna obrolan + Tambahkan foto profil Balasan @@ -1414,10 +1434,10 @@ Buka blokir Izinkan %1$s mengirim pesan dan berbagi nama dan foto Anda dengannya? Dia tidak akan tahu Anda telah melihat pesannya hingga Anda menerimanya. - Izinkan %1$s mengirimkan Anda pesan dan bagikan nama dan foto Anda dengan mereka? Anda tidak akan menerima pesan apapun sampai Anda membuka blokir mereka. + Izinkan %1$s mengirimi Anda pesan dan bagikan nama dan foto Anda dengan mereka? Anda tidak akan menerima pesan apa pun sampai Anda membuka blokir mereka. - Bolehkan %1$s mengirim pesan ke Anda? Anda tidak akan mendapatkan pesan baru sampai Anda membuka blokir mereka. - Dapatkan pembaruan dan berita dari %1$s? Anda tidak akan menerima pembaruan apapun sampai Anda membuka blokir mereka. + Izinkan %1$s mengirimi Anda pesan? Anda tidak akan menerima pesan apa pun sampai Anda membuka blokir mereka. + Dapatkan pembaruan dan berita dari %1$s? Anda tidak akan menerima pembaruan apa pun sampai Anda membuka blokir mereka. Lanjutkan percakapan Anda dengan grup ini dan bagikan nama dan foto Anda dengan anggotanya? Perbarui grup ini untuk mengaktifkan fitur-fitur baru seperti @penyebutan dan admin. Anggota yang tidak membagikan nama atau fotonya di grup ini akan diundang untuk bergabung. Grup Lama tidak lagi dapat digunakan karena berukuran terlalu besar. Ukuran maksimum grup adalah %1$d. @@ -1425,7 +1445,7 @@ Bergabung dengan grup ini dan bagikan nama dan foto Anda dengan anggotanya? Mereka tidak akan mengetahuinya sampai Anda menerimanya. Bergabung dengan grup ini dan bagikan nama dan foto Anda dengan anggotanya? Anda tidak akan bisa melihat pesan mereka sampai Anda menerimanya. Bergabung ke dalam grup? Mereka tidak akan tahu jika Anda telah membaca pesan mereka, sampai Anda menerimanya. - Buka blokir grup ini dan bagikan nama dan foto Anda dengan anggotanya? Anda tidak akan menerima pesan apapun sampai Anda membuka blokirnya. + Buka blokir grup ini dan bagikan nama dan foto Anda dengan anggotanya? Anda tidak akan menerima pesan apa pun sampai Anda membuka blokir mereka. Lihat Anggota dari %1$s @@ -1520,9 +1540,20 @@ Buat PIN baru + + Kirim kode SMS + + Pendaftaran Signal - Perlu Bantuan pendaftaran ulang PIN untuk Android + + PIN Anda adalah %1$d+ kode digit yang Anda buat, bisa numerik atau alfanumerik.\n\nJika tidak ingat PIN Anda, Anda bisa membuat yang baru. + + Jika tidak ingat PIN Anda, Anda bisa membuat yang baru. + + Kesempatan Anda menebak PIN sudah habis, tapi Anda masih bisa mengakses akun Signal dengan membuat PIN baru. + Peringatan - Jika Anda menonaktifkan PIN, Anda akan kehilangan data saat Anda mendaftar ulang Signal kecuali Anda melakukan pencadangan manual dan memulihkannya. Anda tidak dapat menyalakan Kunci Pendaftaran saat PIN dinonaktifkan. + Jika Anda menonaktifkan PIN, Anda akan kehilangan semua data saat Anda mendaftar ulang Signal kecuali Anda melakukan pencadangan manual dan memulihkannya. Anda tidak dapat mengaktifkan Kunci Pendaftaran jika PIN dinonaktifkan. Nonaktifkan PIN @@ -1644,7 +1675,7 @@ Bunyikan - Senyap + Bisukan Dering @@ -1661,7 +1692,7 @@ Anda tidak akan bisa menerima panggilan suara atau video dan mereka tidak bisa menerima panggilan Anda. Tidak dapat menerima audio & video dari %1$s Tidak dapat menerima audio dan video dari %1$s - Ini mungkin terjadi karena mereka belum memverifikasi pergantian nomor keamanan, ada masalah dengan perangkat, atau mereka memblokir Anda. + Ini mungkin terjadi karena mereka belum memverifikasi pergantian nomor keamanan, ada masalah dengan perangkat mereka, atau mereka memblokir Anda. Usap untuk melihat layar yang dibagikan @@ -1698,11 +1729,18 @@ Signal memerlukan akses ke kontak dan media agar dapat terhubung dengan teman dan mengirimkan pesan. Kontak Anda diunggah menggunakan pendeteksi kontak privat Signal, yang dienkripsi ujung-ke-ujung dan tidak akan terlihat pada layanan Signal. Signal memerlukan akses ke kontak agar dapat terhubung dengan teman Anda. Kontak Anda diunggah menggunakan pendeteksi kontak privat Signal, yang dienkripsi ujung-ke-ujung dan tidak akan terlihat pada layanan Signal. Anda membuat terlalu banyak percobaan untuk mendaftarkan nomor ini. Mohon coba lagi nanti. + + Anda terlalu sering mencoba mendaftar dengan nomor ini. Mohon coba lagi dalam %1$s. Tidak bisa terhubung ke layanan. Mohon cek koneksi jaringan dan coba lagi. Format nomor tidak umum Nomor yang Anda masukkan (%1$s) terlihat bukan format nomor yang umum.\n\nApakah maksud Anda %2$s? Molly Android - Format Nomor Telepon + Panggilan diminta + + SMS diminta + + Kode verifikasi diminta Kamu memiliki %1$d langkah lagi untuk mengirimkan catatan awakutu. @@ -1721,6 +1759,16 @@ Panggil Kode Verifikasi Kirim Ulang Kode + + Ada masalah saat mendaftar? + + • Pastikan ponsel Anda memiliki sinyal seluler untuk menerima SMS atau panggilan\n • Pastikan Anda dapat menerima panggilan telepon ke nomor tersebut\n • Pastikan Anda telah memasukkan nomor ponsel yang benar. + + Untuk informasi selengkapnya, ikuti langkah pemecahan masalah berikut atau Hubungi Dukungan + + langkah pemecahan masalah berikut + + Hubungi Dukungan Nyalakan Kunci Pendaftaran? @@ -1880,13 +1928,17 @@ Anda menebus sebuah lencana - Memberi tanggapan %1$s ke cerita Anda + Menanggapi %1$s pada cerita Anda - Memberi tanggapan %1$s ke cerita mereka + Menanggapi %1$s pada cerita mereka Pembayaran Pesan terjadwal + + Riwayat pesan Anda telah digabungkan + + %1$s milik %2$s Pembaharuan Molly @@ -1958,7 +2010,7 @@ Pesan MMS dienkripsi untuk sesi yang tidak ada - Senyapkan pemberitahuan + Bisukan notifikasi Proses mengimpor data sedang berjalan @@ -2015,14 +2067,16 @@ SMS tidak terenkripsi %1$s %2$s Kontak - Memberi tanggapan %1$s untuk: \"%2$s\". - Memberi tanggapan %1$s untuk video Anda. - Memberi tanggapan %1$s untuk gambar Anda. - Memberi tanggapan %1$s untuk GIF Anda. - Memberi tanggapan %1$s untuk file Anda. - Memberi tanggapan %1$s untuk audio Anda. - Memberi tanggapan %1$s untuk media sekali-lihat Anda. - Memberi tanggapan %1$s untuk stiker Anda. + Menanggapi %1$s pada: \"%2$s\". + Menanggapi %1$s pada video Anda. + Menanggapi %1$s pada gambar Anda. + Menanggapi %1$s pada GIF Anda. + Menanggapi %1$s pada file Anda. + Menanggapi %1$s pada audio Anda. + Menanggapi %1$s pada media sekali-lihat Anda. + + Menanggapi %1$s pada pembayaran Anda. + Menanggapi %1$s pada stiker Anda. Pesan ini telah dihapus. Matikan notifikasi kontak bergabung Signal? Anda dapat menyalakannya kembali di Signal > Pengaturan > Notifikasi. @@ -2404,8 +2458,8 @@ Masalah Pengiriman - Sebuah pesan, stiker, tanggapan, atau tanda pesan terbaca tidak dapat Anda terima dari %1$s. Mereka mungkin mencoba mengirimkannya secara langsung, atau di dalam grup. - Sebuah pesan, stiker, tanggapan, atau tanda pesan terbaca tidak dapat Anda terima dari %1$s. + Sebuah pesan, stiker, tanggapan, atau bukti pesan terbaca tidak dapat Anda terima dari %1$s. Mereka mungkin mencoba mengirimkannya langsung kepada Anda, atau di dalam grup. + Sebuah pesan, stiker, tanggapan, atau bukti pesan terbaca tidak dapat Anda terima dari %1$s. Nama depan (diperlukan) @@ -2472,7 +2526,7 @@ Tertunda - Terkirim ke + Dikirim ke Dikirim dari Terkirim ke Dibaca oleh @@ -2552,10 +2606,10 @@ Gunakan standar Gunakan pilihan khusus - Senyapkan selama 1 jam - Senyapkan selama 8 jam - Senyapkan selama 1 hari - Senyapkan selama 7 hari + Bisukan selama 1 jam + Bisukan selama 8 jam + Bisukan selama 1 hari + Bisukan selama 7 hari Selalu Pengaturan standar @@ -2591,9 +2645,9 @@ Gunakan foto dari kontak Tampilkan foto dari kontak jika tersedia - Biar Percakapan Dibisukan Diarsipkan + Biarkan Obrolan yang Dibisukan tetap Diarsipkan - Percakapan dibisukan yang diarsipkan akan tetap diarsipkan saat pesan baru masuk. + Obrolan dibisukan yang diarsipkan akan tetap diarsipkan saat ada pesan baru masuk. Buat pratinjau tautan Mengambil pratinjau dari tautan secara langsung dari situs web untuk pesan yang Anda kirimkan. Ganti frasa sandi @@ -3046,7 +3100,7 @@ Bunyikan - Senyapkan pemberitahuan + Bisukan notifikasi Pengaturan grup @@ -3207,6 +3261,8 @@ Masukkan PIN Anda Masukkan PIN yang Anda buat untuk akun Anda. Ini berbeda dari kode verifikasi SMS. + + Masukkan PIN yang Anda buat untuk akun Anda. Masukan PIN alfanumerik Masukkan PIN numerik PIN Salah. Coba lagi. @@ -3305,7 +3361,10 @@ Pencadangan Anda berisi file sangat besar yang tidak dapat dicadangkan. Hapus file dan buat pencadangan baru. Tekan untuk mengatur cadangan. Salah nomor? + Panggil saya (%1$02d:%2$02d) + + Kirim Kode Lagi (%1$02d:%2$02d) Kontak Pusat Bantuan Signal Pendaftaran Signal - Kode Verifikasi untuk Android Kode Salah @@ -3313,6 +3372,18 @@ Tidak dikenal Lihat nomor telepon saya Temukan saya melalui nomor telepon + + Nomor telepon + + Pilih siapa yang bisa melihat nomor telepon Anda dan siapa yang bisa menghubungi Anda di Molly dengan nomor tersebut. + + Siapa bisa melihat nomor saya + + Tidak akan ada yang melihat nomor Anda di Molly + + Siapa bisa menemukan saya dengan nomor telepon + + Nomor telepon Anda akan terlihat oleh orang dan grup yang Anda kirimi pesan. Orang yang menyimpan nomor Anda di kontaknya juga akan dapat melihat nomor telepon Anda di Molly. Semua orang Kontak saya Tak seorangpun @@ -3524,7 +3595,7 @@ %1$s/%2$s \"%1$s\" telah diblokir. Gagal memblokir \"%1$s\" - Blokir \"%1$s\" dibuka. + Blokir \"%1$s\" telah dibuka. Tinjau Anggota @@ -3688,12 +3759,12 @@ Nonaktifkan Wallet Saldo Anda - Anda direkomendasikan untuk mentransfer dana Anda ke alamat wallet lain sebelum menonaktifkan pembayaran. Jika Anda memilih untuk tidak mentransfernya sekarang, dana Anda akan tetap di wallet yang tersambung ke Molly jika Anda mengaktifkan kembali pembayaran. + Disarankan untuk mentransfer dana Anda ke alamat dompet lain sebelum menonaktifkan pembayaran. Jika Anda memilih untuk tidak mentransfernya sekarang, dana Anda akan tetap ada di dompet yang terhubung dengan Molly jika Anda mengaktifkan kembali pembayaran. Transfer saldo yang tersisa Nonaktifkan tanpa transfer Nonaktifkan Nonaktifkan tanpa transfer? - Saldo Anda akan tetap di dalam wallet yang terhubung ke Molly jika Anda memilih untuk mengaktifkan kembali pembayaran. + Saldo Anda akan tetap ada di dompet yang terhubung dengan Molly jika Anda memilih untuk mengaktifkan kembali pembayaran. Gagal menonaktifkan wallet. @@ -3906,7 +3977,7 @@ Buat profil - Terblokir + Diblokir %1$d kontak Olah pesan Penghilangan pesan @@ -4066,9 +4137,9 @@ Panggil - Senyap + Bisukan - Disenyapkan + Dibisukan Cari Penghilangan pesan @@ -4087,9 +4158,9 @@ Permintaan & undangan Tautan grup Tambahkan sebagai kontak - Tidak Senyap - Percakapan disenyapkan sampai %1$s - Percakapan disenyapkan selamanya + Bunyikan + Percakapan dibisukan sampai %1$s + Percakapan dibisukan selamanya Nomor telepon disalin ke papan klip. Nomor telepon Dapatkan lencana untuk profil Anda dengan mendukung Signal. Ketuk lencana untuk mempelajari lebih lanjut. @@ -4105,8 +4176,8 @@ Siapa yang dapat mengirimkan pesan? - Senyapkan pemberitahuan - Tidak Senyap + Bisukan notifikasi + Tidak dibisukan Menyebut Selalu berikan notifikasi Jangan berikan notifikasi @@ -4350,7 +4421,7 @@ Donasi Bulanan Dibatalkan Lencana Boost Anda telah kedaluwarsa dan tidak terlihat lagi di profil Anda. - Anda dapat mengaktifkan kembali lencana Boost Anda selama 30 hari lagi dengan kontribusi satu kali. + Anda dapat mengaktifkan kembali lencana Boost untuk 30 hari lagi dengan kontribusi satu kali. Anda tetap dapat menggunakan Signal, tetapi untuk mendukung teknologi yang dibuat untuk Anda, pertimbangkan untuk menjadi penyokong dengan memberikan donasi bulanan. Menjadi Penyokong @@ -4362,7 +4433,7 @@ Donasi bulanan berulang Anda telah dibatalkan karena kami tidak dapat memproses pembayaran Anda. Lencana Anda tidak terlihat lagi di profil Anda. Donasi bulanan berulang Anda telah dibatalkan. %1$s Lencana %2$s Anda tidak terlihat lagi di profil Anda. - Anda tetap dapat menggunakan Signal, tetapi untuk mendukung aplikasi dan mengaktifkan kembali lencana Anda, perpanjang langganan sekarang. + Anda tetap dapat menggunakan Signal, tetapi untuk mendukung aplikasi dan mengaktifkan kembali lencana Anda, lakukan perpanjangan sekarang. Perpanjang langganan Pergi ke Google Pay @@ -4405,7 +4476,7 @@ Donasi Anda tidak bisa dikirim karena kesalahan jaringan. Periksa koneksi dan coba lagi. - Donasi ke %1$s + Donasi atas nama %1$s %1$s berdonasi ke Signal atas nama Anda @@ -5476,5 +5547,15 @@ Hapus nama pengguna + + + j + + m + + Atur + + Waktu minimum sebelum kunci layar diterapkan adalah 1 menit. + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 080f6970b3..249e5a111f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -14,6 +14,7 @@ + No @@ -100,7 +101,7 @@ Aggiungi utente bloccato Gli utenti bloccati non potranno chiamarti o inviarti messaggi. Nessun utente bloccato - Bloccare utente? + Vuoi bloccare l\'utente? \"%1$s\" non potrà chiamarti o inviarti messaggi. Blocca @@ -138,8 +139,8 @@ Continua - Bloccare e abbandonare %1$s? - Bloccare %1$s? + Vuoi bloccare e abbandonare %1$s? + Vuoi bloccare %1$s? Non riceverai più messaggi o aggiornamenti da questo gruppo e i membri non potranno aggiungerti di nuovo a questo gruppo. I membri del gruppo non potranno aggiungerti di nuovo a questo gruppo. I membri del gruppo potranno aggiungerti di nuovo a questo gruppo. @@ -153,10 +154,10 @@ Blocca gli aggiornamenti e le notizie di Signal. Riprendi gli aggiornamenti e le notizie di Signal. - Sbloccare %1$s? + Vuoi sbloccare %1$s? Blocca Blocca e abbandona - Segnala spam e blocca + Segnala come spam e blocca Oggi @@ -366,7 +367,7 @@ Errore durante l\'invio del media - Segnalato come spam e bloccato. + Utente segnalato come spam e bloccato. Al momento la messaggistica SMS è disattivata. Puoi esportare i tuoi messaggi su un\'altra app sul tuo telefono. @@ -447,7 +448,7 @@ %1$s attivo - Bloccare richiesta? + Vuoi bloccare la richiesta? %1$s non sarà in grado di unirsi o richiedere di unirsi a questo gruppo tramite il link del gruppo. Può comunque essere aggiunto al gruppo manualmente. @@ -455,7 +456,7 @@ Annulla - Bloccati + Richiesta bloccata Rimuovi filtro @@ -542,6 +543,15 @@ +%1$d + + Ricollega i tuoi dispositivi + + I dispositivi che avevi aggiunto sono stati scollegati quando è stata annullata la registrazione del tuo dispositivo. Vai nelle Impostazioni per ricollegare i dispositivi che ti interessano. + + Apri impostazioni + + Più tardi + Seleziona membri @@ -935,7 +945,7 @@ Notificami per le menzioni - Ricevere le notifiche quando vieni menzionato nelle chat mutate? + Vuoi ricevere le notifiche quando ti menzionano nelle chat silenziate? Notificami sempre Non notificarmi @@ -953,6 +963,16 @@ Nome utente creato Nome utente copiato + + Impossibile eliminare il nome utente. Riprova più tardi. + + Nome utente eliminato + + + + Qualcosa è andato storto con il tuo nome utente: non è più assegnato al tuo account. Puoi provare a impostarlo di nuovo oppure sceglierne un altro. + + Risolvi ora @@ -1156,8 +1176,8 @@ Nuovo gruppo Invita amici Usa SMS - Aspetto - Aggiungi foto + Colori chat + Aggiungi una foto profilo Risposte @@ -1472,10 +1492,10 @@ Sblocca Permettere a %1$s di inviarti messaggi e condividere il tuo nome e la tua foto con loro? Non sapranno che hai visto i loro messaggi finché non accetti. - Permettere a %1$s di inviarti messaggi e condividere il tuo nome e la tua foto con loro? Non riceverai alcun messaggio finché non li sbloccherai. + Vuoi condividere il tuo nome e la tua foto con %1$s e permettere a questo utente di inviarti messaggi? Non riceverai messaggi da parte sua finché non sbloccherai il contatto. - Permettere a %1$s di inviarti messaggi? Non riceverai alcun messaggio finché non li sbloccherai. - Ricevere aggiornamenti e notizie da %1$s? Non riceverai aggiornamenti finché non li sbloccherai. + Vuoi consentire a %1$s di inviarti messaggi? Non riceverai messaggi da parte sua finché non sbloccherai il contatto. + Vuoi ricevere aggiornamenti e notizie da %1$s? Non riceverai aggiornamenti finché non sbloccherai il contatto. Continuare la tua conversazione con questo gruppo e condividere il tuo nome e la tua foto con i suoi membri? Aggiorna questo gruppo per attivare nuove funzionalità come @menzioni e amministratori. I membri che non hanno condiviso il loro nome o la loro foto in questo gruppo saranno invitati a unirsi. Questo Gruppo Legacy non può più essere usato perché è troppo grande. La dimensione massima del gruppo è %1$d. @@ -1483,7 +1503,7 @@ Unirsi a questo gruppo e condividere il tuo nome e la tua foto con i suoi membri? Non sapranno che hai visto i loro messaggi finché non accetti. Unirsi a questo gruppo e condividere il tuo nome e la tua foto con i suoi membri? Non vedrai i loro messaggi finché non avrai accettato. Vuoi unirti a questo gruppo? Non sapranno che hai visto i loro messaggi finché non accetti. - Sbloccare questo gruppo e condividere il tuo nome e la tua foto con i suoi membri? Non riceverai alcun messaggio finché non li sbloccherai. + Vuoi sbloccare questo gruppo e condividere il tuo nome e la tua foto con chi ne fa parte? Non riceverai messaggi finché non sbloccherai il gruppo. Mostra Membro di %1$s @@ -1584,6 +1604,17 @@ Crea nuovo PIN + + Invia codice via SMS + + Registrazione su Signal - Assistenza per registrare di nuovo il tuo PIN per Android + + Il tuo PIN è un codice di almeno %1$d cifre che hai creato tu e può essere numerico o alfanumerico.\n\nSe non ricordi il tuo PIN, puoi crearne uno nuovo. + + Se non ricordi il tuo PIN, puoi crearne uno nuovo. + + Hai esaurito i tentativi per inserire il tuo PIN, ma puoi comunque accedere al tuo account Signal creando un nuovo PIN. + Attenzione Se disabiliti il PIN, perderai tutti i dati quando ti registri nuovamente con Signal a meno che tu non esegua manualmente il backup e il ripristino. Non è possibile attivare il Blocco registrazione mentre il PIN è disabilitato. @@ -1713,7 +1744,7 @@ Non silenziare - Muto + Silenzia Squilla @@ -1726,12 +1757,12 @@ - %1$s è bloccato + L\'utente %1$s è bloccato Ulteriori informazioni Non riceverai il loro audio o video e loro non riceveranno il tuo. Non è possibile ricevere audio & video da %1$s Non è possibile ricevere audio e video da %1$s - Ciò potrebbe essere dovuto al fatto che non hanno verificato la modifica del codice di sicurezza, c\'è un problema con il loro dispositivo o ti hanno bloccato. + Ciò potrebbe essere dovuto al fatto che non è stata verificata la modifica del codice di sicurezza, c\'è un problema con il dispositivo o ti hanno bloccato. Scorri per vedere la condivisione dello schermo @@ -1768,11 +1799,18 @@ Signal richiede l\'accesso ai tuoi contatti e ai tuoi contenuti multimediali per aiutarti a connetterti con gli amici e inviare messaggi. I tuoi contatti vengono caricati utilizzando la scoperta privata dei contatti di Signal, il che significa che sono crittografati end-to-end e non sono mai visibili al servizio Signal. Signal richiede l\'accesso ai tuoi contatti per aiutarti a connetterti con gli amici. I tuoi contatti vengono caricati utilizzando la scoperta privata dei contatti di Signal, il che significa che sono crittografati end-to-end e non sono mai visibili al servizio Signal. Hai fatto troppi tentativi per registrare questo numero. Per favore riprova più tardi. + + Hai fatto troppi tentativi per registrarti con questo numero. Per favore riprova tra %1$s. Impossibile connettersi al servizio. Per favore controlla la connessione a internet e riprova Formato del numero non standard Il numero che hai inserito (%1$s) sembra essere in un formato non standard.\n\nIntendevi %2$s? Molly Android - Formato Numero di Telefono + Chiamata richiesta + + Hai richiesto il codice via SMS + + Hai richiesto l\'invio del codice di verifica Ti manca solo %1$d passaggio per inviare un log di debug. Ti mancano solo %1$d passaggi per inviare un log di debug. @@ -1792,6 +1830,16 @@ Chiama Codice di verifica Rinvia codice + + Problemi con la tua registrazione? + + • Assicurati che il tuo telefono sia collegato a una rete dati cellulare per poter ricevere il tuo SMS o la chiamata\n • Accertati di poter ricevere una chiamata sul tuo numero\n • Controlla di aver inserito correttamente il tuo numero di telefono. + + Per maggiori info, segui questi passaggi per risolvere eventuali problemi oppure contatta la nostra assistenza + + questi passaggi per risolvere eventuali problemi + + Contatta l\'assistenza Attivare il blocco registrazione? @@ -1953,11 +2001,15 @@ Ha reagito con %1$s alla tua Storia - Ha reagito con %1$s alla propria Storia + Hai reagito con %1$s alla sua Storia Pagamento Messaggi programmati + + La tua cronologia messaggi è stata unita + + %1$s appartiene a %2$s Aggiornamento Molly @@ -2087,14 +2139,16 @@ SMS non sicuro %1$s %2$s Contatto - Reagito %1$s a: \"%2$s\". - Reagito %1$s al tuo video. - Reagito %1$s alla tua immagine. - Reagito %1$s alla tua GIF. - Reagito %1$s al tuo file. - Reagito %1$s al tuo audio. - Reagito %1$s al tuo media visualizzabile una volta. - Reagito %1$s al tuo adesivo. + Ha reagito con %1$s a: \"%2$s\". + Ha reagito con %1$s al tuo video. + Ha reagito con %1$s alla tua immagine. + Ha reagito con %1$s alla tua GIF. + Ha reagito con %1$s al tuo file. + Ha reagito con %1$s al tuo audio. + Ha reagito con %1$s al tuo media visualizzabile una volta. + + Ha reagito con %1$s al tuo pagamento. + Ha reagito con %1$s al tuo sticker. Questo messaggio è stato eliminato. Disattivare le notifiche quando un contatto si unisce a Signal? Puoi attivarle di nuovo in Signal > Impostazioni > Notifiche. @@ -2487,8 +2541,8 @@ Problema di consegna - Non è stato possibile consegnarti un messaggio, un adesivo, una reazione o una conferma di lettura da %1$s. Potrebbero aver provato a inviartelo direttamente o in gruppo. - Non è stato possibile consegnarti un messaggio, un adesivo, una reazione o una conferma di lettura da %1$s. + Non è stato possibile consegnarti un messaggio, uno sticker, una reazione o una conferma di lettura da parte di %1$s. Questa persona potrebbe aver provato a inviarti il contenuto in una chat singola o in un gruppo. + Non è stato possibile consegnarti un messaggio, uno sticker, una reazione o una conferma di lettura da parte di %1$s. Nome (obbligatorio) @@ -2635,7 +2689,7 @@ Usa predefinito Usa personalizzato - Silenzia per 1 ora + Silenzia per un\'ora Silenzia per 8 ore Silenzia per 1 giorno Silenzia per 7 giorni @@ -2677,7 +2731,7 @@ Mantieni le chat silenziate nell\'archivio - Le chat silenziate che archivi rimangono archiviate anche quando arriva un nuovo messaggio al loro interno. + Le chat silenziate che archivi rimangono archiviate anche quando arriva un nuovo messaggio. Genera anteprime dei link Recupera le anteprime dei link direttamente dai siti web per i messaggi che invii. Cambia password @@ -3128,7 +3182,7 @@ - Annulla silenzioso + Non silenziare Silenzia notifiche @@ -3295,6 +3349,8 @@ Inserisci il tuo PIN Inserisci il PIN che hai creato per il tuo account. Questo è diverso dal tuo codice di verifica SMS. + + Inserisci il PIN che hai creato per il tuo account. Inserisci PIN alfanumerico Inserisci PIN numerico PIN errato. Riprova. @@ -3398,7 +3454,10 @@ Il tuo backup contiene un file di dimensioni troppo grandi e non può essere salvato. Ti consigliamo di eliminarlo e di creare un nuovo backup. Clicca per gestire i backup. Numero errato? + Chiamami (%1$02d:%2$02d) + + Invia di nuovo il codice (%1$02d:%2$02d) Contatta l\'assistenza Signal Registrazione Signal - Codice di verifica per Android Codice errato @@ -3406,6 +3465,18 @@ Sconosciuto Vedere il mio numero di telefono Trovarmi tramite numero di telefono + + Numero di telefono + + Scegli chi può vedere il tuo numero di telefono e chi può contattarti su Molly tramite il tuo numero. + + Chi può vedere il mio numero di telefono + + Nessuno vedrà il tuo numero di telefono su Molly + + Chi può trovarmi tramite il mio numero + + Il tuo numero di telefono sarà visibile a tutte le persone e ai gruppi a cui invii messaggi. Le persone che hanno il tuo numero tra i loro contatti lo vedranno anche su Molly. Chiunque I miei contatti Nessuno @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" è stato bloccato. + L\'utente \"%1$s\" è stato bloccato. Impossibile bloccare \"%1$s\" - \"%1$s\" è stato sbloccato. + L\'utente \"%1$s\" è stato sbloccato. Controlla membri @@ -3667,17 +3738,17 @@ Connessione Wi-Fi debole: passaggio alla rete mobile effettuato. - Eliminando il tuo account verranno: + Eliminando il tuo account: Inserisci il tuo numero di telefono Elimina account - Elimina le informazioni del tuo account e la foto profilo - Elimina tutti i tuoi messaggi + Verranno eliminate le informazioni del tuo account e la foto profilo + Verranno eliminati tutti i tuoi messaggi Elimina %1$s nel tuo account pagamenti Nessun prefisso telefonico internazionale specificato Nessun numero specificato Il numero di telefono che hai inserito non corrisponde a quello del tuo account. Vuoi davvero eliminare il tuo account? - Questo eliminerà il tuo account Signal e ripristinerà l\'applicazione. L\'app si chiuderà al termine del processo. + Quest\'azione eliminerà il tuo account Signal e ripristinerà l\'applicazione. L\'app si chiuderà al termine del processo. Impossibile eliminare i dati locali. Puoi cancellarli manualmente nelle impostazioni di sistema dell\'applicazione. Avvia impostazioni app @@ -3784,12 +3855,12 @@ Disattiva portafoglio Il tuo saldo - Si consiglia di trasferire i fondi su un altro indirizzo del portafoglio prima di disattivare i pagamenti. Se scegli di non trasferire i tuoi fondi ora, rimarranno nel tuo portafoglio collegato a Molly se riattivi i pagamenti. + Ti consigliamo di trasferire i fondi su un altro indirizzo del wallet prima di disattivare i pagamenti. Se scegli di non trasferirli ora, i tuoi fondi rimarranno nel tuo wallet collegato a Molly se riattivi i pagamenti. Trasferisci il saldo rimanente Disattiva senza trasferire Disattiva Disattivare senza trasferire? - Il tuo saldo rimarrà nel tuo portafoglio collegato a Molly se scegli di riattivare i pagamenti. + Il tuo saldo rimarrà nel tuo wallet collegato a Molly se scegli di riattivare i pagamenti. Errore durante la disattivazione del portafoglio. @@ -4008,7 +4079,7 @@ Messaggistica Messaggi a scomparsa Sicurezza app - Blocca gli screenshot nell\'elenco delle app recenti e all\'interno dell\'app + Blocca gli screenshot nell\'elenco delle chat recenti e all\'interno dell\'app Messaggi e chiamate Signal, ritrasmetti sempre le chiamate e mittente sigillato Timer predefinito per nuove chat Imposta un timer predefinito per la scomparsa dei messaggi per tutte le nuove chat avviate da te. @@ -4106,7 +4177,7 @@ Non ora - Personalizza reazioni + Personalizza le reazioni Clicca per sostituire un\'emoji Resetta Salva @@ -4187,7 +4258,7 @@ Link del gruppo Aggiungi come un contatto Non silenziare - Conversazione silenziata fino a %1$s + Conversazione silenziata fino al %1$s Conversazione silenziata per sempre Numero di telefono copiato negli appunti. Numero di telefono @@ -4242,7 +4313,7 @@ Hai rimosso %1$s - %1$s è stato bloccato + L\'utente %1$s è stato bloccato Impossibile rimuovere %1$s @@ -4330,7 +4401,7 @@ Aggiungi alla Storia Aggiungi un messaggio Aggiungi una risposta - Inviato a + Invia a Messaggio visualizzabile una volta Uno o più elementi erano troppo grandi Uno o più elementi non erano validi @@ -4453,7 +4524,7 @@ Donazione mensile cancellata Il tuo badge Boost è scaduto e non è più visibile sul tuo profilo. - Puoi riattivare il tuo badge Boost per altri 30 giorni con un contributo una tantum. + Puoi riattivare il tuo Badge Signal - Boost per altri 30 giorni con una donazione singola. Puoi continuare a usare Signal, ma puoi scegliere di effettuare una donazione mensile per sostenere una tecnologia costruita per te. Diventa un sostenitore @@ -4465,7 +4536,7 @@ La tua donazione mensile ricorrente è stata cancellata perché non abbiamo potuto elaborare il tuo pagamento. Il tuo badge non è più visibile sul tuo profilo. La tua donazione mensile ricorrente è stata annullata. %1$s Il tuo badge %2$s non è più visibile sul tuo profilo. - Puoi continuare a usare Signal ma, se vuoi, puoi rinnovare il tuo contributo per sostenere l\'app e riattivare il tuo badge. + Puoi continuare a usare Signal ma, se vuoi, puoi rinnovare la tua donazione per sostenere l\'app e riattivare il tuo badge. Rinnova contributo Vai a Google Pay @@ -4508,7 +4579,7 @@ Impossibile inviare la tua donazione per via di un errore di rete. Controlla la tua connessione e riprova. - Donazione a %1$s + Donazione per conto di %1$s %1$s ha fatto una donazione a Signal per conto tuo @@ -5087,7 +5158,7 @@ Conferma la donazione - Inviato a + Invia a La persona destinataria verrà notificata della donazione in un messaggio privato. Aggiungi di seguito il messaggio che vuoi inviare. @@ -5601,5 +5672,15 @@ Elimina nome utente + + + h + + min + + Imposta + + Il tempo minimo prima che si attivi il blocco schermo è 1 minuto. + diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index fb97f2a4db..d12152df1d 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -14,13 +14,14 @@ + כן לא מחיקה אנא המתן… שמור - הערה לעצמי + הערות לעצמי @@ -97,12 +98,12 @@ משתמשים חסומים - הוסף משתמש חסום - משתמשים חסומים לא יוכלו להתקשר אליך או לשלוח לך הודעות. + הוספת משתמש/ת חסום/ה + משתמשים חסומים לא יוכלו להתקשר או לשלוח לך הודעות. אין משתמשים חסומים - לחסום משתמש? + לחסום משתמש/ת? \"%1$s\" לא יוכל/תוכל להתקשר אליך או לשלוח לך הודעות. - חסום + חסימה @@ -147,16 +148,16 @@ תוכלו להתכתב ולהתקשר זה עם זה, והשם והתמונה שלכם ישותפו עם הצד השני. תוכלו להתכתב זה עם זה. - אנשים חסומים לא יוכלו להתקשר אליך או לשלוח לך הודעות. + אנשים חסומים לא יוכלו להתקשר או לשלוח לך הודעות. אנשים חסומים לא יוכלו לשלוח לך הודעות. - חסום קבלת עדכונים וחדשות של Signal. + חסימת קבלת עדכונים וחדשות של Signal. המשך קבלת עדכונים וחדשות של Signal. - לשחרר חסימה של %1$s? - חסום - חסום ועזוב - דווח על זבל וחסום + לבטל חסימה של %1$s? + חסימה + חסימה ועזיבה + דיווח על דואר זבל וחסימה היום @@ -318,7 +319,7 @@ הודעת Signal בוא נחליף אל Molly %1$s אנא בחר איש קשר - שחרר חסימה + ביטול חסימה הצרופה חורגת ממגבלות הגודל עבור הסוג של ההודעה שאתה שולח. לא היה ניתן להקליט שמע! אינך יכול לשלוח הודעות אל קבוצה זו מאחר שאינך חבר קבוצה יותר. @@ -366,7 +367,7 @@ שגיאה בשליחת מדיה - דֻּוַּח כזבל ונחסם. + דווח/ה כדואר זבל ונחסם/ה. הודעות SMS מושבתות כעת. אפשר לייצא את ההודעות שלך לאפליקציה אחרת בטלפון שלך. @@ -463,11 +464,11 @@ %1$s לא יוכל/תוכל להצטרף או לבקש להצטרף אל הקבוצה הזאת באמצעות קישור הקבוצה. הוא/היא עדין יכול/ה להתווסף אל הקבוצה באופן ידני. - חסום בקשה + חסימת בקשה בטל - חסום + חסום/ה ניקוי מסנן @@ -500,53 +501,53 @@ %1$d שיחות הועברו לתיבת הדואר הנכנס - קרא - קרא - קרא - קרא + נקראה + נקראו + נקראו + נקראו - בטל קריאה - בטל קריאה - בטל קריאה - בטל קריאה + סימון כלא נקרא + סימון כלא נקראו + סימון כלא נקראו + סימון כלא נקראו - הצמד - הצמד - הצמד - הצמד + הצמדה + הצמדה + הצמדה + הצמדה - בטל הצמדה - בטל הצמדה - בטל הצמדה - בטל הצמדה + ביטול הצמדה + ביטול הצמדה + ביטול הצמדה + ביטול הצמדה - השתק - השתק - השתק - השתק + השתקה + השתקה + השתקה + השתקה - בטל השתקה - בטל השתקה - בטל השתקה - בטל השתקה + ביטול השתקה + ביטול השתקה + ביטול השתקה + ביטול השתקה - בחר + בחירה - ארכב - ארכב - ארכב - ארכב + העברה לארכיון + העברה לארכיון + העברה לארכיון + העברה לארכיון - בטל ארכוב - בטל ארכוב - בטל ארכוב - בטל ארכוב + הוצאה מהארכיון + הוצאה מהארכיון + הוצאה מהארכיון + הוצאה מהארכיון מחיקה @@ -554,7 +555,7 @@ מחיקה מחיקה - בחר הכול + בחירת הכל %1$d נבחר %1$d נבחרו @@ -582,6 +583,15 @@ +%1$d + + קישור מחדש של המכשירים שלך + + המכשירים שהוספת נותקו כשבוטלה ההרשמה של המכשיר שלך. צריך ללכת להגדרות כדי לקשר מחדש את המכשירים. + + פתיחת הגדרות + + מאוחר יותר + בחר חברי קבוצה @@ -1011,7 +1021,7 @@ יידע אותי לגבי אזכורים - לקבל התראות כאשר אתה מוזכר בהתכתבויות מושתקות? + לקבל התראות כשמזכירים אותך בצ׳אטים מושתקים? יידע אותי תמיד אל תיידע אותי @@ -1029,6 +1039,16 @@ שם משתמש נוצר שם משתמש הועתק + + לא הצלחנו למחוק שם משתמש. יש לנסות שוב מאוחר יותר. + + שם המשתמש נמחק + + + + משהו השתבש עם שם המשתמש שלך, הוא כבר לא מקושר לחשבון שלך. אפשר לנסות להגדיר אותו שוב או לבחור אחד חדש. + + תיקון כעת @@ -1252,8 +1272,8 @@ קבוצה חדשה הזמן חברים השתמש במסרון - מראה - הוסף תמונה + צבעי צ׳אט + הוספת תמונת פרופיל תשובות @@ -1584,14 +1604,14 @@ אשר המשך מחיקה - חסום - שחרר חסימה + חסימה + ביטול חסימה לאפשר אל %1$s לשלוח אליך הודעות? הצד השני לא יידע שראית את ההודעות שלו עד שלא תאשר. - לאפשר אל %1$s לשלוח אליך הודעות ולשתף את השם והתמונה שלך איתו/איתה? לא תקבל הודעות כלשהן עד שתשחרר חסימה שלו/שלה. + לאפשר ל%1$s לשלוח לך הודעות ולשתף איתו או איתה את השם והתמונה שלך? לא תוצג לך אף הודעה עד שהחסימה תבוטל. - לאפשר אל %1$s לשלוח לך הודעות? לא תקבל הודעות כלשהן עד שלא תבטל חסימה שלו/שלה. - האם לקבל עדכונים וחדשות מאת %1$s? לא תקבל עדכונים כלשהם עד שלא תבטל חסימה שלו/שלה. + לאפשר ל%1$s לשלוח לך הודעות? לא תוצג לך אף הודעה עד שהחסימה תבוטל. + לקבל עדכונים וחדשות מ%1$s? לא יוצג לך אף עדכון עד שהחסימה תבוטל. להמשיך את השיחה שלך עם קבוצה זו ולשתף את השם והתמונה שלך עם חברי הקבוצה? שדרג קבוצה זו כדי להפעיל מאפיינים חדשים כמו @אזכורים ומנהלנים. חברי קבוצה שלא שיתפו את השם או התמונה שלהם בקבוצה זו יוזמנו להצטרף. קבוצה מיושנת זו אינה יכולה עוד להיות בשימוש מאחר שהיא גדולה מדי. גודל הקבוצה המרבי הוא %1$d. @@ -1599,7 +1619,7 @@ להצטרף אל קבוצה זו ולשתף את השם והתמונה שלך עם חברי הקבוצה? חברי הקבוצה לא יידעו שראית את ההודעה שלהם עד שלא תאשר. להצטרף אל קבוצה זו ולשתף את השם והתמונה שלך עם חברי הקבוצה? לא תראה את ההודעות שלהם עד שלא תאשר. להצטרף אל קבוצה זו? הקבוצה לא תידע שראית את ההודעות שלה עד שתאשר. - לשחרר חסימת קבוצה זו ולשתף את השם והתמונה שלך עם חברי הקבוצה? לא תקבל הודעות כלשהן עד שתשחרר חסימה. + לבטל את החסימה של קבוצה זו ולשתף את השם והתמונה שלך עם חברי הקבוצה? לא תוצג לך אף הודעה עד שהחסימה תבוטל. הצג חבר קבוצה של %1$s @@ -1712,9 +1732,20 @@ צור PIN חדש + + שליחת קוד SMS + + הרשמה ל–Signal – עזרה עם PIN הרשמה מחדש עבור Android + + ה–PIN שלך הוא קוד בעל %1$d תווים או יותר שיצרת ויכול להיות מספרי או אלפא–נומרי.\n\nאם שכחת את ה–PIN שלך, אפשר ליצור אחד חדש. + + אם שכחת את ה–PIN שלך, אפשר ליצור אחד חדש. + + נגמרו לך ניחושי ה–PIN, אבל עדיין אפשר לגשת לחשבון Signal שלך באמצעות יצירת PIN חדש. + אזהרה - אם תשבית את ה־PIN, תאבד את כל הנתונים כאשר תירשם מחדש אל Signal אלא אם תגבה ותשחזר באופן ידני. אינך יכול להפעיל נעילת הרשמה בזמן שה־PIN מושבת. + השבתת ה–PIN תגרום לך לאבד את כל הנתונים בעת הרשמה מחדש ל–Signal, אלא אם נעשה גיבוי ושחזור באופן ידני. אי אפשר להפעיל נעילת הרשמה בזמן שה–PIN מושבת. השבת PIN @@ -1744,8 +1775,8 @@ הסטורי שלי - חסום - שחרר חסימה + חסימה + ביטול חסימה @@ -1849,9 +1880,9 @@ מצלמה - בטל השתקה + ביטול השתקה - השתק + השתקה צלצל @@ -1866,12 +1897,12 @@ - %1$s חסום/ה + חסמת את %1$s עוד מידע לא תקבל את השמע או הוידאו שלהם עד שהם לא יקבלו את שלך. לא ניתן לקבל שמע ווידאו מן %1$s לא ניתן לקבל שמע ווידאו מן %1$s - ייתכן שזה מאחר שהם לא וידאו את שינוי מספר הביטחון שלך, או שיש בעיה עם המכשיר שלהם, או שהם חסמו אותך. + יכול להיות שזה בגלל שהם לא אישרו את שינוי מספר הבטיחות שלך, או שיש בעיה עם המכשיר שלהם, או שהם חסמו אותך. החלק כדי להציג שיתוף מסך @@ -1908,11 +1939,18 @@ Signal צריך הרשאה אל אנשי הקשר והמדיה שלך כדי לעזור לך לחבור אל חברים ולשלוח הודעות. אנשי הקשר שלך מועלים ע״י שימוש בגילוי אנשי קשר פרטי של Signal, מה שאומר שהם מוצפנים מקצה־אל־קצה ואף פעם אינם גלויים אל השירות של Signal. Signal צריך הרשאה אל אנשי הקשר שלך כדי לעזור לך לחבור אל חברים. אנשי הקשר שלך מועלים ע״י שימוש בגילוי אנשי קשר פרטי של Signal, מה שאומר שהם מוצפנים מקצה־אל־קצה ואף פעם אינם גלויים אל השירות של Signal. עשית יותר מדי ניסיונות להירשם עם המספר הזה. אנא נסה שוב מאוחר יותר. + + עשית יותר מדי ניסיונות להירשם עם המספר הזה. יש לנסות שוב עוד %1$s. לא היה ניתן להתחבר אל השירות. אנא בדוק את חיבור האינטרנט ונסה שוב. תסדיר אי־תקני של מספר המספר שהכנסת (%1$s) כנראה בתסדיר בלתי תקני.\n\nהאם התכוונת אל %2$s? Molly Android - תסדיר מספר טלפון + שיחה התבקשה + + נשלחה בקשה ל–SMS + + נשלחה בקשה לקוד אימות אתה כעת במרחק צעד %1$d מהגשת יומן ניפוי תקלים. אתה כעת במרחק %1$d צעדים מהגשת יומן ניפוי תקלים. @@ -1934,6 +1972,16 @@ התקשר קוד אימות שליחת קוד מחדש + + נתקלת בבעיות בהרשמה? + + • לוודא שלטלפון שלך יש אות סלולרי שמאפשר לו לקבל את ה–SMS או השיחה\n • לוודא שיש לך אפשרות לקבל שיחת טלפון למספר\n • לבדוק שהכנסת את מספר הטלפון שלך נכון. + + למידע נוסף, יש לבצע את השלבים הבאים לפתרון תקלות או ליצור קשר עם התמיכה + + השלבים הבאים לפתרון תקלות + + יצירת קשר עם תמיכה להפעיל נעילת הרשמה? @@ -2100,6 +2148,10 @@ תשלום הודעה מתוזמנת + + היסטוריית ההודעות שלך מוזגה + + %1$s שייך ל%2$s עדכון Molly @@ -2174,7 +2226,7 @@ הודעת MMS הוצפנה עבור שיח בלתי־קיים - השתק התראות + השתקת התראות יבוא בתהליך @@ -2231,14 +2283,16 @@ מסרון בלתי מאובטח %1$s%2$s איש קשר - הגיב עם %1$s אל: \"%2$s\". - הגיב עם %1$s אל הסרטון שלך. - הגיב עם %1$s אל התמונה שלך. - הגיב %1$s אל ה־GIF שלך. - הגיב עם %1$s אל הקובץ שלך. - הגיב עם %1$s אל השמע שלך. - הגיב/ה עם %1$s אל המדיה לצפייה חד־פעמית שלך. - הגיב עם %1$s אל המדבקה שלך. + הגיב/ה %1$s ל: \"%2$s\". + הגיב/ה %1$s לסרטון שלך. + הגיב/ה %1$s לתמונה שלך. + הגיב/ה %1$s ל–GIF שלך. + הגיב/ה %1$s לקובץ שלך. + הגיב/ה %1$s להודעה הקולית שלך. + הגיב/ה %1$s למדיה לצפייה חד–פעמית שלך. + + הגיב/ה %1$s לתשלום שלך. + הגיב/ה %1$s לסטיקר שלך. הודעה זו נמחקה. לכבות התראות של איש קשר הצטרף אל Signal? אתה יכול לאפשר אותן שוב דרך Signal < הגדרות < התראות. @@ -2450,7 +2504,7 @@ אפשר התראות שיחה עדכן איש קשר - חסום בקשה + חסימת בקשה אין קבוצות במשותף. סקור בקשות בזהירות. אין אנשי קשר בקבוצה הזאת. סקור בקשות בזהירות. הצג @@ -2653,8 +2707,8 @@ סוגיית מסירה - הודעה, מדבקה, תגובה או אישור קריאה לא יכלו להימסר אליך מאת %1$s. הצד השני ניסה אולי לשלוח זאת ישירות אליך, או בקבוצה. - הודעה, מדבקה, תגובה או אישור קריאה לא יכלו להימסר אליך מאת %1$s. + לא ניתן היה למסור לך הודעה, סטיקר, תגובה או אישור קריאה מאת %1$s. יתכן שהצד השני ניסה לשלוח לך את זה ישירות, או בקבוצה. + לא ניתן היה למסור לך הודעה, סטיקר, תגובה או אישור קריאה מאת %1$s. שם פרטי (דרוש) @@ -2801,10 +2855,10 @@ השתמש בברירת מחדל השתמש במותאם אישית - השתק למשך שעה - השתק למשך 8 שעות - השתק למשך יום - השתק למשך 7 ימים + השתקה לשעה 1 + השתקה ל–8 שעות + השתקה ליום 1 + השתקה ל–7 ימים תמיד ברירת מחדל @@ -2843,9 +2897,9 @@ השתמש בתמונות של פנקס הכתובות הצג תמונות אנשי קשר מפנקס הכתובות אם זמינות - השארת התכתבויות מושתקות בארכיון + השארת צ׳אטים מושתקים בארכיון - התכתבויות מושתקות שמאוחסנות בארכיון ישארו בארכיון כשהודעה חדשה מגיעה. + צ׳אטים מושתקים שמאוחסנים בארכיון יישארו בארכיון כשהודעה חדשה מגיעה. חולל תצוגות מקדימות של קישורים אחזר תצוגות מקדימות של קישורים ישירות מאתרים עבור הודעות שאתה שולח. שנה משפט־סיסמה @@ -3139,7 +3193,7 @@ תשלום נשלח תשלום התקבל תשלום הושלם %1$s - חסום מספר + חסימת מספר העבר @@ -3224,7 +3278,7 @@ הודעה חדשה אל… - חסום משתמש + חסימת משתמש/ת הוסף לקבוצה @@ -3298,10 +3352,10 @@ - בטל השתקה + ביטול השתקה - השתק התראות + השתקת התראות הגדרות קבוצה @@ -3471,6 +3525,8 @@ הכנס את ה־PIN שלך הכנס את ה־PIN שיצרת עבור החשבון שלך. הוא שונה מקוד הווידוא שלך במסרון. + + צריך להזין את ה–PIN שיצרת עבור החשבון שלך. הכנס PIN אלפאנומרי הכנס PIN מספרי PIN שגוי. נסה שוב. @@ -3584,7 +3640,10 @@ הגיבוי שלך מכיל קובץ גדול מאוד שלא ניתן לגבות. צריך למחוק אותו וליצור גיבוי חדש. הקש כדי לנהל גיבויים. מספר שגוי? + תתקשרו אלי (%1$02d:%2$02d) + + שליחה מחדש של קוד (%1$02d:%2$02d) צור קשר עם תמיכת Signal הרשמת Signal - קוד וידוא עבור Android קוד שגוי @@ -3592,6 +3651,18 @@ בלתי ידוע ראה את מספר הטלפון שלי מצא אותי לפי מספר טלפון + + מספר טלפון + + אפשר לבחור מי יוכל לראות את מספר הטלפון שלך ומי יוכל ליצור איתך קשר באמצעותו ב–Molly. + + מי יכול לראות את המספר שלי + + אף אחד לא יראה את מספר הטלפון שלך ב–Molly + + מי יכול למצוא אותי לפי המספר שלי + + מספר הטלפון שלך יהיה גלוי לאנשים וקבוצות שיקבלו ממך הודעה. אנשים שיש להם את המספר שלך באנשי הקשר של הטלפון שלהם יראו אותו גם ב–Molly. כולם אנשי הקשר שלי אף אחד @@ -3748,8 +3819,8 @@ - חסום - שחרר חסימה + חסימה + ביטול חסימה הוסף לאנשי הקשר לא ניתן למצוא יישום שמסוגל לפתוח אנשי קשר. @@ -3801,9 +3872,9 @@ %1$s/%2$s - \"%1$s\" נחסם. - נכשל בחסימה של \"%1$s\" - חסימה של \"%1$s\" שוחררה. + \"%1$s\" נחסם/ה. + החסימה של של \"%1$s\" נכשלה + החסימה של \"%1$s\" בוטלה. סקור חברי קבוצה @@ -3834,7 +3905,7 @@ איש הקשר שלך הסרה מהקבוצה עדכן איש קשר - חסום + חסימה מחיקה שינה/שינתה לאחרונה את שם הפרופיל מן %1$s אל %2$s @@ -3860,8 +3931,8 @@ מחיקת החשבון שלך: הכנס את מספר הטלפון שלך מחיקת חשבון - מחיקת מידע החשבון שלך ותמונת הפרופיל שלך - מחיקת כל ההודעות שלך + תמחק את מידע החשבון שלך ותמונת הפרופיל שלך + תמחק את כל ההודעות שלך מחיקת %1$s בחשבון התשלומים שלך קוד מדינה לא צויין מספר לא צויין @@ -3976,12 +4047,12 @@ בטל הפעלת ארנק המאזן שלך - מומלץ שתעביר את הכספים שלך אל כתובת ארנק אחרת לפני ביטול הפעלת תשלומים. אם תבחר לא להעביר את הכספים שלך עכשיו, הם יישארו בארנק שמקושר אל Molly אם תפעיל מחדש תשלומים. + מומלץ להעביר את הכספים שלך לכתובת ארנק אחרת לפני ביטול ההפעלה של תשלומים. במקרה של בחירה לא להעביר את הכספים עכשיו, הם יישארו בארנק שמקושר אל Molly בעת הפעלה מחדש של תשלומים. העבר מאזן נותר בטל הפעלה בלי להעביר בטל הפעלה לבטל הפעלה בלי להעביר? - המאזן שלך יישאר בארנק שמקושר אל Molly אם תבחר להפעיל מחדש תשלומים. + היתרה שלך תישאר בארנק שמקושר אל Molly במקרה של בחירה להפעיל מחדש את תשלומים. שגיאה בביטול הפעלת ארנק. @@ -4197,12 +4268,12 @@ צור פרופיל - חסום + חסום/ה %1$d אנשי קשר תכתובת הודעות נעלמות אבטחת יישום - חסום צילומי מסך ברשימת האחרונים ובתוך היישום + חסימת צילומי מסך ברשימת השיחות האחרונות ובתוך האפליקציה הודעות ושיחות של Signal, ממסר שיחות תמיד, ושולח אטום קוצב זמן ברירת מחדל עבור התכתבויות חדשות הגדר קוצב זמן ברירת מחדל של הודעות נעלמות עבור כל ההתכתבויות החדשות שהותחלו על ידך. @@ -4304,7 +4375,7 @@ לא עכשיו - התאם אישית תגובות + התאמה אישית של תגובות הקש כדי להחליף אימוג’י אפס שמור @@ -4363,9 +4434,9 @@ התקשר - השתק + השתקה - מושתק + מושתק/ת חפש הודעות נעלמות @@ -4373,10 +4444,10 @@ פרטי איש קשר הצג מספר ביטחון - חוסם - חסום קבוצה - שחרר חסימה - שחרר חסימת קבוצה + חסימה + חסימת קבוצה + ביטול חסימה + ביטול חסימת קבוצה הוסף אל קבוצה ראה הכול הוסף חברי קבוצה @@ -4384,9 +4455,9 @@ בקשות והזמנות קישור קבוצה הוסף כאיש קשר - בטל השתקה - שיחות מושתקות עד %1$s - שיחות מושתקות לתמיד + ביטול השתקה + שיחה מושתקת עד %1$s + שיחה מושתקת לתמיד מספר טלפון הועתק אל לוח הגזירה. מספר טלפון משיגים תגים לפרופיל שלך באמצעות תמיכה ב–Signal. אפשר ללחוץ על תג למידע נוסף. @@ -4402,8 +4473,8 @@ מי יכול לשלוח הודעות? - השתק התראות - לא מושתק + השתקת התראות + לא מושתק/ת אזכורים יידע תמיד אל תיידע @@ -4432,7 +4503,7 @@ הסרה - חסום + חסימה להסיר את %1$s? @@ -4440,7 +4511,7 @@ %1$s הוסר/ה - %1$s נחסם/ה + חסמת את %1$s לא הצלחנו להסיר את %1$s @@ -4536,7 +4607,7 @@ הוספה לסטורי הוסף הודעה הוסף תשובה - שלח אל + שליחה אל הודעה לצפייה חד־פעמית פריט אחד או יותר היו יותר מדי גדולים פריט אחד או יותר היו בלתי תקפים @@ -4714,7 +4785,7 @@ לא הצלחנו לשלוח את התרומה שלך בגלל שגיאת רשת. כדאי לבדוק את החיבור ולנסות שוב. - תרומה ל%1$s + תרומה בשם %1$s %1$s תרם/ה ל–Signal בשמך @@ -5125,7 +5196,7 @@ תשובות ותגובות - התר תשובות ותגובות + הפעלת תשובות ותגובות אפשר לתת לאנשים שיכולים לצפות בסטורי שלך להגיב ולהשיב @@ -5311,7 +5382,7 @@ אישור תרומה - שלח אל + שליחה אל נעדכן את הנמען/ת על התרומה בהודעת 1–על–1. אפשר להוסיף הודעה משלך למטה. @@ -5564,7 +5635,7 @@ מייצאים הודעות SMS - זה עשוי לקחת זמן מה + זה עשוי לקחת זמן מייצאים %1$d מתוך %2$d… @@ -5851,5 +5922,15 @@ מחיקת שם משתמש + + + ש׳ + + דק׳ + + הגדרה + + הזמן המינימלי לפני הפעלת נעילת המסך הוא דקה 1. + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e89d4ec89f..947e595885 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,6 +14,7 @@ + はい いいえ @@ -318,7 +319,7 @@ Signalメッセージ メッセージアプリをMollyにしましょう %1$s 連絡先を選択してください - ブロックを解除します + ブロック解除 添付ファイルのサイズが、送信するメッセージタイプに応じた制限を超えています。 録音できません! メンバーではなくなったので、このグループにメッセージを送ることはできません。 @@ -449,7 +450,7 @@ キャンセル - ブロック中のユーザー + ブロック済 検索条件を解除する @@ -476,28 +477,28 @@ 未読にする - ピン留め + ピン留めする - ピン留め解除 + ピン留めを解除する - ミュート + ミュートする - ミュート解除 + ミュートを解除する - 選択 + 選択する - アーカイブ + アーカイブする - アーカイブ解除 + アーカイブを解除する - 消去 + 消去する - すべて選択 + すべて選択する %1$d件選択済み @@ -522,6 +523,15 @@ +%1$d + + 端末を再リンクしてください + + 端末の登録解除時に、追加した端末のリンクが解除されました。設定に移動して、任意の端末を再リンクしてください。 + + 設定を開く + + あとで + メンバーの選択 @@ -915,6 +925,16 @@ ユーザー名を作成しました ユーザー名をコピーしました + + ユーザーネームを削除できませんでした。あとで再度試してください。 + + ユーザーネームを削除しました + + + + ユーザーネームに問題が発生したため、アカウントに割り当てられていません。もう一度設定し直すか、新しいものを選んでみてください。 + + 今すぐ修正する @@ -1108,8 +1128,8 @@ 新規グループ 友達を招待しましょう SMSを使用する - デザイン - 画像を追加 + チャットの色 + プロフィール画像を追加しよう 返信 @@ -1520,6 +1540,17 @@ 新しいPINを作成 + + SMSコードを送信する + + Signalの登録 - AndroidでPINの再登録に関するサポートが必要です + + あなたのPINは %1$d桁以上のコードで、数字または英数字のいずれかです。\n\nPIN を忘れた場合は、新しい PIN を作成できます。 + + PIN を忘れた場合は、新しい PIN を作成できます。 + + 思いつくPINの候補がなくなっても、新しいPINを作成することで、Signalアカウントにアクセスすることができます。 + ご注意ください PINを無効にすると、自身でバックアップと復元を行わない限り、Signalに再登録する際すべてのデータが失われます。PINを無効にしている間は登録ロックは使用できません。 @@ -1553,7 +1584,7 @@ ブロックする - ブロックを解除する + ブロック解除 @@ -1698,11 +1729,18 @@ Signalは、ご友人とのつながりやメッセージ送信を支えるため、連絡先とメディアへのアクセス許可が必要です。連絡先はエンドツーエンドで暗号化し、Signalプライベートコンタクトディスカバリーを使用してアップロードされるため、Signalのサービス管理者は閲覧できません。 Signalは、ご友人とのつながりを支えるため、連絡先へのアクセス許可が必要です。連絡先はエンドツーエンドで暗号化し、Signalプライベートコンタクトディスカバリーを使用してアップロードされるため、Signalのサービス管理者は閲覧できません。 この番号の登録試行回数が多すぎます。あとで再度試してください。 + + この番号の登録試行回数が多すぎます。%1$s分後にもう一度お試しください。. サービスに接続できませんでした。ネットワークの接続を確認してから、再度試してください。 非標準の数字フォーマット あなたが入力した数字 (%1$s) は、非標準のフォーマットのようです。\n\n%2$s を意図していすか? Molly Android - 電話番号フォーマット + 通話を要求されました + + SMS が要求されました + + 認証コードが要求されました デバッグログの提出まで、あと%1$dステップです。 @@ -1721,6 +1759,16 @@ 通話 認証コード コードを再送信する + + 登録でお困りですか? + + • お使いの携帯電話が圏内にあってSMSまたは通話が可能かどうかを確認してください\n • 電話番号への着信が可能か確認してください\n • 電話番号が正しく入力されていることを確認してください。 + + 詳細については、これらのトラブルシューティングの手順に従うか、サポートにお問い合わせください + + これらのトラブルシューティングの手順 + + サポートに問い合わせる 登録ロックを有効にしますか? @@ -1880,13 +1928,17 @@ バッジに引き換えました - あなたのストーリーにリアクション%1$sしました + あなたのストーリーに%1$sのリアクションがありました。 - 彼らのストーリー%1$sにリアクションしました + この人のストーリーに%1$sのリアクションをしました 支払い 予約メッセージ + + メッセージ履歴が統合されました + + %1$s は %2$s の番号です Mollyアップデート @@ -2015,14 +2067,16 @@ 安全でないSMS %1$s %2$s 連絡先 - 「%2$s」に %1$s - あなたのビデオに %1$s - あなたの画像に %1$s - あなたのGIFに %1$s - あなたのファイルに %1$s - あなたの音声に %1$s - あなたの使い捨てメディアに %1$s - あなたのステッカーに %1$s + 「%2$s」に %1$sのリアクションをしました。 + あなたの動画に %1$sのリアクションがありました。 + あなたの画像に %1$sのリアクションがありました。 + あなたのGIFに %1$sのリアクションがありました。 + あなたのファイルに %1$sのリアクションがありました。 + あなたの音声に %1$sのリアクションがありました。 + あなたの使い捨てメディアに %1$sのリアクションがありました。 + + あなたの支払いに %1$s のリアクションがありました。 + あなたのステッカーに %1$sのリアクションがありました。 このメッセージは消去されました。 連絡先のSignal参加時の通知を受け取らないようにしますか?この設定はあとでSignal > 設定 > 通知から元に戻せます。 @@ -2404,7 +2458,7 @@ 配送エラー - %1$s からのメッセージ、ステッカー、リアクションまたは既読通知を、あなたに配送できませんでした。直接またはグループで、送信しようとしているかもしれません。 + %1$s からのメッセージ、ステッカー、リアクションまたは既読通知をあなたに配送できませんでした。直接送信を試みたか、グループ内で送ろうとしたかもしれません。 %1$s からのメッセージ、ステッカー、リアクションまたは既読通知を、あなたに配送できませんでした。 @@ -2472,7 +2526,7 @@ 保留中 - 宛先 + 送信済み 送信元 配送先 既読者 @@ -2552,10 +2606,10 @@ 既定を使う カスタムを使う - 1時間 - 8時間 - 1日間 - 7日間 + 1時間ミュート + 8時間ミュート + 1日間ミュート + 7日間ミュート 常時 既定の設定 @@ -3043,7 +3097,7 @@ - ミュートを解除 + ミュート解除 通知をミュート @@ -3207,6 +3261,8 @@ PINを入力してください あなたのアカウント用に作成したPINを入力してください。これはSMSの認証コードとは違います。 + + アカウント用に作成した PIN を入力してください。 英数字のPINを入力 数字のPINを入力 PINが違います。再度試してください。 @@ -3305,7 +3361,10 @@ 非常に大きなファイルが含まれているためバックアップできません。そのファイルを消去して、新しいバックアップを作成してください。 タップしてバックアップを管理 電話番号を訂正 + 電話で確認 (%1$02d:%2$02d) + + コードの再送信まで (%1$02d:%2$02d) Signalサポートに問い合わせ Signal登録 - Android用認証コード コードが違います @@ -3313,6 +3372,18 @@ 不明 私の電話番号を確認できる人 電話番号で私を検索できる人 + + 電話番号 + + あなたの電話番号を見ることができる人と、Mollyであなたに連絡できる人を選択してください。 + + 電話番号を見ることができる人 + + あなたの電話番号は、誰のMollyにも表示されません + + 私を電話番号から検索できる人 + + あなたの電話番号は、メッセージを送信したすべての相手とグループに表示されます。連絡先にあなたの電話番号がある人にも、Mollyで表示されます。 全員 自分の連絡先 なし @@ -3572,7 +3643,7 @@ Wi-Fi強度が弱いのでモバイルデータ通信に切り替えます。 - アカウントを削除すると: + アカウントを消去すると: あなたの電話番号を入力してください アカウントを消去する あなたのアカウント情報とプロフィール画像を消去します @@ -3906,7 +3977,7 @@ プロファイルを作成 - ブロック中のユーザー + ブロック済 %1$d件 メッセージ送受信 消えるメッセージ @@ -4078,7 +4149,7 @@ 安全番号の検証 ブロック グループをブロック - ブロックを解除 + ブロック解除 グループのブロックを解除 グループへ追加 すべて表示 @@ -4087,7 +4158,7 @@ 申請と招待 グループリンク 連絡先に追加 - ミュートを解除 + ミュート解除 チャットは%1$sまでミュートされます チャットは常にミュートされます 電話番号をクリップボードにコピーしました。 @@ -4143,7 +4214,7 @@ %1$s さんは削除されています - %1$s さんはブロックされています + %1$s はブロックされました %1$s さんを削除できません @@ -4405,7 +4476,7 @@ ネットワークエラーのため寄付を送信できませんでした。インターネット接続を確認して再度試してください。 - %1$sへの寄付 + %1$sに代わって寄付をする %1$s があなたに代わってSignalに寄付しました @@ -4747,13 +4818,13 @@ このグループのメンバーではなくなったので、このストーリーに返信することはできません。 - ストーリーにリアクションがありました + ストーリーにリアクションしました 閲覧 返信 - ストーリーにリアクション + ストーリーにリアクションする %1$s に個人的に返信しています @@ -4949,9 +5020,9 @@ %1$s のストーリーにリアクションしました - あなたのストーリーにリアクションされました + あなたのストーリーにリアクションがありました - ストーリーにリアクションがありました + ストーリーにリアクションしました @@ -5201,7 +5272,7 @@ SMSメッセージをエクスポートしています - しばらく掛かるかも知れません + 少し時間がかかるかも知れません %2$d 件中 %1$d 件をエクスポートしています… @@ -5476,5 +5547,15 @@ ユーザーネームの消去 + + + 時間 + + + + 設定する + + 画面ロックが作動するまでの最短時間は 1 分です。 + diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 80019ee0b8..7b150e17b0 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -14,13 +14,14 @@ + დიახ არა წაშლა გთხოვთ დაელოდოთ… შენახვა - შენიშვნა საკუთარ თავს + ჩემი ჩანიშვნები @@ -98,7 +99,7 @@ დაბლოკილი მომხმარებლები დაბლოკილი მომხმარებლის დამატება - დაბლოკილ მომხმარებლებს არ ექნებათ შესაძლებლობა დაგირეკონ ან გამოგიგზავნონ შეტყობინებები. + დაბლოკილ მომხმარებლებს არ ექნებათ შესაძლებლობა დაგირეკონ ან მოგწერონ. დაბლოკილი მომხმარებლები ვერ მოიძებნა დავბლოკოთ მომხმარებელი? \"%1$s\" ვერ შეძლებს დარეკვას და მოწერას. @@ -147,16 +148,16 @@ თქვენ შეძლებთ ერთმანეთთან მიმოწერას და დარეკვას, და შენი სახლი და სურათი იქნება მათთან გაზიარებული. ერთმანეთთან მიმოწერას შეძლებთ. - ბლოკირებული ადამიანები ვეღარ შეძლებენ შენთან დარეკვას ან შეტყობინების გამოგზავნას. + ბლოკირებული ადამიანები ვეღარ შეძლებენ შენთან დარეკვას ან მოწერას. დაბლოკილი ხალხი ვერ მოგწერს. - დაბლოკე Signal-ის სიახლეები. + Signal-ის განახლებების და სიახლეების მიღების დაბლოკვა. განაგრძე Signal-ის სიახლეების მიღება. განვბლოკოთ %1$s? დაბლოკვა - დაბლოკვა და გასვლა - სპამის დარეპორტება და დაბლოკვა + დაბლოკვა და დატოვება + სპამად დაფიქსირება და დაბლოკვა დღეს @@ -366,7 +367,7 @@ მედია ფაილის გაგზავნისას შეცდომა მოხდა - დარეპორტდა როგორც სპამი და დაიბლოკა. + დაფიქსირდა როგორც სპამი და დაიბლოკა. SMS მიმოწერები ამ ეტაპზე გამორთულია. შეგიძლია შენი შეტყობინებები შენს ტელეფონში არსებულ სხვა აპში გადაიტანო. @@ -542,6 +543,15 @@ +%1$d + + თავიდან მიაბი შენი მოწყობილობები + + შენ მიერ დამატებული მოწყობილობები მას შემდეგ აღარ არის მიბმული, რაც შენი მოწყობილობის რეგისტრაცია გააუქმე. მოწყობილობების ახლიდან მისაბმელად გადადი პარამეტრებში. + + პარამეტრებში შესვლა + + მოგვიანებით + წევრების არჩევა @@ -953,6 +963,16 @@ მომხმარებლის სახელი შეიქმნა მომხმარებლის სახელი დაკოპირდა + + მომხმარებლის სახელის წაშლა ვერ მოხერხდა. მოგვიანებით სცადე. + + მომხმარებლის სახელი წაიშალა + + + + შენს მომხმარებლის სახელს რაღაც მოუვიდა, ის შენს მონაცემებთან აღარ არის დაკავშირებული. შეგიძლია სცადო და ის ახლიდან დააყენო, ან ახალი შეარჩიო. + + ახლა მოგვარება @@ -1156,8 +1176,8 @@ ახალი ჯგუფი მოიწვიე მეგობრები SMS-ის გამოყენება - ვიზუალი - ფოტოს დამატება + ჩატის ფერები + პროფილის ფოტოს დამატება პასუხები @@ -1472,9 +1492,9 @@ განბლოკვა მივცეთ უფლება %1$s-ს მოგწეროს და შენი სახელი და ფოტო გავუზიაროთ? ის ვერ გაიგებს, რომ მისი შეტყობინება ნახე, სანამ არ დაეთანხმები. - მივცეთ უფლება %1$s-ს მოგწეროს და შენი სახელი და ფოტო გავუზიაროთ? შეტყობინებებს არ მიიღებ, სანამ მას არ განბლოკავ. + მივცეთ უფლება %1$s-ს მოგწეროს და შენი სახელი და ფოტო გავუზიაროთ? წერილებს არ მიიღებ, სანამ მას არ განბლოკავ. - მივცეთ უფლება %1$s-ს მოგწეროს? შეტყობინებებს არ მიიღებ, სანამ მას არ განბლოკავ. + მივცეთ უფლება %1$s-ს მოგწეროს? წერილებს არ მიიღებ, სანამ მას არ განბლოკავ. გსურს შეიტყო სიახლეები %1$s-ის შესახებ? განახლებებს არ მიიღებ, სანამ მას არ განბლოკავ. გსურს გააგრძელო მიმოწერა ამ ჯგუფთან და გაუზიარო შენი სახელი და ფოტო მის წევრებს? განაახლე ეს ჯგუფი ახალი ფუნქციების, მაგალითად @მონიშვნებისა და ადმინების გასააქტიურებლად. წევრები, რომლებსაც არ გაუზიარებიათ თავიანთი სახელი ან ფოტო ამ ჯგუფში, მოწვეულნი იქნებიან გასაწევრიანებლად. @@ -1483,7 +1503,7 @@ გსურს შეუერთდე ამ ჯგუფს და გაუზიარო შენი სახელი და ფოტო მის წევრებს? ისინი ვერ გაიგებენ, რომ მათი შეტყობინებები ნახე, სანამ არ დაეთანხმები. გსურს შეუერთდე ამ ჯგუფს და გაუზიარო შენი სახელი და ფოტო მის წევრებს? მათ შეტყობინებებს ვერ ნახავ, სანამ არ დაეთანხმები. გსურს შეუერთდე ამ ჯგუფს? ისინი ვერ გაიგებენ, რომ მათი შეტყობინებები ნახე, სანამ არ დაეთანხმები. - გსურს განბლოკო ეს ჯგუფი და გაუზიარო შენი სახელი და ფოტო მის წევრებს? შეტყობინებებს არ მიიღებ, სანამ მას არ განბლოკავ. + გსურს განბლოკო ეს ჯგუფი და გაუზიარო შენი სახელი და ფოტო მის წევრებს? წერილებს არ მიიღებ, სანამ მას არ განბლოკავ. View %1$s-ის წევრი @@ -1584,9 +1604,20 @@ შექმენი ახალი პინ-კოდი + + SMS კოდის გამოგზავნა + + რეგისტრაცია Signal-ზე - მესაჭიროება დახმარება ანდროიდზე პინ-კოდის თავიდან დასარეგისტრირებლად + + შენი პინ-კოდი %1$d+ ციფრიანი კოდია, რომელიც შეიძლება იყოს ციფრული, ან ანბანურ-ციფრული.\n\nთუ შენს პინ-კოდს ვერ იხსენებ, შეგიძლია ახალი შექმნა. + + თუ შენს პინ-კოდს ვერ იხსენებ, შეგიძლია ახალი შექმნა. + + პინ-კოდის გამოცნობის ცდები ამოგეწურა, მაგრამ შენი Signal-ის მონაცემებზე წვდომა მაინც გექნება ახალი პინ-კოდის შექმნით. + გაფრთხილება - თუ პინ-კოდს გამორთავ, Signal-ზე ხელახლა დარეგისტრირებისას ყველა მონაცემს დაკარგავ, თუ ხელით არ შექმნი სარეზერვო კოპიას და აღადგენ. რეგისტრაციის დაბლოკვას ვერ ჩართავ, სანამ პინ-კოდი გამორთულია. + თუ პინ-კოდს გამორთავ, Signal-ზე ხელახლა დარეგისტრირებისას ყველა მონაცემს დაკარგავ, თუ ხელით არ შექმნი სარეზერვო ასლს და აღადგენ. რეგისტრაციის დაბლოკვას ვერ ჩართავ, სანამ პინ-კოდი გამორთულია. პინ-კოდის გამორთვა @@ -1731,7 +1762,7 @@ მათ აუდიოს ან ვიდეოს არ მიიღებ და ის არ მიიღებს შენსას. აუდიოს & ვიდეოს მიღება %1$s-სგან შეუძლებელია აუდიოსა და ვიდეოს მიღება %1$s-სგან შეუძლებელია - ეს შეიძლება იმის გამო იყოს, რომ მან არ დაავერიფიცირა შენი უსაფრთხოების ნომრის ცვლილება, მის მოწყობილობას პრობლემა აქვს ან დაგბლოკა. + ეს შეიძლება იმით იყოს გამოწვეული, რომ მან არ დაავერიფიცირა შენი უსაფრთხოების ნომრის ცვლილება, მის მოწყობილობას პრობლემა აქვს ან დაგბლოკა. გადაწიე ეკრანის გაზიარების სანახავად @@ -1768,11 +1799,18 @@ მეგობრებთან დაკავშირების დასახმარებლად და შეტყობინებების გასაგზავნად, Signal-ს კონტაქტებსა და მედიაზე წვდომის ნებართვა სჭირდება. შენი კონტაქტები იტვირთება Signal-ის პირადი კონტაქტის აღმოჩენის საშუალებით, რაც ნიშნავს, რომ ისინი ბოლომდე დაშიფრულია და არასოდეს ჩანს Signal-ის სერვისისთვის. მეგობრებთან დაკავშირების დასახმარებლად Signal-ს კონტაქტებზე წვდომის ნებართვა სჭირდება. შენი კონტაქტები იტვირთება Signal-ის პირადი კონტაქტის აღმოჩენის საშუალებით, რაც ნიშნავს, რომ ისინი ბოლომდე დაშიფრულია და არასოდეს ჩანს Signal-ის სერვისისთვის. თქვენ მოახდინეთ ძალიან ბევრი მცდელობა ამ ნომრის დარეგისტრირებისთვის. გთხოვთ ხელახლა სცადოთ მოგვიანებით. + + ამ ნომრის დარეგისტრირება ზედმეტად ბევრჯერ სცადე. გთხოვთ, %1$s-ში თავიდან სცადო. სერვისთან დაკავშირება შეუძლებელია. გთხოვთ, შეამოწმოთ ქსელის კავშირი და ხელახლა სცადოთ. ნომრის არასტანდარტული ფორმატი შენ მიერ შეყვანილი ნომერი (%1$s), როგორც ჩანს, არასტანდარტული ფორმატისაა. %2$s-ს გულისხმობდი? Molly Android-ი - ტელეფონის ნომრის ფორმატი + დარეკვა მოთხოვნილია + + SMS-ი მოთხოვნილია + + ვერიფიკაციის კოდი მოთხოვნილია ახლა გაშორებთ %1$d ნაბიჯი პრობლემების მოგვარების ჟურნალის გაგზავნიდან. ახლა %1$d ნაბიჯი გაშორებს გაუმართაობის რეესტრის გაგზავნისგან. @@ -1792,6 +1830,16 @@ დარეკვა ვერიფიკაციის კოდი კოდის თავიდან გაგზავნა + + ვერ რეგისტრირდები? + + • დარწმუნდი, რომ შენს ტელეფონს SMS-ის ან ზარის მისაღებად ქსელის კავშირი აქვს\n • დაადასტურე, რომ შეგიძლია ნომერზე სატელეფონო ზარი მიიღო\n • შეამოწმე, რომ სწორად შეიყვანე შენი ტელეფონის ნომერი. + + მეტი ინფორმაციისთვის, გთხოვთ, მიჰყვე პრობლემების მოგვარების ამ ნაბიჯებს ან მხარდაჭერის გუნდს დაუკავშირდე + + პრობლემების მოგვარების ამ ნაბიჯებს + + მხარდაჭერის გუნდთან დაკავშირება ჩავრთოთ რეგისტრაციის დაბლოკვა? @@ -1958,6 +2006,10 @@ ტრანზაქცია გადავადებული შეტყობინება + + შენი მიმოწოერის ისტორია გაერთიანდა + + %1$s %2$s-ს ეკუთვნის Molly-ის განახლება @@ -2087,13 +2139,15 @@ დაუცველი SMS-ი %1$s %2$s კონტაქტი - გამოეხმაურა %1$s-ს: \"%2$s\". + გამოეხმაურა %1$s: \"%2$s-ს\". გამოეხმაურა %1$s შენს ვიდეოს. გამოეხმაურა %1$s შენს სურათს. გამოეხმაურა %1$s შენს GIF-ს. გამოეხმაურა %1$s შენს ფაილს. გამოეხმაურა %1$s შენს აუდიოს. - გამოეხმაურა %1$s შენს ერთჯერად მედია-ფაილს. + გამოეხმაურა %1$s შენს ერთხელ სანახავ მედია-ფაილს. + + Reacted %1$s to your payment. გამოეხმაურა %1$s შენს სტიკერს. ეს შეტყობინება იქნა წაშლილი. @@ -2487,8 +2541,8 @@ გაგზავნის პრობლემა - შეტყობინება, სტიკერი, რეაქცია ან წაკითვის დასტური ვერ გამოგეგზავნება %1$s -სგან. შესაძლოა მან გამოგზავნა პირდაპირ, ან ჯგუფში სცადა. - შეტყობინება, სტიკერი, რეაქცია ან წაკითხვის დასტური ვერ გამოგეგზავნება %1$s -სგან. + %1$s -სგან შეტყობინება, სტიკერი, რეაქცია ან წაკითხვის დასტური ვერ გამოგეგზავნება. შესაძლოა მან გამოგზავნა პირდაპირ, ან ჯგუფში სცადა. + %1$s -სგან შეტყობინება, სტიკერი, რეაქცია ან წაკითხვის დასტური ვერ გამოგეგზავნება. სახელი (სავალდებულო) @@ -2677,7 +2731,7 @@ შეინახე დადუმებული ჩატები არქივში - ახალი შეტყობინების მიღებისას, დაარქივებული დადუმებული ჩატები არქივში დარჩება. + ახალი წერილის მიღებისას, დაარქივებული დადუმებული ჩატები არქივში დარჩება. ბმულსი წინასწარი ნახვის გენერირება მიიღე ბმულის წინასწარი ნახვა შენს მიერ გაგზავნილი შეტყობინებებისთვის პირდაპირ ვებსაიტებიდან. პაროლ-ფრაზის შეცვლა @@ -3295,6 +3349,8 @@ შეიყვანე შენი პინ-კოდი შეიყვანე შენი მონაცემებისთვის შექმნილი პინ-კოდი. ის განსხვავდება შენი SMS ვერიფიკაციის კოდისგან. + + შეიყვანე შენი ანგარიშისთვის შექმნილი პინ-კოდი. შეიყვანე ანბანურ-ციფრული პინ-კოდი შეიყვანე ციფრული პინ-კოდი არასწორი პინ-კოდი. ხელახლა სცადე. @@ -3398,7 +3454,10 @@ შენი სარეზერვო კოპია შეიცავს ძალიან დიდ ფაილს, რომლის კოპირებაც ვერ ხერხდება. გთხოვთ წაშალო ის და ახალი სარეზერვო კოპია შექმნა. დააჭირე სარეზერვო კოპიების სამართავად. ნომერი არასწორია? + დამირეკეთ (%1$02d:%2$02d) + + კოდის თავიდან გაგზავნა (%1$02d:%2$02d) დაუკავშირდით Signal-ის მხარდაჭერას Signal-ის რეგისტრაცია - დადასტურების კოდი Android-ითვის არასწორი კოდი @@ -3406,6 +3465,18 @@ უცნობი ჩემი მობილურის ნომრის ნახვა ჩემი პოვნა მოვილურის ნომრით + + ტელეფონის ნომერი + + შეარჩიე, ვის შეუძლია ნახოს შენი ტელეფონის ნომერი და ვინ შეიძლება დაგიკავშირდეს Molly-ზე მისი დახმარებით. + + ვის შეუძლია ჩემი ნომრის ნახვა + + Molly-ზე შენი ტელეფონის ნომერს ვერავინ ნახავს + + ვის შეუძლია ნომრით მიპოვოს + + შენი ტელეფონის ნომერი ხილული იქნება ყველა ადამიანისა და ჯგუფისთვის, ვისთანაც მიმოწერა გაქვს. ადამიანები, რომლებსაც შენი ნომერი სატელეფონო კონტაქტებში აქვთ, მას Molly-ზეც ნახავენ. ყველა ჩემი კონტაქტები არავინ @@ -3616,7 +3687,7 @@ %1$s/%2$s \"%1$s\" დაბლოკილია. - \"%1$s\"-ის დაბლოკვა ვერ მოხერხდა. + \"%1$s\"-ის დაბლოკვა ვერ მოხერხდა \"%1$s\" განბლოკილია. @@ -3667,17 +3738,17 @@ Wi-Fi კავშირი სუსტია. გადაირთო მობილურის ინტერნეტზე. - შენი მონაცემების წაშლა: + შენი ანგარიშის წაშლით: შეიყვანე შენი ტელეფონის ნომერი მონაცემების წაშლა - წაშლის შენს მონაცემებს და პროფილის ფოტოს - ყველა შენი შეტყობინების წაშალა + წაიშლება შენი მონაცემები და პროფილის ფოტოს + წაიშლება ყველა შენი შეტყობინება წაშლის %1$s შენი ტრანზაქციების მონაცემებიდან ქვეყნის კოდი არ არის მითითებული ნომერი არ არის მითითებული შეყვანილი მობილურის ნომერი არ ემთხვევა შენს მონაცემებში აღნიშნულს. დარწმუნებული ხარ, რომ შენი მონაცემების წაშლა გსურს? - ეს წაშლის Signal-ის შენ მონაცემებს და განაახლებს აპლიკაციას. პროცესის დასრულების შემდეგ აპლიკაცია დაიხურება. + ეს წაშლის Signal-ის შენს მონაცემებს და განაახლებს აპლიკაციას. პროცესის დასრულების შემდეგ აპლიკაცია დაიხურება. ადგილობრივი მონაცემების წაშლა ვერ მოხერხდა. შეგიძლია ისინი ხელით წაშალო სისტემის აპლიკაციის პარამეტრებში. აპის პარამეტრების გაშვება @@ -3784,12 +3855,12 @@ საფულის დეაქტივაცია შენი ბალანსი - გადახდების დეაქტივაციამდე რეკომენდებულია შენი თანხების სხვა საფულის მისამართზე გადარიცხვა. თუ გადაწყვეტ, რომ შენი თანხები ახლა არ გადარიცხო, ისინი დარჩება Molly-თან დაკავშირებულ შენს საფულეში, თუ ხელახლა გააქტიურებთ გადახდებს. + გადახდების დეაქტივაციამდე რეკომენდებულია, შენი თანხები სხვა საფულის მისამართზე გადარიცხო. თუ გადაწყვეტ, რომ შენი თანხები ახლა არ გადარიცხო, ისინი დარჩება Molly-თან დაკავშირებულ შენს საფულეში, თუ გადახდებს ხელახლა გააქტიურებ. დარჩენილი ბალანსის გადატანა დეაქტივაცია გადატანის გარეშე დეაქტივაცია გსურს გადატანის გარეშე დეაქტივაცია? - შენი ბალანსი დარჩება შენს საფულეში, რომელიც დაკავშირებულია Molly-თან, თუ ტრანზაქციების ხელახლა გააქტიურებას გადაწყვეტ. + თუ ტრანზაქციების ხელახლა გააქტიურებას გადაწყვეტ, შენი ბალანსი შენს საფულეში დარჩება, რომელიც Molly-თანაა დაკავშირებული. საფულის დეაქტივაციისას ხარვეზი მოხდა. @@ -4187,8 +4258,8 @@ ჯგუფის ბმული კონტაქტად დამატება მდუმარე რეჟიმის გამორთვა - მიწერმოწერა დადუმებულია %1$s-მდე - მიწერმოწერა დადუმებულია სამუდამოდ + მიმოწერა დადუმებულია %1$s-მდე + მიმოწერა დადუმებულია სამუდამოდ მობილურის ნომერი ბუფერში დაკოპირდა. ტელეფონის ნომერი მიიღე ემბლემები შენი პროფილისთვის Signal-ის მხარდაჭერით. მეტი ინფორმაციისთვის დააჭირე ემბლემას. @@ -4242,7 +4313,7 @@ %1$s წაიშალა - %1$s დაიბლოკა + %1$s დაბლოკილია %1$s-ის წაშლა ვერ მოხერხდა @@ -4453,7 +4524,7 @@ ყოველთვიური დონაცია გაუქმებულია შენი ბუსტის ემბლემას ვადა ამოეწურა და შენს პროფილზე ის აღარ ჩანს. - ერთჯერადი კონტრიბუციით შეგიძლია ბუსტის ემბლემა დამატებით 30 დღით გააგრძელო. + შეგიძლია ერთჯერადი კონტრიბუციით ბუსტის ემბლემა დამატებით 30 დღით გააგრძელო. შეგიძლია Signal-ის გამოყენება გააგრძელო, მაგრამ შენთვის შექმნილი ტექნოლოგიის დასახმარებლად, შენ გაქვს შესაძლებლობა მისი მხარდამჭერი გახდე ყოველთვიური დონაციების გაკეთებით. გახდი მხარდამჭერი @@ -4465,7 +4536,7 @@ შენი განმეორებადი ყოველთვიური დონაცია გაუქმდა, რადგან შენი ტრანზაქცია ვერ დავამუშავეთ. შენი ემბლემა აღარ ჩანს შენს პროფილზე. შენი განმეორებადი ყოველთვიური დონაცია გაუქმდა. %1$s შენი %2$s ემბლემა აღარ ჩანს შენს პროფილზე. - შეგიძლია გააგრძელო Signal-ის გამოყენება, მაგრამ აპის მხარდასაჭერად და შენი ემბლემის ხელახლა გასააქტიურებლად, აღადგინე გამოწერა. + შეგიძლია Signal-ის გამოყენება გააგრძელო, მაგრამ აპის მხარდასაჭერად და შენი ემბლემის ხელახლა გასააქტიურებლად, გამოწერა უნდა აღადგინო. გამოწერის განახლება Google Pay-ში შესვლა @@ -4508,7 +4579,7 @@ შენი დონაცია ვერ გაიგზავნა კავშირის ხარვეზის გამო. შეამოწმე შენი კავშირი და თავიდან სცადე. - დონაცია %1$s-სთვის + დონაცია %1$s-ის სახელზე %1$s-მა Signal-ს დონაცია შენს სახელზე გაუკეთა @@ -4907,7 +4978,7 @@ პასუხები & გამოხმაურება - პასუხებისა & გამოხმაურებების დაშვება + პასუხებისა & გამოხმაურების დაშვება ნება მიეცი ადამიანებს, რომლებსაც შეუძლიათ შენი Story-ის ნახვა, მას გამოეხმაურონ @@ -5601,5 +5672,15 @@ მომხმარებლის სახელის წაშლა + + + სთ + + წთ + + დაყენება + + ეკრანის დაბლოკვის გამოყენებამდე მინიმალური დროა 1 წუთი. + diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index abf83d5dda..d409214d80 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -14,13 +14,14 @@ + Иә Жоқ Жою Күте тұрыңыз… Сақтау - Өзіне өзі ескерту + Өзіне ескертпе @@ -98,11 +99,11 @@ Блокталған пайдаланушылар Блокталған пайдаланушыны қосу - Бұғатталған пайдаланушылар сізге қоңырау соға және хабарлама жібере алмайтын болады. + Блокталған пайдаланушылар сізге қоңырау соғып, хат жібере алмайтын болады. Блокталған пайдаланушылар жоқ Пайдаланушыны блоктау керек пе? \"%1$s\" сізге қоңырау шалып, хат жібере алмайды. - Бұғаттау + Блоктау @@ -138,7 +139,7 @@ Жалғастыру - %1$s блокталған күйде қалсын ба? + %1$s тобын блоктап тастап, шығып кету керек пе? %1$s блокталсын ба? Енді сізге бұл топтан хаттар немесе жаңалықтар келмейді және оның мүшелері сізді топқа қайта қоса алмайды. Топ мүшелері сізді бұл топқа қайта қоса алмайды. @@ -150,11 +151,11 @@ Блокталған адамдар сізге қоңырау шалып, хат жаза алмайды. Блокталған адамдар сізге хат жібере алмайды. - Signal-дағы жаңартулар мен хабарлар алу функциясын блоктаңыз. + Signal-дағы жаңартулар мен хаттар алу функциясын блоктаңыз. Signal-дағы жаңартулар мен хабарларды алуды қайта іске қосыңыз. %1$s блоктан шығарылсын ба? - Бұғаттау + Блоктау Блоктап, шығып кету Спам деп хабарлау және блоктау @@ -318,7 +319,7 @@ Signal хаты Molly %1$s қолданбасына ауысайық Контактіні таңдаңыз - Бұғатын ағыту + Блоктан шығару Сіз жіберіп жатқан хат түрі үшін тіркеменің өлшемі бекітілген шектен асып кетті. Аудио жазу мүмкін болмады! Бұл топқа хат жібере алмайсыз, себебі сіз енді бұл топтың мүшесі емессіз. @@ -455,7 +456,7 @@ Болдырмау - Бұғаттаулы + Блокталған Сүзгіні тазалау @@ -542,6 +543,15 @@ +%1$d + + Құрылғыларыңызды қайта байланыстыру + + Құрылғыңызды тіркеуден шығарған кезде, сіз қосқан құрылғылардың байланысы үзілді. \"Параметрлер\" бөліміне кіріп, құрылғыларды қайта байланыстырыңыз. + + Параметрлерді ашу + + Кейінірек + Мүшелерді таңдау @@ -953,6 +963,16 @@ Пайдаланушы аты жасалды Пайдаланушы аты көшірілді + + Пайдаланушы аты жойылмады. Кейінірек қайталап көріңіз. + + Пайдаланушы аты жойылды + + + + Пайдаланушы атыңызға байланысты бірдеңе дұрыс болмады, ол енді сіздің аккаунтыңызға тағайындалып тұрған жоқ. Оны қайта орнаты көруіңізге болады немесе жаңасын таңдаңыз. + + Қазір түзету @@ -1156,8 +1176,8 @@ Жаңа топ Достарды шақыру SMS қолдану - Сыртқы көрінісі - Фотосурет қосу + Чат түстері + Профиль фотосуретін қосу Жауаптар @@ -1468,8 +1488,8 @@ Қабылдау Жалғастыру Жою - Бұғаттау - Бұғатын ағыту + Блоктау + Блоктан шығару %1$s сізге хат жазуына рұқсат етіп, оған аты-жөніңіз бен фотосуретіңіздің көрсетілгенін қалайсыз ба? Сіз қабылдамайынша, ол хатты оқығаныңызды білмейтін болады. %1$s сізге хат жазуына рұқсат етіп, оған аты-жөніңіз бен фотосуретіңіздің көрсетілгенін қалайсыз ба? Оны блоктан шығармайынша, сіз одан бірде-бір хат алмайтын боласыз. @@ -1483,7 +1503,7 @@ Осы топқа қосылып, оның мүшелеріне аты-жөніңіз бен фотосуретіңіздің көрсетілгенін қалайсыз ба? Оларды қабылдамайынша, сіз олардың хатын оқығаныңызды біле алмайды. Осы топқа қосылып, оның мүшелеріне аты-жөніңіз бен фотосуретіңіздің көрсетілгенін қалайсыз ба? Оларды қабылдамайынша, олардың хаттарын көре алмайсыз. Осы топқа қосылу керек пе? Сіз қабылдамайынша, олар хаттарын оқығаныңызды білмейді. - Осы топтың бұғатын шешіп, атыңыз бен фотосуретіңізді оның мүшелерімен бөлісу керек пе? Олардың бұғатын шешпейінше хабарлама алмайсыз. + Осы топты блоктан шығарып, атыңыз бен фотосуретіңіз топ мүшелеріне көрсетілгенін қалайсыз ба? Блоктан шығармайынша, хат алмайсыз. View %1$s мүшесі @@ -1584,6 +1604,17 @@ Жаңа PIN-код жасау + + SMS кодын жіберу + + Signal-ға тіркелу – Android құрылғысы үшін PIN кодын қайта тіркеу кезінде көмек керек + + Сіздің PIN кодыңыз – %1$d + сіз жасаған цифрлық код сандық немесе әріптік-сандық болуы мүмкін.\n\nPIN кодыңыз есіңізде болмаса, жаңасын ойлап табыңыз. + + PIN кодыңыз есіңізде болмаса, жаңасын ойлап табыңыз. + + PIN кодын енгізуге берілген мүмкіндіктің барлығын пайдаланып қойдыңыз, бірақ жаңа PIN кодын жасап, Signal аккаунтыңызға әлі де кіре аласыз. + Ескерту PIN-кодты өшіріп қойсаңыз, резервтік көшірмесін қолмен жасап, оны қалпына келтірмейінше, Signal қолданбасына қайта тіркелгенде, барлық дерегіңіз өшіп қалады. PIN-кодты өшіріп қойып, Тіркелу құлпын аша алмайсыз. @@ -1616,8 +1647,8 @@ Менің сторисім - Бұғаттау - Бұғатын ағыту + Блоктау + Блоктан шығару @@ -1711,9 +1742,9 @@ Фотоаппарат - Дыбысын шығару + Дыбысын қосу - Дыбысын басу + Дыбысын өшіру Қоңырау шалу @@ -1731,7 +1762,7 @@ Сіз олардың, ал олар сіздің аудио немесе видеоларыңызды ала алмайды. %1$s деген кісінің аудио және видеоларын ала алмайсыз %1$s жіберген аудио және видеоларды ала алмайсыз - Ол сіздің қауіпсіздік нөміріңіздің өзгерісін верификацияламаған, құрылғыларында ақау болуы немесе ол сізді блоктап қойған болуы мүмкін. + Бұл сіздің қауіпсіздік нөміріңіздің өзгергені верификацияланбаған, құрылғыда ақау болуы немесе ол сізді блоктап қойған болуы мүмкін. Экранды ортақ көру үшін свайп жасаңыз @@ -1768,11 +1799,18 @@ Достарыңызбен байланыс орнатып, олармен хат алмасу үшін, Signal қолданбасына контактілер мен мультимедианы пайдалануға рұқсат керек. Сіздің контактілеріңіз Signal қолданбасының жеке контактілерді анықтау функциясы көмегімен жүктеп салынады. Бұл дегеніміз олар толығымен шифрланып, Signal қызметіне ешқашан көрсетілмейтінін білдіреді. Достарыңызбен байланыс орнату үшін, Signal қолданбасына контактілерді пайдалануға рұқсат керек. Сіздің контактілеріңіз Signal қолданбасының жеке контактілерді анықтау функциясы көмегімен жүктеп салынады. Бұл дегеніміз олар толығымен шифрланып, Signal қызметіне ешқашан көрсетілмейтінін білдіреді. Осы нөмірді тіркеу үшін сіз тым көп әрекет жасадыңыз. Кейінірек тағы бір рет байқап көріңіз. + + Осы нөмірді тіркеу үшін сіз тым көп әрекет жасадыңыз. %1$s өткен соң, қайталап көріңіз. Қызметке жалғану мүмкін емес. Желіге жалғанғаныңызды тексеріңіз де, тағы бір рет байқап көріңіз. Стандартты емес нөмір форматы Сіз енгізген нөмірдің (%1$s) форматы стандартты емес екен.\n\n%2$s деп жазғыңыз келіп пе еді? Molly Android - Телефон нөмірінің форматы + Қоңырау шалу сұралды + + SMS сұралды + + Верификация коды сұралды You are now %1$d step away from submitting a debug log. Дұрыстау журналын жіберу үшін енді %1$d қадам қалды. @@ -1792,6 +1830,16 @@ Қоңырау Верификация коды Кодты қайтадан жіберу + + Тіркелу кезінде қиындыққа тап болып жатырсыз ба? + + • SMS немесе қоңырау қабылдай алатындай телефоныңызда ұялы байланыс болуы керек. \n • Сол нөмірге телефон қоңырауын қабылдай алатын болуыңыз керек\n • Телефон нөміріңізді дұрыс енгізгеніңізді тексеріңіз. + + Толық ақпарат алу үшін ақауларды түзету қадамдарын орындаңыз немесе қолдау көрсету қызметіне хабарласыңыз + + бұл ақауларды түзету қадамдары + + Байланыстағы тұлғаны қолдау Тіркеу құлпын іске қосу керек пе? @@ -1953,11 +2001,15 @@ Сіздің сторисіңізге %1$s деп реакция қалдырды - Олардың сторисіне %1$s деп реакция қалдырды + Оның сторисіне %1$s деп реакция қалдырды Төлем Жоспарланған хат + + Хаттар тарихы біріктірілді + + %1$s нөмірі %2$s деген кісіге тиесілі Molly-ды жаңарту @@ -2094,6 +2146,8 @@ Файлыңызға %1$s деп реакция білдірді. Аудиоңызға %1$s деп реакция білдірді. Бір рет көрсетілетін мультимедиа файлыңызға %1$s деп реакция білдірді. + + Reacted %1$s to your payment. Стикеріңізге %1$s деп реакция білдірді. Бұл хат жойылды. @@ -2487,7 +2541,7 @@ Жеткізу мәселесі туындады - %1$s жіберген хат, стикер, реакция немесе оқылғандығы туралы хабарландыру сізге жеткізілмеді. Ол топта емес, тікелей сізге жібергісі келген болуы мүмкін. + %1$s жіберген хат, стикер, реакция немесе оқылғандығы туралы хабарландыру сізге жеткізілмеді. Ол топқа емес, тікелей сізге жібергісі келген болуы мүмкін. %1$s жіберген хатты, стикерді, реакцияны немесе оқылғандығы туралы хабарландыруды сізге жеткізу мүмкін болмады. @@ -2555,7 +2609,7 @@ Күтілуде - Кімге жіберілді + Алушы Кімнен Кімге жеткізілді Оқыған @@ -2635,10 +2689,10 @@ Әдепкі мәнді пайдалану Реттелмелі мәнді пайдалану - Дауысын 1 сағатқа өшіру - Дауысын 8 сағатқа өшіру - Дауысын 1 күнге өшіру - Дауысын 7 күнге өшіру + Дыбысын 1 сағатқа өшіру + Дыбысын 8 сағатқа өшіру + Дыбысын 1 күнге өшіру + Дыбысын 7 күнге өшіру Әрқашан Әдепкі параметрлер @@ -2675,9 +2729,9 @@ Мекенжайлар кітабындағы фотосуреттерді қолдану Бар болса, мекенжайлар кітабындағы контакт фотосуреттерін көрсету - Keep Muted Chats Archived + Дыбысы өшірілген чаттарды мұрағатта қалдыру - Muted chats that are archived will remain archived when a new message arrives. + Мұрағаттағы дыбысы өшірілген чаттарға жаңа хат келгенде олар мұрағатта қала береді. Сілтеменің шағын көріністерін жасау Жіберетін хаттарыңыз үшін сілтеменің шағын көріністерін тікелей веб-сайттардан алыңыз. Құпия сөйлемді өзгерту @@ -3128,7 +3182,7 @@ - Дыбысын шығару + Дыбысын қосу Хабарландырулардың дыбысын өшіру @@ -3295,6 +3349,8 @@ PIN кодыңызды енгізу Тіркелгіңіз үшін жасаған PIN кодты енгізіңіз. Ол сіздің SMS арқылы жіберілген тексеру кодыңыздан басқаша. + + Аккаунт үшін ойлап тапқан PIN кодыңызды енгізіңіз. Әріпті-санды PIN енгізу Санды PIN енгізу Дұрыс емес PIN. Қайтадан байқап көріңіз. @@ -3398,7 +3454,10 @@ Резервтік көшірмеде өте үлкен файл бар және оның резервтік көшірмесін жасау мүмкін емес. Үлкен файлды жойып тастап, жаңасын жасаңыз. Резервтік көшірмелерді басқару үшін түртіңіз. Нөмір қате ме? + Маған қоңырау шалу (%1$02d:%2$02d) + + Кодты қайта жіберу (%1$02d:%2$02d) Signal қолдау қызметіне хабарласу Signal қолданбасында тіркелу - Android жүйесіне арналған тексеру коды Код дұрыс емес @@ -3406,6 +3465,18 @@ Белгісіз Телефон нөмірімді көру Мені телефон нөмірім арқылы табу + + Телефон нөмірі + + Телефон нөміріңізді кім көре алатынын және Molly-да сізге кім жаза алатынын таңдаңыз. + + Менің нөмірімді кім көре алады? + + Molly-да сіздің нөміріңізді ешкім көрмейді + + Мені нөмірім арқылы кім таба алады? + + Телефон нөміріңіз сіз хат жазған адамдарға және топтарға көрініп тұрады. Басқа адамның телефонындағы контактілер тізімінде болсаңыз, ол адам да сізді Molly-дан көре алады. Барлығы Менің контактілерім Ешкім @@ -3562,8 +3633,8 @@ - Бұғаттау - Бұғатын ағыту + Блоктау + Блоктан шығару Контактілерді қосу Контактілерді аша алатын қолданба табылмады. @@ -3644,7 +3715,7 @@ Контактіңіз Топтан өшіру Контактіні жаңарту - Бұғаттау + Блоктау Жою Жақында профиль атауын (%1$s) %2$s деп өзгертті @@ -4003,7 +4074,7 @@ Профиль жасау - Бұғаттаулы + Блокталған %1$d контакт Хат алмасу Disappearing messages @@ -4106,7 +4177,7 @@ Кейін - Реакцияларды бейімдеу + Реакцияларды реттеу Эмодзиді ауыстыру үшін түртіңіз Бастапқы күйге қайтару Сақтау @@ -4165,7 +4236,7 @@ Қоңырау - Дыбысын басу + Дыбысын өшіру Дыбысы өшірілген @@ -4175,9 +4246,9 @@ Контакт мәліметтері Қауіпсіздік нөмірін көру - Бұғаттау + Блоктау Топты блоктау - Бұғатын шешу + Блоктан шығару Топты блоктан шығару Топқа қосу Барлығын көру @@ -4186,7 +4257,7 @@ Өтініштер және шақырулар Топ сілтемесі Контакт ретінде қосу - Дыбысын шығару + Дыбысын қосу Әңгіменің дыбысы %1$s дейін өшірілген Әңгіменің дыбысы біржола өшірілген Телефон нөмірі буферге көшірілді. @@ -4205,7 +4276,7 @@ Хабарландырулардың дыбысын өшіру - Дыбысы басылған жоқ + Дыбысы өшірілмеген Атап өтулер Үнемі хабарлау Хабарламау @@ -4234,7 +4305,7 @@ Өшіру - Бұғаттау + Блоктау %1$s өшірілсін бе? @@ -4330,7 +4401,7 @@ Сторис қосу Хат қосу Жауап қосу - Жіберу + Алушы Бір рет көрінетін хат Бір немесе бірнеше элемент өте үлкен болды Бір немесе бірнеше элемент дұрыс болмады @@ -4508,7 +4579,7 @@ Желідегі қатеге байланысты демеу ретінде берілген ақша жіберілмеді. Контактіні тексеріп, қайталап көріңіз. - %1$s деген кісіге берілген демеу + %1$s атынан демеушілік жасау %1$s сіздің атыңыздан Signal-ға демеу берді @@ -5087,7 +5158,7 @@ Демеуді растау - Жіберу + Алушы Алушыға демеу берілгені туралы жеке хат жіберіледі. Хатыңызды төменге жазыңыз. @@ -5601,5 +5672,15 @@ Пайдаланушы атын жою + + + сағ + + мин + + Орнату + + Экран құлыпталғанға дейінгі минималды уақыт – 1 минут. + diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml index ddbe3e5c54..2d2d2e28bb 100644 --- a/app/src/main/res/values-km/strings.xml +++ b/app/src/main/res/values-km/strings.xml @@ -14,13 +14,14 @@ + មែន ទេ លុប សូមរង់ចាំ… រក្សាទុក - កំណត់ចំណាំខ្លួនឯង + កំណត់ចំណាំផ្ទាល់ខ្លួន @@ -96,13 +97,13 @@ កំពុងពិនិត្យសារ… - ហាមឃាត់អ្នកប្រើនេះ - ហាមឃាត់អ្នកប្រើប្រាស់ - អ្នកប្រើប្រាស់ដែលបានហាមឃាត់ នឹងមិនអាចហៅ ឬផ្ញើសារមកអ្នកបានទេ។ - ពុំមានហាមឃាត់អ្នកប្រើនេះ - ហាមឃាត់អ្នកប្រើប្រាស់? + អ្នកប្រើដែលបានទប់ស្កាត់ + បញ្ចូលអ្នកប្រើដែលបានទប់ស្កាត់ + អ្នកប្រើដែលបានទប់ស្កាត់នឹងមិនអាចហៅ ឬផ្ញើសារមកអ្នកបានទេ។ + មិនមានអ្នកប្រើដែលបានទប់ស្កាត់ទេ + ទប់ស្កាត់អ្នកប្រើឬ? \"%1$s\" នឹងមិនអាចហៅអ្នកឬផ្ញើសារមកអ្នកបានទេ។ - រារាំង + ទប់ស្កាត់ @@ -138,8 +139,8 @@ បន្ត - ហាមឃាត់ និងចាកចេញ %1$s? - ហាមឃាត់%1$s? + ទប់ស្កាត់ និងចាកចេញពី %1$s ឬ? + ទប់ស្កាត់ %1$s ឬ? អ្នកនឹងលែងទទួលសារឬការព័ត៌មានពីក្រុមនេះ ហើយសមាជិកនឹងមិនអាចដាក់អ្នកចូលក្នុងក្រុមនេះបានទៀតទេ។ សមាជិកនឹងមិនអាចដាក់អ្នកចូលក្នុងក្រុមនេះបានទៀតទេ។ សមាជិកនឹងអាចដាក់អ្នកចូលក្នុងក្រុមនេះបានម្តងទៀត។ @@ -147,16 +148,16 @@ អ្នកនឹងអាចផ្ញើសារនិងហៅចេញទៅវិញទៅមក ហើយឈ្មោះនិងរូបភាពរបស់អ្នក នឹងត្រូវចែករំលែកជាមួយពួកគេ។ អ្នកនឹងអាចផ្ញើសារគ្នាទៅវិញទៅមក។ - មនុស្សដែលបានហាមឃាត់ នឹងមិនអាចហៅមកអ្នក ឬផ្ញើសារបានទេ។ - មនុស្សដែលត្រូវបានទប់ស្កាត់នឹងមិនអាចផ្ញើសារទៅអ្នកបានទេ។ + មនុស្សដែលបានទប់ស្កាត់នឹងមិនអាចហៅ ឬផ្ញើសារមកអ្នកបានទេ។ + មនុស្សដែលបានទប់ស្កាត់នឹងមិនអាចផ្ញើសារមកអ្នកបានទេ។ - ហាមឃាត់ការទទួលដំណឹង និងបច្ចុប្បន្នភាព Signal។ + ទប់ស្កាត់ការទទួលព័ត៌មាន និងបច្ចុប្បន្នភាពអំពី Signal។ បន្តការទទួលដំណឹង និងបច្ចុប្បន្នភាព Signal។ - លែងហាមឃាត់ %1$s? - រារាំង - ហាមឃាត់ និងចាកចេញ - រាយការណ៍សារឥតបានការ និងហាមឃាត់ + ឈប់ទប់ស្កាត់ %1$s ឬ? + ទប់ស្កាត់ + ទប់ស្កាត់ និងចាកចេញ + រាយការណ៍សារឥតបានការ និងទប់ស្កាត់ ថ្ងៃនេះ @@ -318,7 +319,7 @@ សារ Signal តោះដូរទៅកាន់ Molly %1$s សូមជ្រើសរើសលេខទំនាក់ទំនង១ - បើកវិញ + ឈប់ទប់ស្កាត់ ឯកសារភ្ជាប់លើសទំហំកំណត់ សម្រាប់ប្រភេទសារដែលអ្នកកំពុងផ្ញើ។ មិនអាចថតសំឡេងបាន! អ្នកមិនអាចផ្ញើសារទៅក្រុមនេះបានទេ ដោយសារអ្នកមិនមែនជាសមាជិកក្រុម។ @@ -366,7 +367,7 @@ បញ្ហាបញ្ជូនមេឌា - បានរាយការណ៍សារឥតបានការ និងបានហាមឃាត់។ + បានរាយការណ៍ជាសារឥតបានការ និងបានទប់ស្កាត់។ ការផ្ញើសារជាអក្សរបច្ចុប្បន្នត្រូវបានបិទ។ អ្នកអាចនាំចេញសាររបស់អ្នកទៅកម្មវិធីមួយទៀតនៅលើទូរសព្ទរបស់អ្នក។ @@ -449,7 +450,7 @@ បោះបង់ - បានហាមឃាត់ + បានទប់ស្កាត់ សម្អាតតម្រង @@ -489,10 +490,10 @@ ជ្រើសរើស - បណ្ណសារ + ទុកក្នុងបណ្ណសារ - បិទបណ្ណសារ + ឈប់ទុកក្នុងបណ្ណសារ លុប @@ -522,6 +523,15 @@ +%1$d + + ភ្ជាប់ឧបករណ៍របស់អ្នកឡើងវិញ + + ឧបករណ៍ដែលអ្នកបានបញ្ចូលត្រូវបានផ្តាច់នៅពេលដែលឧបករណ៍របស់អ្នកមិនត្រូវបានចុះឈ្មោះ។ ចូលទៅកាន់ការកំណត់ ដើម្បីភ្ជាប់ឧបករណ៍ណាមួយឡើងវិញ។ + + បើកការកំណត់ + + ពេលក្រោយ + ជ្រើសរើសសមាជិក @@ -897,7 +907,7 @@ ជូនដំណឹងខ្ញុំសម្រាប់ការហៅឈ្មោះ - ទទួលបានការជូនដំណឹងនៅពេលដែលអ្នកត្រូវបានគេហៅនៅក្នុងការសន្ទនាស្ងាត់ៗ ? + ទទួលបានការជូនដំណឹងនៅពេលដែលអ្នកត្រូវបានគេលើកឡើងនៅក្នុងការជជែកដែលបានបិទសំឡេងឬ? ជូនដំណឹងខ្ញុំជានិច្ច កុំជូនដំណឹងខ្ញុំ @@ -915,6 +925,16 @@ បានបង្កើតឈ្មោះអ្នកប្រើ បានចម្លងឈ្មោះអ្នកប្រើ + + មិនអាចលុបឈ្មោះអ្នកប្រើបានទេ។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។ + + ឈ្មោះអ្នកប្រើត្រូវបានលុប + + + + មានអ្វីមួយមិនប្រក្រតីជាមួយឈ្មោះអ្នកប្រើរបស់អ្នក ព្រោះវាលែងកំណត់ជាឈ្មោះរបស់គណនីអ្នកទៀតហើយ។ អ្នកអាចសាកល្បង និងកំណត់វាម្តងទៀត ឬជ្រើសរើសឈ្មោះថ្មីមួយ។ + + ដោះស្រាយឥឡូវនេះ @@ -1108,8 +1128,8 @@ ក្រុមថ្មី អញ្ជើញមិត្តភក្តិ ប្រើប្រាស់ SMS - រូបរាង - បន្ថែមរូបភាព + ពណ៌ការជជែក + បញ្ចូលរូបថតប្រូហ្វាល់ ការឆ្លើយតប @@ -1410,14 +1430,14 @@ យល់ព្រម បន្ត លុប - រារាំង - បើកវិញ + ទប់ស្កាត់ + ឈប់ទប់ស្កាត់ អនុញ្ញាត %1$s ផ្ញើសារមកអ្នក និងចែករំលែកឈ្មោះ និងរូបភាពរបស់អ្នកជាមួយពួកគេ? ពួកគេនឹងមិនដឹងថា អ្នកបានមើលឃើញសាររបស់គេ រហូតដល់អ្នកយល់ព្រម។ - អនុញ្ញាត %1$s ផ្ញើសារមកអ្នក និងចែកឈ្មោះ និងរូបភាពរបស់អ្នកជាមួយសមាជិកក្រុម? អ្នកនឹងមិនទទួលសារណាមួយឡើយ រហូតអ្នកលែងហាមឃាត់ពួកគេ។ + អាចឲ្យ %1$s ផ្ញើសារមកអ្នកបាន និងចែករំលែកឈ្មោះ និងរូបថតរបស់អ្នកជាមួយពួកគេ? អ្នកនឹងមិនទទួលបានសារណាមួយឡើយ រហូតទាល់តែអ្នកឈប់ទប់ស្កាត់ពួកគេ។ - អនុញ្ញាតឲ្យ %1$s ផ្ញើសារទៅអ្នកឬ? អ្នក​នឹង​មិន​ទទួល​បាន​សារ​ណា​មួយ​ឡើយ រហូត​ទាល់​តែ​អ្នក​ឈប់​ទប់ស្កាត់​ពួកគេ។ - ទទួលបច្ចុប្បន្នភាព និងព័ត៌មានពី %1$s? អ្នកនឹងមិនទទួលបច្ចុប្បន្នភាពណាមួយឡើយ រហូតអ្នកលែងហាមឃាត់វា។ + អាចឲ្យ %1$s ផ្ញើសារមកអ្នកបាន? អ្នកនឹងមិនទទួលបានសារណាមួយឡើយ រហូតទាល់តែអ្នកឈប់ទប់ស្កាត់ពួកគេ។ + ទទួលបានបច្ចុប្បន្នភាព និងព័ត៌មានពី %1$s? អ្នកនឹងមិនទទួលបានបច្ចុប្បន្នភាពណាមួយឡើយ រហូតទាល់តែអ្នកឈប់ទប់ស្កាត់ពួកគេ។ បន្តការសន្ទនារបស់អ្នកជាមួយក្រុមនេះ និងចែករំលែកឈ្មោះ និងរូបភាពរបស់អ្នកជាមួយសមាជិកក្រុម។ ដំឡើងក្រុមនេះ ដើម្បីបើកមុខងារថ្មីៗដូចជា @mentions និងអ្នកគ្រប់គ្រង។ សមាជិកដែលមិនអាចចែករំលែកឈ្មោះ ឬរូបភាពរបស់ពួកគេនៅក្នុងក្រុម នឹងត្រូវបានអញ្ជើញឲ្យចូលរួម។ ក្រុមជំនាន់មុន អាចឈប់ប្រើប្រាស់ ដោយសារវាមានទំហំធំពេក។ ទំហំអតិបរមាក្រុមគឺ %1$d។ @@ -1425,7 +1445,7 @@ ចូលក្រុមនេះ និងចែករំលែកឈ្មោះ និងរូបថតអ្នក ជាមួយសមាជិកក្រុម? ពួកគេនឹងមិនដឹងថាអ្នកបានឃើញសាររបស់ពួកគេ រហូតអ្នកយល់ព្រមទទួល។ ចូលរួមក្រុមនេះ ហើយចែករំលែកឈ្មោះ និងរូបថតរបស់អ្នកជាមួយសមាជិកទាំងនោះឬ? អ្នកនឹងមិនឃើញសាររបស់ពួកគេ រហូតទាល់តែអ្នកយល់ព្រមទទួល។ ចូលរួមក្រុមនេះ? ពួកគេនឹងមិនដឹងថា អ្នកបានឃើញសាររបស់ពួកគេ រហូតអ្នកយល់ព្រម។ - លែងហាមឃាត់ក្រុមនេះ និងចែករំលែកឈ្មោះ និងរូបភាពរបស់អ្នកជាមួយសមាជិកក្រុម? អ្នកនឹងមិនទទួលសារណាមួយឡើយ រហូតអ្នកលែងហាមឃាត់ពួកគេ។ + ឈប់ទប់ស្កាត់ក្រុមនេះ និងចែករំលែកឈ្មោះ និងរូបថតរបស់អ្នកជាមួយសមាជិកក្រុម? អ្នកនឹងមិនទទួលបានសារណាមួយឡើយ រហូតទាល់តែអ្នកឈប់ទប់ស្កាត់ពួកគេ។ បង្ហាញ សមាជិកនៃ %1$s @@ -1520,9 +1540,20 @@ បង្កើតលេខ PIN ថ្មី + + ផ្ញើលេខកូដតាមសារជាអក្សរ + + ការចុះឈ្មោះសម្រាប់ Signal - ត្រូវការជំនួយក្នុងការចុះឈ្មោះឡើងវិញនូវលេខកូដសម្ងាត់សម្រាប់ Android + + លេខកូដសម្ងាត់របស់អ្នកគឺជាលេខកូដ %1$d ខ្ទង់ដែលអ្នកបានបង្កើតឡើង ហើយវាអាចជាលេខ ឬជាអក្សរនិងលេខ។\n\nប្រសិនបើអ្នកចាំលេខកូដសម្ងាត់របស់អ្នកមិនបានទេ អ្នកអាចបង្កើតលេខកូដសម្ងាត់ថ្មី។ + + ប្រសិនបើអ្នកចាំលេខកូដសម្ងាត់របស់អ្នកមិនបានទេ អ្នកអាចបង្កើតលេខកូដសម្ងាត់ថ្មី។ + + ការសាកល្បងទាយស្មានលេខកូដសម្ងាត់របស់អ្នកបានអស់ហើយ ប៉ុន្តែអ្នកនៅតែអាចចូលប្រើគណនី Signal របស់អ្នកបាន ដោយបង្កើតលេខកូដសម្ងាត់ថ្មី។ + ព្រមាន - បើអ្នកបានបិទលេខកូដ PIN អ្នកនឹងបាត់បង់ទិន្នន័យទាំងអស់ នៅពេលអ្នកចុះឈ្មោះ Signal ឡើងវិញ លុះត្រាអ្នកបម្រុងទុក និងស្តារឡើងវិញដោយខ្លួនឯង។ អ្នកមិនអាចបើក ការចាក់សោការចុះឈ្មោះ នៅពេលលេខកូដ PIN បានបិទនោះទេ។ + បើអ្នកបិទលេខកូដសម្ងាត់ អ្នកនឹងបាត់បង់ទិន្នន័យទាំងអស់ នៅពេលអ្នកចុះឈ្មោះ Signal ឡើងវិញ លើកលែងតែអ្នកបម្រុងទុក និងស្តារឡើងវិញដោយខ្លួនឯង។ អ្នកមិនអាចបើក ការចាក់សោការចុះឈ្មោះ ខណៈពេលដែលលេខកូដសម្ងាត់ត្រូវបានបិទនោះទេ។ បិទលេខកូដ PIN @@ -1552,8 +1583,8 @@ រឿងរ៉ាវរបស់ខ្ញុំ - ហាមឃាត់ - បើកវិញ + ទប់ស្កាត់ + ឈប់ទប់ស្កាត់ @@ -1656,12 +1687,12 @@ - %1$s ត្រូវបានហាមឃាត់ + %1$s ត្រូវបានទប់ស្កាត់ ព័ត៌មានបន្ថែម អ្នកនឹងមិនទទួលបានសំឡេងឬវីដេអូរបស់ពួកគេទេ ហើយពួកគេនឹងមិនទទួលបានរបស់អ្នកដែរ។ មិនទទួលសំឡេង & វីដែអូពី %1$s មិនអាចទទួលសំឡេងនិងវីដេអូពី %1$s - នេះអាចដោយសារ ពួកគេមិនត្រូវបានជូនដំណឹងការផ្លាស់ប្តូរលេខសុវត្ថិភាពរបស់អ្នក មានបញ្ហាជាមួយឧបករណ៍របស់ពួកគេ ឬពួកគេបានហាមឃាត់អ្នក។ + វាអាចមកពីពួកគេមិនបានផ្ទៀងផ្ទាត់ការផ្លាស់ប្តូរលេខសុវត្ថិភាពរបស់អ្នក ឧបករណ៍របស់ពួកគេមានបញ្ហា ឬពួកគេបានទប់ស្កាត់អ្នក។ អូស ដើម្បីបង្ហាញអេក្រង់ចែករំលែក @@ -1698,11 +1729,18 @@ Signal ត្រូវការអនុញ្ញាតបញ្ជីទំនាក់ទំនង និងឯកសារមេឌៀ ដើម្បីជួយតភ្ជាប់អ្នកជាមួយមិត្តភក្តិ និងផ្ញើសារ។ បញ្ជីទំនាក់ទំនងរបស់អ្នក ត្រូវបានផ្ទុកប្រើប្រាស់ការរកឃើញបញ្ជីទំនាក់ទំនងឯកជនរបស់ Signal ដែលមានន័យថាវាត្រូវបានធ្វើកូដនីយកម្មទាំងសងខាង និងមិនដែលបង្ហាញទៅកាន់សេវាកម្ម Signal ទេ។ Signal ត្រូវការអនុញ្ញាតបញ្ជីទំនាក់ទំនង ដើម្បីជួយតភ្ជាប់អ្នកជាមួយមិត្តភក្តិ។ បញ្ជីទំនាក់ទំនងរបស់អ្នក ត្រូវបានផ្ទុកប្រើប្រាស់ការរកឃើញបញ្ជីទំនាក់ទំនងឯកជនរបស់ Signal ដែលមានន័យថាវាត្រូវបានធ្វើកូដនីយកម្មទាំងសងខាង និងមិនដែលបង្ហាញទៅកាន់សេវាកម្ម Signal ទេ។ អ្នកបានសាកល្បងច្រើនដងពេក ដើម្បីចុះឈ្មោះជាមួយលេខទូរស័ព្ទនេះ។ សូមសាកល្បងម្តងទៀតនៅពេលក្រោយ។ + + អ្នកបានព្យាយាមជាច្រើនដងពេកក្នុងការចុះឈ្មោះលេខនេះ។ សូមព្យាយាមម្តងទៀតក្នុងរយៈពេល %1$s។ មិនអាចតភ្ជាប់សេវាបាន។ សូមពិនិត្យការតភ្ជាប់បណ្តាញ ហើយព្យាយាមម្តងទៀត។ ទម្រង់លេខមិនស្តង់ដារ លេខដែលអ្នកបានបញ្ចូល (%1$s) មានទម្រង់លេខមិនស្តង់ដារ.\n\nតើអ្នកមានន័យថា %2$s? Molly Android - ទម្រង់លេខទូរស័ព្ទ + បានស្នើសុំការហៅ + + បានស្នើសុំលេខកូដតាមសារជាអក្សរ + + បានស្នើសុំលេខកូដផ្ទៀងផ្ទាត់ You are now %1$d steps away from submitting a debug log. @@ -1721,6 +1759,16 @@ ហៅចេញ កូដផ្ទៀងផ្ទាត់ ផ្ញើកូដម្តងទៀត + + មានបញ្ហាក្នុងការចុះឈ្មោះឬ? + + • ត្រូវប្រាកដថាទូរសព្ទរបស់អ្នកមានសញ្ញាសេវាទូរសព្ទ ដើម្បីអាចទទួលសារជាអក្សរ ឬការហៅចូលបាន\n • ត្រូវបញ្ជាក់ថាអ្នកអាចទទួលការហៅចូលទៅលេខទូរសព្ទនោះបាន\n • ត្រូវពិនិត្យមើលថាអ្នកបានបញ្ចូលលេខទូរសព្ទរបស់អ្នកបានត្រឹមត្រូវហើយ។ + + សម្រាប់ព័ត៌មានបន្ថែម សូមធ្វើតាមជំហានដោះស្រាយបញ្ហាទាំងនេះ ឬទាក់ទងផ្នែកជំនួយ + + ជំហានដោះស្រាយបញ្ហាទាំងនេះ + + ទាក់ទងផ្នែកជំនួយ បើកការចាក់សោរចុះឈ្មោះ? @@ -1887,6 +1935,10 @@ ការបង់ប្រាក់ សារដែលបានកំណត់ពេលផ្ញើ + + ប្រវត្តិសាររបស់អ្នកត្រូវបានបញ្ចូលគ្នា + + %1$s ជាលេខរបស់ %2$s បច្ចុប្បន្នភាព Molly @@ -1958,7 +2010,7 @@ សារMMS ត្រូវបានកាណាល់កូដសម្ងាត់សម្រាប់កំឡុងពេលមិនប្រើប្រាស់ - បិទសំឡេងសារជូនដំណឹង + បិទសំឡេងការជូនដំណឹង ការនាំចូលកំពុងដំណើរការ @@ -2015,14 +2067,16 @@ SMS គ្មានសុវត្ថិភាព %1$s %2$s ទំនាក់ទំនង - ប្រតិកម្ម %1$s ទៅ៖ \"%2$s\"។ - ប្រតិកម្ម %1$s ទៅវីដេអូអ្នក។ - ប្រតិកម្ម %1$s ទៅរូបភាពអ្នក។ - ប្រតិកម្ម %1$s ទៅ GIF របស់អ្នក។ - ប្រតិកម្ម %1$s ទៅឯកសារអ្នក។ - ប្រតិកម្ម %1$s ទៅសំឡេងអ្នក។ - ប្រតិកម្ម%1$s ទៅឯកសារមេឌៀមើលម្តងរបស់អ្នក។ - ប្រតិកម្ម %1$s ទៅស្ទីកគ័រអ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹង៖ \"%2$s\"។ + បានប្រតិកម្ម %1$s ទៅនឹងវីដេអូរបស់អ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹងរូបភាពរបស់អ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹងរូបចលនារបស់អ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹងឯកសាររបស់អ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹងសំឡេងរបស់អ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹងមេឌៀដែលមើលបានតែម្តងរបស់អ្នក។ + + បានប្រតិកម្ម %1$s ទៅនឹងការបង់ប្រាក់របស់អ្នក។ + បានប្រតិកម្ម %1$s ទៅនឹងស្ទីគ័ររបស់អ្នក។ សារនេះត្រូវបានលុប។ បិទសារជូនដំណឹងលេខទំនាក់ទំនងចូលរួម Signal? អ្នកអាចបើកវាម្តងទៀតក្នុង Signal > ការកំណត់ > សារជូនដំណឹង។ @@ -2404,8 +2458,8 @@ បញ្ហាការបញ្ជូន - សារ រូបស្ទីកគ័រ ប្រតិកម្ម និងការទទួលការអាន មិនអាចបញ្ជូនទៅអ្នកពី %1$s ទេ។​ ពួកគេអាចព្យាយាមបញ្ជូនវាទៅអ្នកផ្ទាល់ ឬនៅក្នុងក្រុម។ - សារ រូបស្ទីកគ័រ ប្រតិកម្ម និងការទទួលការអាន មិនអាចបញ្ជូនទៅអ្នកពី %1$s។ + សារ ស្ទីគ័រ ប្រតិកម្ម ឬមុខងារអានសារមិនអាចបញ្ជូនពី %1$s ទៅអ្នកបានទេ។ គេប្រហែលជាបានសាកល្បងផ្ញើវាទៅអ្នកដោយផ្ទាល់ ឬនៅក្នុងក្រុម។ + សារ ស្ទីគ័រ ប្រតិកម្ម ឬមុខងារអានសារមិនអាចបញ្ជូនពី %1$s ទៅអ្នកបានទេ។ ឈ្មោះ (ចាំបាច់) @@ -2887,7 +2941,7 @@ ការទូទាត់បានផ្ញើ ការទូទាត់បានទទួល ការទូទាត់បានបញ្ចប់ %1$s - ប្លក់លេខ + លេខ Block ការបញ្ជូន @@ -2972,7 +3026,7 @@ ផ្ញើសារថ្មីទៅកាន់… - ហាមឃាត់អ្នកប្រើប្រាស់ + ទប់ស្កាត់អ្នកប្រើ បន្ថែមចូលក្រុម @@ -3046,7 +3100,7 @@ បើកសំឡេង - បិទសំឡេងសារជូនដំណឹង + បិទសំឡេងការជូនដំណឹង ការកំណត់ក្រុម @@ -3207,6 +3261,8 @@ បញ្ចូលលេខ PIN របស់អ្នក បញ្ចូលលេខ PIN ដែលអ្នកបានបង្កើតសម្រាប់ គណនីរបស់អ្នក។ នេះខុសពីលេខកូដផ្ទៀងផ្ទាត់ពីសារ SMS ។ + + បញ្ចូលលេខកូដសម្ងាត់ដែលអ្នកបានបង្កើតសម្រាប់គណនីរបស់អ្នក។ បញ្ចូលលេខ PIN អក្សរក្រមលេខ បញ្ចូលលេខកូដ PIN លេខកូដ PIN មិនត្រឹមត្រូវ។ សាកល្បងម្តងទៀត។ @@ -3305,7 +3361,10 @@ ការបម្រុងទុករបស់អ្នកមានឯកសារមួយដែលមានទំហំធំខ្លាំងណាស់ដែលមិនអាចបម្រុងទុកបានទេ។ សូមលុបវា ហើយបង្កើតការបម្រុងទុកថ្មី។ ចុច ដើម្បីគ្រប់គ្រងការបម្រុងទុក។ ខុសលេខ? + ហៅមកខ្ញុំ (%1$02d:%2$02d) + + ផ្ញើលេខកូដឡើងវិញ (%1$02d:%2$02d) ទាក់ទងជំនួយ Signal ការចុះឈ្មោះ Signal - ការផ្ទៀងផ្ទាត់កូដសម្រាប់ Android លេខកូដមិនត្រឹមត្រូវ @@ -3313,6 +3372,18 @@ មិនស្គាល់ មើលលេខទូរស័ព្ទរបស់ខ្ញុំ ស្វែងរកលេខទូរស័ព្ទរបស់ខ្ញុំ + + លេខទូរសព្ទ + + ជ្រើសរើសអ្នកដែលអាចមើលឃើញលេខទូរសព្ទរបស់អ្នក និងអ្នកណាដែលអាចប្រើវាទាក់ទងអ្នកបាននៅលើ Molly។ + + អ្នកដែលអាចមើលឃើញលេខរបស់ខ្ញុំ + + គ្មាននរណាម្នាក់មើលឃើញលេខទូរសព្ទរបស់អ្នកនៅលើ Molly ទេ + + អ្នកដែលអាចរកខ្ញុំឃើញតាមលេខរបស់ខ្ញុំ + + មនុស្ស និងក្រុមទាំងឡាយដែលអ្នកផ្ញើសារទៅនឹងអាចមើលឃើញលេខទូរសព្ទរបស់អ្នក។ អ្នកទាំងឡាយដែលមានលេខទូរសព្ទរបស់អ្នកនៅក្នុងបញ្ជីទំនាក់ទំនងរបស់ពួកគេក៏នឹងអាចមើលវាឃើញនៅលើ Molly ដែរ។ អ្នករាល់គ្នា បញ្ជីទំនាក់ទំនងរបស់ខ្ញុំ គ្មាននរណា @@ -3469,8 +3540,8 @@ - រារាំង - បើកវិញ + ទប់ស្កាត់ + ឈប់ទប់ស្កាត់ បន្ថែមទៅបញ្ជីទំនាក់ទំនង មិនមានកម្មវិធីដែលអាចបើកលេខទំនាក់ទំនងបាន @@ -3522,9 +3593,9 @@ %1$s/%2$s - %1$sត្រូវបានបិទ - បរជ័យក្នុងការបិទ%1$s - %1$sត្រូវបានដកចេញពីការបិទ + \"%1$s\" ត្រូវបានទប់ស្កាត់។ + មិនអាចទប់ស្កាត់ \"%1$s\" បាន + \"%1$s\" ត្រូវបានឈប់ទប់ស្កាត់។ ពិនិត្យមើលសមាជិក @@ -3549,7 +3620,7 @@ លេខទំនាក់ទំនងរបស់អ្នក ដកចេញពីក្រុម ធ្វើបច្ចុប្បន្នភាពលេខទំនាក់ទំនង - រារាំង + ទប់ស្កាត់ លុប ថ្មីៗនេះ %1$sបានប្ដូរឈ្មោះគណនីទៅ %2$s @@ -3572,7 +3643,7 @@ បានប្តូរទៅប្រព័ន្ធអ៊ីនធឺណិតទូរសព្ទ ព្រោះប្រព័ន្ធ Wi-Fi ខ្សោយ។ - លុបគណនីអ្នកនឹង៖ + ការលុបគណនីរបស់អ្នកនឹង៖ វាយបញ្ចូលលេខរបស់អ្នក លុបគណនី លុបព័ត៌មានគណនី និងរូបថតប្រូហ្វាល់របស់អ្នក @@ -3582,7 +3653,7 @@ មិនបានបញ្ជាក់លេខទូរស័ព្ទ លេខទូរស័ព្ទដែលអ្នកបានបញ្ជូលមិនត្រូវគ្នាជាមួយគណនីអ្នក។ តើអ្នកប្រាកដទេថា ចង់លុបគណនីរបស់អ្នក? - ការធ្វើបែបនេះនឹងលុបគណនី Signal របស់អ្នក ហើយកំណត់កម្មវិធីនេះឡើងវិញ។ បន្ទាប់ពីដំណើរការនេះបញ្ចប់ កម្មវិធីនេះនឹងបិទ។ + ការធ្វើបែបនេះនឹងលុបគណនី Signal របស់អ្នក ហើយកំណត់កម្មវិធីនេះឡើងវិញ។ កម្មវិធីនេះនឹងបិទបន្ទាប់ពីដំណើរការនេះបញ្ចប់។ មិនអាចលុបទិន្នន័យក្នុងឧបករណ៍បានទេ។ អ្នកអាចលុបវាបានដោយខ្លួនឯងនៅក្នុងការកំណត់កម្មវិធីក្នុងប្រព័ន្ធ។ បើកការកំណត់របស់កម្មវិធី @@ -3688,12 +3759,12 @@ បិទដំណើរការ Wallet សមតុល្យរបស់អ្នក - អ្នកគួរតែផ្ទេរប្រាក់របស់អ្នកទៅកាន់អាសយដ្ឋានកាបូបមួយផ្សេងទៀត មុនពេលអ្នកបិទដំណើរការការទូទាត់ប្រាក់។ ប្រសិនបើអ្នកជ្រើសរើសមិនផ្ទេរប្រាក់របស់អ្នកឥឡូវនេះ ប្រាក់នឹងនៅតែមាននៅក្នុងកាបូបរបស់អ្នកដែលភ្ជាប់ទៅ Molly ប្រសិនបើអ្នកដំណើរការការទូទាត់ឡើងវិញ។ + អ្នកគួរតែផ្ទេរប្រាក់របស់អ្នកទៅកាន់អាសយដ្ឋានកាបូបមួយផ្សេងទៀត មុនពេលបិទដំណើរការការបង់ប្រាក់។ ប្រសិនបើអ្នកជ្រើសរើសមិនផ្ទេរប្រាក់របស់អ្នកឥឡូវនេះទេ ប្រាក់នោះនឹងនៅតែមាននៅក្នុងកាបូបរបស់អ្នកដែលបានភ្ជាប់ជាមួយ Molly ប្រសិនបើអ្នកសម្រេចឲ្យការបង់ប្រាក់បើកដំណើរការឡើងវិញ។ ផ្ទេរសមតុល្យដែលនៅសល់ បិទដំណើរការដោយមិនចាំបាច់ផ្ទេរ បិទ បិទដំណើរការដោយមិនចាំបាច់ផ្ទេរ? - សមតុល្យរបស់អ្នកនឹងនៅក្នុងកាបូបលុយដែលអ្នកភ្ជាប់ជាមួយ Molly ប្រសិនបើអ្នកសម្រេចអោយការទូទាត់ប្រាក់ដំណើរការឡើងវិញ។ + សមតុល្យរបស់អ្នកនឹងនៅតែមាននៅក្នុងកាបូបរបស់អ្នកដែលបានភ្ជាប់ជាមួយ Molly ប្រសិនបើអ្នកសម្រេចឲ្យការបង់ប្រាក់បើកដំណើរការឡើងវិញ។ មានបញ្ហាក្នុងការបិទកាបូបលុយ @@ -3906,12 +3977,12 @@ បង្កើត​គណនី - បានហាមឃាត់ + បានទប់ស្កាត់ %1$d លេខទំនាក់ទំនង ការផ្ញើសារ សារដែលបាត់ទៅវិញ សន្តិសុខកម្មវិធី - បិទថតរូបអេក្រង់ក្នុងបញ្ជីថ្មី និងក្នុងកម្មវិធី + ទប់ស្កាត់រូបថតអេក្រង់នៅក្នុងបញ្ជីថ្មីៗនេះ និងក្នុងកម្មវិធី សារ និងការហៅ Signal តែងតែបញ្ជូនការហៅទូរសព្ទ និងអ្នកផ្ញើដែលបិទជិត ពេលវេលាតាមលំនាំដើមសម្រាប់ការសន្ទនាថ្មីៗ កំណត់ថេរវេលានៃការលុបសារសម្រាប់រាល់ការសន្ទនាថ្មីៗដែលចាប់ផ្ដើមដោយអ្នក @@ -4007,7 +4078,7 @@ ពេលក្រោយ - ដាក់រូបប្រតិកម្មតាមចំនូលចិត្ត + ដាក់រូបប្រតិកម្មតាមបំណង ចុចដើម្បីដាក់រូបតំណាងអារម្មណ៍ជំនួស កំណត់ឡើងវិញ រក្សាទុក @@ -4068,7 +4139,7 @@ បិទសំឡេង - បានបិទសម្លេង + បានបិទសំឡេង ស្វែងរក សារបាត់ទៅវិញ @@ -4076,10 +4147,10 @@ ​ព័ត៌មានលម្អិតនៃលេខទំនាក់ទំនង មើលលេខសុវត្ថិភាព - រារាំង - ហាមឃាត់ក្រុម - បើកវិញ - លែងហាមឃាត់ក្រុម + ទប់ស្កាត់ + ទប់ស្កាត់ក្រុម + ឈប់ទប់ស្កាត់ + ឈប់ទប់ស្កាត់ក្រុម បញ្ចូលទៅក្នុងក្រុម មើលទាំងអស់ បន្ថែមសមាជិក @@ -4088,8 +4159,8 @@ តំណភ្ជាប់របស់ក្រុម បញ្ចូលជាលេខទំនាក់ទំនង បើកសំឡេង - ការសន្ទនានេះត្រូវបានបិទសម្លេងរហូតដល់%1$s - ការសន្ទនានេះត្រូវបានបិទសម្លេងជារៀងរហូត + ការសន្ទនាត្រូវបានបិទសំឡេងរហូតដល់ %1$s + ការសន្ទនាត្រូវបានបិទសំឡេងជារៀងរហូត ថតចម្លងលេខទូរស័ព្ទ លេខទូរស័ព្ទ ទទួលបានស្លាកសម្រាប់ប្រូហ្វាល់របស់អ្នកតាមរយៈការគាំទ្រដល់ Signal។ សូមចុចលើស្លាកដើម្បីស្វែងយល់បន្ថែម។ @@ -4105,8 +4176,8 @@ អ្នកណាអាចផ្ញើសារបានខ្លះ? - បិទសំឡេងសារជូនដំណឹង - មិនបិទសំឡេង + បិទសំឡេងការជូនដំណឹង + មិនបានបិទសំឡេង ហៅ តែងតែជូនដំណឹង កុំជូនដំណឹង @@ -4135,7 +4206,7 @@ ដកចេញ - រារាំង + ទប់ស្កាត់ ដក %1$s ចេញឬ? @@ -4405,7 +4476,7 @@ ការបរិច្ចាគរបស់អ្នកមិនអាចផ្ញើបានទេ ដោយសារមានបញ្ហាបណ្តាញ។ សូមពិនិត្យមើលសេវាអ៊ីនធឺណិតរបស់អ្នក រួចព្យាយាមម្តងទៀត។ - ការបរិច្ចាគទៅ %1$s + ការបរិច្ចាគតាងនាមឲ្យ %1$s %1$s បានបរិច្ចាគទៅ Signal ជំនួសឲ្យអ្នក @@ -4796,9 +4867,9 @@ ជ្រើសរើសអ្នកដែលអាចមើលរឿងរ៉ាវរបស់អ្នកបាន។ ការផ្លាស់ប្តូរនានានឹងមិនប៉ះពាល់ដល់រឿងរ៉ាវដែលអ្នកបានផ្ញើហើយនោះទេ។ - ការឆ្លើយតប និងការប្រតិកម្ម + ការឆ្លើយតប និងប្រតិកម្ម - អនុញ្ញាតការឆ្លើយតប និងការប្រតិកម្ម + អនុញ្ញាតការឆ្លើយតប និងប្រតិកម្ម អនុញ្ញាតឲ្យមនុស្សដែលអាចមើលរឿងរ៉ាវរបស់អ្នក ប្រតិកម្ម និងឆ្លើយតបបាន @@ -5201,7 +5272,7 @@ ការនាំចេញសារជាអក្សរ - វាអាចចំណាយពេលបន្តិច + វាអាចនឹងចំណាយពេលបន្តិច កំពុងនាំចេញ %1$d នៃ %2$d … @@ -5476,5 +5547,15 @@ លុបឈ្មោះអ្នកប្រើ + + + ម៉ោង + + នាទី + + កំណត់ + + រយៈពេលអប្បបរមាមុនពេលចាក់សោអេក្រង់ គឺ 1 នាទី។ + diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 0799fb2c34..ce4aed44ac 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -14,6 +14,7 @@ + ಹೌದು ಇಲ್ಲ @@ -98,9 +99,9 @@ ನಿರ್ಬಂಧಿಸಿದ ಬಳಕೆದಾರರು ನಿರ್ಬಂಧಿಸಿದ ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ - ನಿರ್ಬಂಧಿಸಿದ ಬಳಕೆದಾರರು ನಿಮಗೆ ಕರೆ ಮಾಡಲು ಅಥವಾ ನಿಮಗೆ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. + ನಿರ್ಬಂಧಿಸಿದ ಬಳಕೆದಾರರು ನಿಮಗೆ ಕರೆ ಮಾಡಲು ಅಥವಾ ನಿಮಗೆ ಮೆಸೇಜ್‌ಗಳನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. ನಿರ್ಬಂಧಿಸಿದ ಬಳಕೆದಾರರು ಇಲ್ಲ - ಬಳಕೆದಾರರನ್ನು ನಿರ್ಬಂಧಿಸುವುದೆ? + ಬಳಕೆದಾರರನ್ನು ನಿರ್ಬಂಧಿಸಬೇಕೇ? \"%1$s\" ಇವರು ನಿಮಗೆ ಸಂದೇಶಗಳನ್ನು ಕಳಿಸಲು ಅಥವಾ ಕರೆ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ ನಿರ್ಬಂಧಿಸಿ @@ -138,8 +139,8 @@ ಮುಂದುವರಿಸಿ - %1$s ಅನ್ನು ನಿರ್ಬಂಧಿಸುವುದೇ ಮತ್ತು ತೊರೆಯುವುದೇ? - %1$s ನಿರ್ಬಂಧಿಸುವುದೇ? + %1$s ಅನ್ನು ನಿರ್ಬಂಧಿಸಬೇಕೇ ಮತ್ತು ತೊರೆಯಬೇಕೇ? + %1$s ನಿರ್ಬಂಧಿಸಬೇಕೇ? ನೀವು ಇನ್ನು ಮುಂದೆ ಈ ಗುಂಪಿನಿಂದ ಸಂದೇಶಗಳು ಅಥವಾ ನವೀಕರಣಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ, ಮತ್ತು ಸದಸ್ಯರು ನಿಮ್ಮನ್ನು ಮತ್ತೆ ಈ ಗುಂಪಿಗೆ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. ಗುಂಪು ಸದಸ್ಯರಿಗೆ ನಿಮ್ಮನ್ನು ಮತ್ತೆ ಈ ಗುಂಪಿಗೆ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. ಗುಂಪಿನ ಸದಸ್ಯರು ನಿಮ್ಮನ್ನು ಪುನಃ ಈ ಗುಂಪಿಗೆ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. @@ -147,15 +148,15 @@ ನೀವು ಪರಸ್ಪರ ಸಂದೇಶ ಕಳುಹಿಸಲು ಮತ್ತು ಕರೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಅವರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲಾಗುತ್ತದೆ. ಪರಸ್ಪರ ಸಂದೇಶ ಕಳುಹಿಸಲು ನಿಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ. - ನಿರ್ಬಂಧಿಸಿದ ಜನರಿಗೆ ನಿಮಗೆ ಕರೆ ಮಾಡಲು ಅಥವಾ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. - ನಿಮಗೆ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ನಿರ್ಬಂಧಿತ ಜನರಿಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. + ನಿಮಗೆ ಕರೆ ಮಾಡಲು ಅಥವಾ ಮೆಸೇಜ್‌ಗಳನ್ನು ಕಳುಹಿಸಲು ನಿರ್ಬಂಧಿಸಿದ ಜನರಿಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. + ನಿಮಗೆ ಮೆಸೇಜ್‌ಗಳನ್ನು ಕಳುಹಿಸಲು ನಿರ್ಬಂಧಿತ ಜನರಿಗೆ ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. ನಿರ್ಬಂಧವು Signal ಅಪ್‌ಡೇಟ್‌ಗಳು ಮತ್ತು ಸುದ್ದಿಯನ್ನು ಪಡೆಯುತ್ತದೆ. ಪುನರಾರಂಭವು Signal ಅಪ್‌ಡೇಟ್‌ಗಳು ಮತ್ತು ಸುದ್ದಿಯನ್ನು ಪಡೆಯುತ್ತದೆ. - %1$sನಿರ್ಬಂಧ ತೆಗೆಯುವುದೇ? + %1$sನಿರ್ಬಂಧ ತೆಗೆಯಬೇಕೇ? ನಿರ್ಬಂಧಿಸಿ - ನಿರ್ಬಂಧಿಸಿ ಹಾಗು ಹೊರಹೋಗಿ + ನಿರ್ಬಂಧಿಸಿ ಹಾಗು ತೊರೆಯಿರಿ ಸ್ಪ್ಯಾಮ್ ವರದಿ ಮಾಡಿ ಮತ್ತು ಬ್ಲಾಕ್ ಮಾಡಿ @@ -447,11 +448,11 @@ %1$s ಆನ್ - ನಿರ್ಬಂಧ ವಿನಂತಿ? + ವಿನಂತಿಯನ್ನು ನಿರ್ಬಂಧಿಸಬೇಕೇ? %1$s ಅವರು ಗ್ರೂಪ್ ಲಿಂಕ್ ಮೂಲಕ ಈ ಗ್ರೂಪ್‌ಗೆ ಸೇರಲು ಸಾಧ್ಯವಿಲ್ಲ ಅಥವಾ ಸೇರಲು ವಿನಂತಿಸಲೂ ಸಾಧ್ಯವಿಲ್ಲ. ಅವರು ಈಗಲೂ ಮ್ಯಾನುವಲ್ ಆಗಿ ಗ್ರೂಪ್‌ಗೆ ಸೇರಬಹುದು. - ನಿರ್ಬಂಧ ವಿನಂತಿ + ವಿನಂತಿಯನ್ನು ನಿರ್ಬಂಧಿಸಿ ರದ್ದುಗೊಳಿಸಿ @@ -480,19 +481,19 @@ %1$d ಸಂಭಾಷಣೆಗಳನ್ನು ಇನ್‍ಬಾಕ್ಸ್ ಗೆ ಸರಿಸಲಾಗಿದೆ - ಓದಿ - ಓದಿ + ಓದಿರುವುದು + ಓದಿರುವುದು ಓದದಿರುವುದು ಓದದಿರುವುದು - ಪಿನ್ - ಪಿನ್ + PIN + PIN - ಅನ್‌ಪಿನ್ ಮಾಡು + ಅನ್‌ಪಿನ್ ಮಾಡಿ ಅನ್‌ಪಿನ್ ಮಾಡಿ @@ -503,7 +504,7 @@ ಅನ್‌ಮ್ಯೂಟ್ ಮಾಡಿ ಅನ್‌ಮ್ಯೂಟ್ ಮಾಡಿ - ಆಯ್ಕೆ ಮಾಡಿ + ಆಯ್ಕೆಮಾಡಿ ಆರ್ಕೈವ್ ಮಾಡಿ ಆರ್ಕೈವ್ ಮಾಡಿ @@ -542,6 +543,15 @@ +%1$d + + ನಿಮ್ಮ ಸಾಧನಗಳನ್ನು ಮರುಲಿಂಕ್ ಮಾಡಿ + + ನಿಮ್ಮ ಸಾಧನವನ್ನು ನೋಂದಾಯಿಸದಿದ್ದಾಗ ನೀವು ಸೇರಿಸಿದ ಸಾಧನಗಳು ಅನ್‌ಲಿಂಕ್ ಆಗಿವೆ. ಯಾವುದೇ ಸಾಧನವನ್ನು ಮರುಲಿಂಕ್ ಮಾಡಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಎಂಬಲ್ಲಿಗೆ ಹೋಗಿ. + + ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ + + ನಂತರ + ಸದಸ್ಯರನ್ನು ಆಯ್ಕೆಮಾಡಿ @@ -935,7 +945,7 @@ \@ಉಲ್ಲೇಖಗಳು ಬಂದರೆ ಎಚ್ಚರಿಸು - ಮ್ಯೂಟ್ ಮಾಡಿದ ಚಾಟ್‌ಗಳಲ್ಲಿ ನಿಮ್ಮನ್ನು ಉಲ್ಲೇಖಿಸಿದಾಗ ಅಧಿಸೂಚನೆಗಳನ್ನು ಸ್ವೀಕರಿಸಿ + ಮ್ಯೂಟ್ ಮಾಡಿದ ಚಾಟ್‌ಗಳಲ್ಲಿ ನಿಮ್ಮನ್ನು ಉಲ್ಲೇಖಿಸಿದಾಗ ಅಧಿಸೂಚನೆಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದೇ? ಯಾವಾಗಲೂ ನನಗೆ ಸೂಚನೆ ನೀಡಿ ನನಗೆ ಸೂಚನೆ ನೀಡಬೇಡಿ @@ -953,6 +963,16 @@ Username ರಚಿಸಲಾಗಿದೆ Username ಅನ್ನು ನಕಲಿಸಲಾಗಿದೆ + + ಯೂಸರ್‌ನೇಮ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ. + + ಯೂಸರ್‌ನೇಮ್ ಅಳಿಸಲಾಗಿದೆ + + + + ನಿಮ್ಮ ಯೂಸರ್‌ನೇಮ್‌ನಲ್ಲಿ ಏನೋ ತಪ್ಪಾಗಿದೆ, ಅದನ್ನು ಇನ್ನುಮುಂದೆ ನಿಮ್ಮ ಖಾತೆಗೆ ನಿಯೋಜಿಸಲಾಗುವುದಿಲ್ಲ. ನೀವು ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ ಸೆಟ್ ಮಾಡಬಹುದು ಅಥವಾ ಹೊಸತನ್ನು ಆಯ್ಕೆ ಮಾಡಬಹುದು. + + ಈಗಲೇ ಸರಿಪಡಿಸಿ @@ -1156,8 +1176,8 @@ ಹೊಸ ಗುಂಪು ಸ್ನೇಹಿತರನ್ನು ಆಹ್ವಾನಿಸಿ ಎಸ್‌ಎಂಎಸ್ ಬಳಸಿ - ಲಕ್ಷಣ - ಫೋಟೋ ಸೇರಿಸಿ + ಚಾಟ್ ಬಣ್ಣಗಳು + ಪ್ರೊಫೈಲ್ ಫೋಟೋ ಸೇರಿಸಿ ಪ್ರತಿಕ್ರಿಯೆಗಳು @@ -1472,10 +1492,10 @@ ನಿರ್ಬಂಧ ತೆಗೆಯಿರಿ %1$s ಅವರು ನಿಮಗೆ ಮೆಸೇಜ್ ಮಾಡಲು ಮತ್ತು ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಅವರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲು ಅನುಮತಿಸುತ್ತೀರಾ? ನೀವು ಸಮ್ಮತಿಸುವವರೆಗೆ ಸಂದೇಶವನ್ನು ಓದಿದ್ದೀರಿ ಎಂದು ಅವರು ತಿಳಿಯುವುದಿಲ್ಲ. - %1$s ಅವರು ನಿಮಗೆ ಮೆಸೇಜ್ ಮಾಡಲು ಮತ್ತು ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಅವರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲು ಅನುಮತಿಸುತ್ತೀರಾ? ನೀವು ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ಯಾವುದೇ ಸಂದೇಶ ನಿಮಗೆ ಬರುವುದಿಲ್ಲ. + %1$s ಅವರು ನಿಮಗೆ ಮೆಸೇಜ್ ಮಾಡಲು ಮತ್ತು ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಅವರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲು ಅನುಮತಿಸುತ್ತೀರಾ? ನೀವು ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ನೀವು ಯಾವುದೇ ಮೆಸೇಜ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. - %1$s ಅವರು ನಿಮಗೆ ಸಂದೇಶ ಕಳುಹಿಸಬಹುದೇ? ನೀವು ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವ ತನಕ ನೀವು ಯಾವುದೇ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. - %1$s ಅವರಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳು ಮತ್ತು ಸುದ್ದಿ ಪಡೆಯುವುದೇ? ನೀವು ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವ ತನಕ ನೀವು ಯಾವುದೇ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. + %1$s ಅವರು ನಿಮಗೆ ಮೆಸೇಜ್ ಕಳುಹಿಸಬಹುದೇ? ನೀವು ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ನೀವು ಯಾವುದೇ ಮೆಸೇಜ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. + %1$s ಅವರಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳು ಮತ್ತು ಸುದ್ದಿ ಪಡೆಯಬೇಕೇ? ನೀವು ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ನೀವು ಯಾವುದೇ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. ಈ ಗುಂಪಿನೊಂದಿಗೆ ನಿಮ್ಮ ಚಾಟ್ ಅನ್ನು ಮುಂದುವರಿಸುವುದೇ ಹಾಗೂ ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಇದರ ಸದಸ್ಯರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳುವುದೇ? \@ಮೆನ್ಷನ್‌ಗಳು ಮತ್ತು ಅಡ್ಮಿನ್‌ಗಳಂತಹ ಹೊಸ ಫೀಚರ್‌ಗಳನ್ನು ಆಕ್ಟಿವೇಟ್ ಮಾಡಲು ಈ ಗ್ರೂಪ್‌ ಅನ್ನು ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಿ. ಈ ಗುಂಪಿನಲ್ಲಿ ತಮ್ಮ ಹೆಸರು ಅಥವಾ ಫೋಟೋವನ್ನು ಹಂಚಿಕೊಳ್ಳದ ಸದಸ್ಯರಿಗೆ ಸೇರುವಂತೆ ಆಹ್ವಾನಿಸಲಾಗುತ್ತದೆ. ಈ ಲೆಗಸಿ ಗ್ರೂಪ್ ಅನ್ನು ಇನ್ನು ಬಳಸಲಾಗದು. ಯಾಕೆಂದರೆ, ಇದು ತುಂಬಾ ದೊಡ್ಡದಾಗಿದೆ. ಗರಿಷ್ಠ ಗ್ರೂಪ್ ಗಾತ್ರವು %1$d. @@ -1483,7 +1503,7 @@ ಈ ಗುಂಪನ್ನು ಸೇರುವುದೇ ಹಾಗೂ ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಇದರ ಸದಸ್ಯರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳುವುದೇ? ನೀವು ಸ್ವೀಕರಿಸುವವರೆಗೂ ಅವರಿಗೆ ನೀವು ಅವರ ಸಂದೇಶಗಳನ್ನು ನೋಡಿದ್ದೀರಿ ಎನ್ನುವುದು ತಿಳಿಯುವುದಿಲ್ಲ. ಈ ಗ್ರೂಪ್‌ಗೆ ಸೇರಿ ಮತ್ತು ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೊಟೋವನ್ನು ಇದರ ಸದಸ್ಯರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳುವುದೇ? ನೀವು ಸ್ವೀಕರಿಸುವವರೆಗೂ ನೀವು ಅವರ ಸಂದೇಶಗಳನ್ನು ನೋಡುವುದಿಲ್ಲ. ಈ ಗುಂಪನ್ನು ಸೇರುತ್ತೀರಾ? ನೀವು ಸ್ವೀಕರಿಸುವವರೆಗೂ ಅವರಿಗೆ ನೀವು ಅವರ ಸಂದೇಶಗಳನ್ನು ನೋಡಿದ್ದೀರಿ ಎನ್ನುವುದು ತಿಳಿಯುವುದಿಲ್ಲ. - ಈ ಗುಂಪನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವುದೇ ಹಾಗೂ ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಇದರ ಸದಸ್ಯರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳುವುದೇ? ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ನೀವು ಯಾವುದೇ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. + ಈ ಗ್ರೂಪ್ ಅನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡಬೇಕೇ ಹಾಗೂ ನಿಮ್ಮ ಹೆಸರು ಮತ್ತು ಫೋಟೋವನ್ನು ಇದರ ಸದಸ್ಯರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಬೇಕೇ? ಅವರನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ನೀವು ಯಾವುದೇ ಮೆಸೇಜ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುವುದಿಲ್ಲ. ತೋರಿಸು %1$s ರ ಸದಸ್ಯ @@ -1584,9 +1604,20 @@ ಹೊಸ ಪಿನ್ ರಚಿಸಿ + + SMS ಕೋಡ್ ಕಳುಹಿಸಿ + + Signal ನೋಂದಣಿ - Android ಗೆ PIN ಮರುನೋಂದಾಯಿಸುವ ಕುರಿತು ಸಹಾಯ ಬೇಕಿದೆ + + ನಿಮ್ಮ PIN ನೀವು ರಚಿಸಿದ %1$d+ ಅಂಕಿಯ ಕೋಡ್ ಆಗಿದ್ದು, ಅದು ಅಂಕಿಗಳಾಗಿರಬಹುದು ಅಥವಾ ಅಕ್ಷರಅಂಕಿ ಸಂಯೋಜನೆಯಾಗಿರಬಹುದು.\n\nನಿಮ್ಮ PIN ನಿಮಗೆ ನೆನಪಾಗದಿದ್ದರೆ, ನೀವು ಹೊಸತನ್ನು ರಚಿಸಬಹುದು. + + ನಿಮ್ಮ PIN ನಿಮಗೆ ನೆನಪಾಗದಿದ್ದರೆ, ನೀವು ಹೊಸತನ್ನು ರಚಿಸಬಹುದು. + + ನಿಮ್ಮPIN ಊಹೆಗಳ ಅವಕಾಶ ಮುಗಿದಿದೆ, ಹೊಸ PIN ರಚಿಸುವ ಮೂಲಕ ನಿಮ್ಮ Signal ಖಾತೆಗೆ ಈಗಲೂ ಪ್ರವೇಶಿಸಬಹುದು. + ಎಚ್ಚರಿಕೆ - ನೀವು ಪಿನ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದರೆ, ನೀವು Signal ಅನ್ನು ಮರುನೋಂದಾಯಿಸುವಾಗ ಮ್ಯಾನ್ಯುಅಲ್ ಆಗಿ ಎಲ್ಲಾ ಡೇಟಾ ಬ್ಯಾಕಪ್ ಮತ್ತು ರಿಸ್ಟೋರ್ ಮಾಡದಿದ್ದರೆ, ನೀವು ಅದನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ. ಪಿನ್ ನಿಷ್ಕ್ರಿಯವಾಗಿರುವಾಗ ನೀವು ನೋಂದಣಿ ಲಾಕ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + ನೀವು PIN ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದರೆ, ನೀವು Signal ಅನ್ನು ಮರುನೋಂದಾಯಿಸುವಾಗ ಮ್ಯಾನ್ಯುವಲ್ ಆಗಿ ಎಲ್ಲಾ ಡೇಟಾ ಬ್ಯಾಕಪ್ ಮತ್ತು ರಿಸ್ಟೋರ್ ಮಾಡದಿದ್ದರೆ, ನೀವು ಅದನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ. PIN ನಿಷ್ಕ್ರಿಯವಾಗಿರುವಾಗ ನೀವು ನೋಂದಣಿ ಲಾಕ್ ಅನ್ನು ಆನ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. ಪಿನ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ @@ -1768,11 +1799,18 @@ ಸ್ನೇಹಿತರೊಂದಿಗೆ ಕನೆಕ್ಟ್ ಆಗಲು ಮತ್ತು ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ನಿಮಗೆ ನೆರವಾಗಲು Signal ಗೆ ಸಂಪರ್ಕಗಳು ಮತ್ತು ಮಾಧ್ಯಮ ಅನುಮತಿಗಳು ಬೇಕಾಗುತ್ತವೆ. Signal ನ ಗೌಪ್ಯ ಸಂಪರ್ಕ ಅನ್ವೇಷಣೆಯನ್ನು ಬಳಸಿಕೊಂಡು ನಿಮ್ಮ ಸಂಪರ್ಕಗಳು ಅಪ್‌ಲೋಡ್ ಆಗಿವೆ, ಇದರರ್ಥ, ಅವುಗಳು ಎಂಡ್-ಟು-ಎಂಡ್ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಆಗಿವೆ ಮತ್ತು Signal ಸೇವೆಗೆ ಯಾವತ್ತೂ ಕಾಣಿಸುವುದಿಲ್ಲ. ಸ್ನೇಹಿತರೊಂದಿಗೆ ಕನೆಕ್ಟ್ ಆಗಲು ನಿಮಗೆ ನೆರವಾಗಲು Signal ಗೆ ಸಂಪರ್ಕಗಳು ಮತ್ತು ಮಾಧ್ಯಮ ಅನುಮತಿ ಬೇಕಾಗುತ್ತದೆ. Signal ನ ಗೌಪ್ಯ ಸಂಪರ್ಕ ಅನ್ವೇಷಣೆಯನ್ನು ಬಳಸಿಕೊಂಡು ನಿಮ್ಮ ಸಂಪರ್ಕಗಳು ಅಪ್‌ಲೋಡ್ ಆಗಿವೆ, ಇದರರ್ಥ, ಅವುಗಳು ಎಂಡ್-ಟು-ಎಂಡ್ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಆಗಿವೆ ಮತ್ತು Signal ಸೇವೆಗೆ ಯಾವತ್ತೂ ಕಾಣಿಸುವುದಿಲ್ಲ. ಈ ಸಂಖ್ಯೆಯನ್ನು ರಿಜಿಸ್ಟರ್ ಮಾಡಲು ನೀವು ತುಂಬಾ ಪ್ರಯತ್ನಗಳನ್ನು ಮಾಡಿದ್ದೀರಿ. ದಯವಿಟ್ಟು ಪುನಃ + + ಈ ಸಂಖ್ಯೆಯನ್ನು ನೋಂದಾಯಿಸಲು ನೀವು ತುಂಬಾ ಪ್ರಯತ್ನಗಳನ್ನು ಮಾಡಿದ್ದೀರಿ. ದಯವಿಟ್ಟು %1$s ನಲ್ಲಿ ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ. ಸೇವೆಗೆ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ದಯವಿಟ್ಟು ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕವನ್ನು ಪರಿಶೀಲಿಸಿ ಮತ್ತು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. ಪ್ರಮಾಣೀಕೃತವಲ್ಲದ ಸಂಖ್ಯಾ ಸ್ವರೂಪ ನೀವು ನಮೂದಿಸಿದ ಸಂಖ್ಯೆ (%1$s) ಪ್ರಮಾಣೀಕೃತವಲ್ಲದ ಸ್ವರೂಪದಲ್ಲಿ ಕಾಣಿಸುತ್ತಿದೆ.\n\nನೀವು ಅಂದುಕೊಂಡಿದ್ದು ಇದೇನಾ %2$s? Molly ಆಂಡ್ರಾಯ್ಡ್ - ಫೋನ್ ಸಂಖ್ಯೆ ಸ್ವರೂಪ + ಕರೆ ವಿನಂತಿಸಲಾಗಿದೆ + + SMS ವಿನಂತಿಸಲಾಗಿದೆ + + ದೃಢೀಕರಣ ಕೋಡ್ ವಿನಂತಿಸಲಾಗಿದೆ ನೀವು ಈಗ %1$d ಎ ಸಲ್ಲಿಸುವುದರಿಂದ ದೂರವಿರಿಡೀಬಗ್ ಲಾಗ್. ನೀವು ಡಿಬಗ್ ಲಾಗ್ ಅನ್ನು ಸಲ್ಲಿಸುವುದರಿಂದ %1$d ಹೆಜ್ಜೆಗಳಷ್ಟು ದೂರದಲ್ಲಿದ್ದೀರಿ. @@ -1792,6 +1830,16 @@ ಕರೆ ದೃಢೀಕರಣ ಕೋಡ್ ಕೋಡ್ ಅನ್ನು ಪುನಃ ಕಳುಹಿಸಿ + + ನೋಂದಾಯಿಸುವಲ್ಲಿ ಸಮಸ್ಯೆ ಎದುರಿಸುತ್ತಿದ್ದೀರಾ? + + • ನಿಮ್ಮ SMS ಅಥವಾ ಕರೆಯನ್ನು ಸ್ವೀಕರಿಸಲು ನಿಮ್ಮ ಫೋನ್ ಸೆಲ್ಯುಲಾರ್ ಸಿಗ್ನಲ್ ಅನ್ನು ಹೊಂದಿದೆ ಎಂದು ಖಚಿತಪಡಿಸಿ\n • ಈ ಸಂಖ್ಯೆಗೆ ನೀವು ಫೋನ್ ಕರೆಯನ್ನು ಸ್ವೀಕರಿಸಬಹುದು ಎಂದು ಖಚಿತಪಡಿಸಿ\n • ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಸರಿಯಾಗಿ ನಮೂದಿಸಿದ್ದೀರಾ ಎಂದು ಪರಿಶೀಲಿಸಿ. + + ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ದಯವಿಟ್ಟು ಈ ಸಮಸ್ಯೆ ಪರಿಹಾರದ ಹಂತಗಳನ್ನು ಅನುಸರಿಸಿ ಅಥವಾ ಬೆಂಬಲವನ್ನು ಸಂಪರ್ಕಿಸಿ + + ಈ ಸಮಸ್ಯೆ ಪರಿಹಾರದ ಹಂತಗಳನ್ನು + + ಸಂಪರ್ಕ ಬೆಂಬಲ ರಿಜಿಸ್ಟ್ರೇಶನ್‌ ಲಾಕ್ ಆನ್‌ ಮಾಡುವುದೇ? @@ -1951,13 +1999,17 @@ ನೀವು ಒಂದು ಬ್ಯಾಡ್ಜ್ ಅನ್ನು ರಿಡೀಮ್ ಮಾಡಿದ್ದೀರಿ - ನಿಮ್ಮ ಸ್ಟೋರಿಗೆ %1$s ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ + %1$s ಅವರು ನಿಮ್ಮ ಸ್ಟೋರಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ - ಅವರ ಸ್ಟೋರಿಗೆ %1$s ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ + %1$s ಅವರು ತಮ್ಮ ಸ್ಟೋರಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ ಪಾವತಿ ನಿಗದಿಗೊಳಿಸಿದ ಮೆಸೇಜ್ + + ನಿಮ್ಮ ಸಂದೇಶ ಇತಿಹಾಸವನ್ನು ವಿಲೀನಗೊಳಿಸಲಾಗಿದೆ + + %1$s ಯು %2$s ಅವರದ್ದಾಗಿದೆ Molly ನವೀಕರಿಸಿ @@ -2087,14 +2139,16 @@ ಅಸುರಕ್ಷಿತ ಎಸ್‌ಎಂಎಸ್ %1$s %2$s ಸಂಪರ್ಕ - %1$s ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ: \"%2$s\". - %1$s ನಿಮ್ಮ ವೀಡಿಯೊಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ - %1$s ನಿಮ್ಮ ಚಿತ್ರಕ್ಕೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. - ನಿಮ್ಮ GIF ಗೆ %1$s ಅವರು ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. - %1$s ನಿಮ್ಮ ಫೈಲ್ ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. - %1$s ನಿಮ್ಮ ಆಡಿಯೊಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. - ನಿಮ್ಮ ವ್ಯೂ-ಒನ್ಸ್ ಮೀಡಿಯಾಕ್ಕೆ %1$s ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. - %1$s ನಿಮ್ಮ ಸ್ಟಿಕ್ಕರ್‌ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ + %1$s ಅವರು ಇವರಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ: \"%2$s\". + %1$s ಅವರು ನಿಮ್ಮ ವೀಡಿಯೊಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + %1$s ಅವರು ನಿಮ್ಮ ಚಿತ್ರಕ್ಕೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + %1$s ಅವರು ನಿಮ್ಮ GIF ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + %1$s ಅವರು ನಿಮ್ಮ ಫೈಲ್‌ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + %1$s ಅವರು ನಿಮ್ಮ ಆಡಿಯೊಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + %1$s ಅವರು ನಿಮ್ಮ ವ್ಯೂ-ಒನ್ಸ್ ಮೀಡಿಯಾಕ್ಕೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + + %1$s ಅವರು ನಿಮ್ಮ ಪಾವತಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. + %1$s ಅವರು ನಿಮ್ಮ ಸ್ಟಿಕ್ಕರ್‌ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿದ್ದಾರೆ. ಈ ಮೆಸೇಜನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಸಂಪರ್ಕ Signal ಗೆ ಸೇರಿದ ನೊಟಿಫಿಕೇಶನ್‌ಗಳನ್ನು ಆಫ್ ಮಾಡುವುದೇ? Signal > ಸೆಟ್ಟಿಂಗ್‌ಗಳು > ನೊಟಿಫಿಕೇಶನ್‌ಗಳಲ್ಲಿ ಇದನ್ನು ನೀವು ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು. @@ -2298,7 +2352,7 @@ ಕಾಲ್ ನೊಟಿಫಿಕೇಶನ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ ಸಂಪರ್ಕ ನವೀಕರಿಸಿ - ನಿರ್ಬಂಧ ವಿನಂತಿ + ವಿನಂತಿಯನ್ನು ನಿರ್ಬಂಧಿಸಿ ಯಾವುದೇ ಗ್ರೂಪ್‌ನಲ್ಲಿ ನೀವಿಬ್ಬರೂ ಇಲ್ಲ. ವಿನಂತಿಗಳನ್ನು ಎಚ್ಚರಿಕೆಯಿಂದ ಪರಿಶೀಲಿಸಿ. ಈ ಗ್ರೂಪ್‌ನಲ್ಲಿ ಯಾವುದೇ ಸಂಪರ್ಕಗಳಿಲ್ಲ. ವಿನಂತಿಗಳನ್ನು ಎಚ್ಚರಿಕೆಯಿಂದ ಪರಿಶೀಲಿಸಿ. ತೋರಿಸು @@ -2487,8 +2541,8 @@ ಡೆಲಿವರಿ ಸಮಸ್ಯೆ - ಮೆಸೇಜ್, ಸ್ಟಿಕ್ಕರ್, ಪ್ರತಿಸ್ಪಂದನೆ ಅಥವಾ ರೀಡ್ ರಿಸಿಟ್ ಅನ್ನು %1$s ಇಂದ ನಿಮಗೆ ಡೆಲಿವರಿ ಮಾಡಲಾಗದು. ಅವರು ನೇರವಾಗಿ ಅಥವಾ ಗ್ರೂಪ್‌ನಲ್ಲಿ ನಿಮಗೆ ಕಳುಹಿಸಲು ಪ್ರಯತ್ನಿಸಿರಬಹುದು. - ಮೆಸೇಜ್, ಸ್ಟಿಕ್ಕರ್, ಪ್ರತಿಸ್ಪಂದನೆ ಅಥವಾ ರೀಡ್ ರಿಸಿಟ್ ಅನ್ನು %1$s ಇಂದ ನಿಮಗೆ ಡೆಲಿವರಿ ಮಾಡಲಾಗದು. + ಮೆಸೇಜ್, ಸ್ಟಿಕ್ಕರ್, ಪ್ರತಿಕ್ರಿಯೆ ಅಥವಾ ಓದಿದ ರಸೀದಿಯನ್ನು %1$s ಅವರಿಂದ ನಿಮಗೆ ಡೆಲಿವರಿ ಮಾಡಲಾಗಲಿಲ್ಲ. ಅವರು ನೇರವಾಗಿ ಅಥವಾ ಗ್ರೂಪ್‌ನಲ್ಲಿ ನಿಮಗೆ ಕಳುಹಿಸಲು ಪ್ರಯತ್ನಿಸಿರಬಹುದು. + ಮೆಸೇಜ್, ಸ್ಟಿಕ್ಕರ್, ಪ್ರತಿಕ್ರಿಯೆ ಅಥವಾ ಓದಿದ ರಸೀದಿಯನ್ನು %1$s ಅವರಿಂದ ನಿಮಗೆ ಡೆಲಿವರಿ ಮಾಡಲಾಗಲಿಲ್ಲ. ಮೊದಲ ಹೆಸರು (ಅಗತ್ಯವಿದೆ) @@ -2635,10 +2689,10 @@ ಡೀಫಾಲ್ಟ್ ಬಳಸಿ ಕಸ್ಟಮ್ ಬಳಸಿ - 1 ಗಂಟೆ ಮ್ಯೂಟ್ ಮಾಡಿ - 8 ಗಂಟೆಗಳವರೆಗೆ ಮ್ಯೂಟ್ ಮಾಡಿ + 1 ಗಂಟೆಯ ಕಾಲ ಮ್ಯೂಟ್ ಮಾಡಿ + 8 ಗಂಟೆಗಳ ಕಾಲ ಮ್ಯೂಟ್ ಮಾಡಿ 1 ದಿನದ ಕಾಲ ಮ್ಯೂಟ್ ಮಾಡಿ - 7 ದಿನಗಳವರೆಗೆ ಮ್ಯೂಟ್ ಮಾಡಿ + 7 ದಿನಗಳ ಕಾಲ ಮ್ಯೂಟ್ ಮಾಡಿ ಎಂದಿಗೂ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳು ಡೀಫಾಲ್ಟ್ @@ -2675,9 +2729,9 @@ ಅಡ್ರೆಸ್ ಬುಕ್ ಫೋಟೋಗಳನ್ನು ಬಳಸಿ ಲಭ್ಯವಿದ್ದರೆ ನಿಮ್ಮ ಅಡ್ರೆಸ್ ಬುಕ್‌ನಿಂದ ಕಾಂಟ್ಯಾಕ್ಟ್ ಫೋಟೋಗಳನ್ನು ಡಿಸ್‌ಪ್ಲೇ ಮಾಡುತ್ತದೆ - Keep Muted Chats Archived + ಮ್ಯೂಟ್ ಮಾಡಿದ ಚಾಟ್‌ಗಳನ್ನು ಆರ್ಕೈವ್ ಆಗಿರಿಸಿ - Muted chats that are archived will remain archived when a new message arrives. + ಹೊಸ ಮೆಸೇಜ್ ಬಂದಾಗ, ಆರ್ಕೈವ್ ಆಗಿರುವ ಮ್ಯೂಟ್ ಮಾಡಿದ ಚಾಟ್‌ಗಳು ಆರ್ಕೈವ್ ಆಗಿಯೇ ಇರುತ್ತವೆ. ಲಿಂಕ್ ಪೂರ್ವವೀಕ್ಷಣೆಗಳನ್ನು ರಚಿಸಿ ನೀವು ಕಳುಹಿಸುವ ಸಂದೇಶಗಳಿಗಾಗಿ ಲಿಂಕ್ ಪೂರ್ವವೀಕ್ಷಣೆಗಳನ್ನು ವೆಬ್‌ಸೈಟ್‌ಗಳಿಂದ ನೇರವಾಗಿ ಹಿಂಪಡೆಯಿರಿ. ಪಾಸ್‌‌ಫ್ರೇಸ್ ಬದಲಾಯಿಸಿ @@ -2971,7 +3025,7 @@ ಪೇಮೆಂಟ್ ಕಳುಹಿಸಲಾಗಿದೆ ಪೇಮೆಂಟ್ ಸ್ವೀಕರಿಸಲಾಗಿದೆ ಪೇಮೆಂಟ್ ಪೂರ್ಣಗೊಂಡಿದೆ %1$s - ಸಂಖ್ಯೆಯನ್ನು ಬ್ಲಾಕ್ ಮಾಡಿ + ಸಂಖ್ಯೆಯನ್ನು ನಿರ್ಬಂಧಿಸಿ ವರ್ಗಾವಣೆ @@ -3295,6 +3349,8 @@ ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ ನೀವು ಖಾತೆಗಾಗಿ ರಚಿಸಿದ ಪಿನ್ ಅನ್ನು ನಮೂದಿಸಿ. ಇದು ಎಸ್‌ಎಂಎಸ್‌ ದೃಢೀಕರಣ ಕೋಡ್ ಗಿಂತ ಭಿನ್ನವಾಗಿರುತ್ತದೆ. + + ನಿಮ್ಮ ಖಾತೆಗಾಗಿ ನೀವು ರಚಿಸಿದ PIN ನಮೂದಿಸಿ. ಆಲ್ಫಾನ್ಯೂಮರಿಕ್ ಪಿನ್ ನಮೂದಿಸಿ ಸಂಖ್ಯಾ ಪಿನ್ ನಮೂದಿಸಿ ತಪ್ಪಾದ ಪಿನ್. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. @@ -3398,7 +3454,10 @@ ನಿಮ್ಮ ಬ್ಯಾಕಪ್, ಬ್ಯಾಕಪ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲದ‌ ತೀರಾ ದೊಡ್ಡ ಫೈಲ್ ಅನ್ನು ಹೊಂದಿದೆ. ದಯವಿಟ್ಟು ಅದನ್ನು ಅಳಿಸಿ ಮತ್ತು ಹೊಸತೊಂದು ಬ್ಯಾಕಪ್ ರಚಿಸಿ. ಬ್ಯಾಕಪ್‌ಗಳನ್ನು ನಿರ್ವಯಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಸಂಖ್ಯೆ ತಪ್ಪಾಗಿದೆಯೇ? + (%1$02d:%2$02d) ರ ಬಳಿಕ ನನಗೆ ಕರೆ ಮಾಡಿ + + ಕೋಡ್ ಮರುಕಳುಹಿಸಿ (%1$02d:%2$02d) ಸಂಪರ್ಕಿಸಿ Signal ಬೆಂಬಲ Signal ನೋಂದಣಿ - ಆಂಡ್ರಾಯ್ಡ್‌ಗೆ ದೃಢೀಕರಣ ಕೋಡ್ ತಪ್ಪಾದ ಕೋಡ್ @@ -3406,6 +3465,18 @@ ತಿಳಿಯದಿಲ್ಲ ನನ್ನ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ನೋಡಿ ಫೋನ್ ಸಂಖ್ಯೆಯ ಮೂಲಕ ನನ್ನನ್ನು ಹುಡುಕಿ + + ದೂರವಾಣಿ ಸಂಖ್ಯೆ + + ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಯಾರು ನೋಡಬಹುದು ಮತ್ತು ಅದರ ಮೂಲಕ Molly ನಲ್ಲಿ ನಿಮ್ಮನ್ನು ಯಾರು ಸಂಪರ್ಕಿಸಬಹುದು ಎಂಬುದನ್ನು ಆಯ್ಕೆಮಾಡಿ. + + ನನ್ನ ಸಂಖ್ಯೆಯನ್ನು ಯಾರು ನೋಡಬಹುದು + + Molly ನಲ್ಲಿ ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಯಾರೂ ನೋಡಲಾಗುವುದಿಲ್ಲ + + ಸಂಖ್ಯೆಯ ಮೂಲಕ ನನ್ನನ್ನು ಯಾರು ಕಂಡುಹಿಡಿಯಬಹುದು + + ನೀವು ಸಂದೇಶ ಕಳುಹಿಸುವ ಜನರಿಗೆ ಮತ್ತು ಗುಂಪುಗಳಿಗೆ ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆ ಕಾಣಿಸುತ್ತದೆ. ತಮ್ಮ ಫೋನ್ ಸಂಪರ್ಕಗಳಲ್ಲಿ ನಿಮ್ಮ ಸಂಖ್ಯೆಯನ್ನು ಹೊಂದಿರುವ ಜನರೂ ಸಹ Molly ನಲ್ಲಿ ಅದನ್ನು ನೋಡುತ್ತಾರೆ. ಎಲ್ಲರೂ ನನ್ನ ಸಂಪರ್ಕಗಳು ಯಾರೂ ಇಲ್ಲ @@ -3615,9 +3686,9 @@ %1$s/%2$s/ - \"%1$s\" ರನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. - \"%1$s\" ರನ್ನು ನಿರ್ಬಂಧಿಸಲು ಆಗಲಿಲ್ಲ - \"%1$s\" ರನ್ನು ಅನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. + \"%1$s\" ಅವರನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. + \"%1$s\" ಅವರನ್ನು ನಿರ್ಬಂಧಿಸಲು ಆಗಲಿಲ್ಲ + \"%1$s\" ಅವರ ನಿರ್ಬಂಧ ತೆಗೆಯಲಾಗಿದೆ. ಸದಸ್ಯರನ್ನು ಪರಿಶೀಲಿಸಿ @@ -3677,7 +3748,7 @@ ಯಾವುದೇ ಸಂಖ್ಯೆ ನಿರ್ದಿಷ್ಟಪಡಿಸಿಲ್ಲ ನೀವು ನಮೂದಿಸಿದ ಫೋನ್ ನಿಮ್ಮ ಖಾತೆಗೆ ಹೋಲಿಕೆಯಾಗುತ್ತಿಲ್ಲ. ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಅಳಿಸಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ? - ಇದು ನಿಮ್ಮ Signal ಖಾತೆಯನ್ನು ಅಳಿಸುತ್ತದೆ ಮತ್ತು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ರಿಸೆಟ್ ಮಾಡುತ್ತದೆ. ಪ್ರಕ್ರಿಯೆ ಮುಗಿದ ನಂತರ ಆಪ್ ಮುಚ್ಚುತ್ತದೆ. + ಇದು ನಿಮ್ಮ Signal ಖಾತೆಯನ್ನು ಅಳಿಸುತ್ತದೆ ಮತ್ತು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ರಿಸೆಟ್ ಮಾಡುತ್ತದೆ. ಪ್ರಕ್ರಿಯೆ ಮುಗಿದ ನಂತರ ಆ್ಯಪ್ ಮುಚ್ಚುತ್ತದೆ. ಸ್ಥಳೀಯ ಡೇಟಾ ಅಳಿಸಲು ವಿಫಲವಾಗಿದೆ. ಸಿಸ್ಟಮ್ ಅಪ್ಲಿಕೇಶನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ನೀವು ಮ್ಯಾನ್ಯುವಲ್ ಆಗಿ ತೆರವುಗೊಳಿಸಬಹುದು. ಆಪ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ @@ -3784,12 +3855,12 @@ ವಾಲೆಟ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ನಿಮ್ಮ ಬ್ಯಾಲೆನ್ಸ್ - ಪೇಮೆಂಟ್ಸ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುವದಕ್ಕೂ ಮುನ್ನ ಇನ್ನೊಂದು ವಾಲೆಟ್‌ಗೆ ನಿಮ್ಮ ಹಣವನ್ನು ವರ್ಗಾವಣೆ ಮಾಡಿಕೊಳ್ಳುವುದನ್ನು ನಿಮಗೆ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ. ಈಗ ನಿಮ್ಮ ಹಣವನ್ನು ವರ್ಗಾವಣೆ ಮಾಡದಿರಲು ನೀವು ಆಯ್ಕೆ ಮಾಡಿಕೊಂಡರೆ, ಪೇಮೆಂಟ್‌ಗಳನ್ನು ನೀವು ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಿದರೆ Molly ಗೆ ಲಿಂಕ್‌ ಮಾಡಿದ ನಿಮ್ಮ ವಾಲೆಟ್‌ನಲ್ಲಿಯೇ ಅವು ಇರುತ್ತವೆ. + ಪಾವತಿಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುವುದಕ್ಕೂ ಮುನ್ನ ಇನ್ನೊಂದು ವಾಲೆಟ್ ವಿಳಾಸಕ್ಕೆ ನಿಮ್ಮ ಹಣವನ್ನು ವರ್ಗಾವಣೆ ಮಾಡಿಕೊಳ್ಳುವುದನ್ನು ನಿಮಗೆ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ. ಈಗ ನಿಮ್ಮ ಹಣವನ್ನು ವರ್ಗಾವಣೆ ಮಾಡದಿರಲು ನೀವು ಆಯ್ಕೆ ಮಾಡಿಕೊಂಡರೆ, ಪಾವತಿಗಳನ್ನು ನೀವು ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಿದರೆ Molly ಗೆ ಲಿಂಕ್‌ ಮಾಡಿದ ನಿಮ್ಮ ವಾಲೆಟ್‌ನಲ್ಲಿಯೇ ಅವು ಇರುತ್ತವೆ. ಉಳಿದ ಬ್ಯಾಲೆನ್ಸ್ ಅನ್ನು ವರ್ಗಾವಣೆ ಮಾಡಿ ವರ್ಗಾಯಿಸದೆ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ವರ್ಗಾವಣೆ ಮಾಡದೇ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುವುದೇ? - ನೀವು ಪೇಮೆಂಟ್ಸ್ ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಲು ನೀವು ಆಯ್ಕೆ ಮಾಡಿಕೊಂಡರೆ, Molly ಗೆ ಲಿಂಕ್ ಮಾಡಿದ ನಿಮ್ಮ ವಾಲೆಟ್‌ನಲ್ಲಿ ನಿಮ್ಮ ಬ್ಯಾಲೆನ್ಸ್ ಇರುತ್ತದೆ. + ನೀವು ಪಾವತಿಗಳನ್ನು ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಲು ಆಯ್ಕೆ ಮಾಡಿಕೊಂಡರೆ, Molly ಗೆ ಲಿಂಕ್ ಮಾಡಿದ ನಿಮ್ಮ ವಾಲೆಟ್‌ನಲ್ಲಿ ನಿಮ್ಮ ಬ್ಯಾಲೆನ್ಸ್ ಇರುತ್ತದೆ. ವಾಲೆಟ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುವಲ್ಲಿ ದೋಷ. @@ -4008,7 +4079,7 @@ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ ಕಣ್ಮರೆಯಾಗುವ ಸಂದೇಶಗಳು ಆಪ್ ಸುರಕ್ಷತೆ - ಇತ್ತೀಚಿನ ಪಟ್ಟಿಯಲ್ಲಿ ಮತ್ತು ಆ್ಯಪ್ ಒಳಗೆ ಸ್ಕ್ರೀನ್ ಶಾಟ್ ನಿರ್ಬಂಧಿಸಿ + ಇತ್ತೀಚಿನ ಪಟ್ಟಿಯಲ್ಲಿ ಮತ್ತು ಆ್ಯಪ್‌ನ ಒಳಗೆ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ Signal ಮೆಸೇಜ್‌ಗಳು ಮತ್ತು ಕಾಲ್‌ಗಳು, ಎಂದಿಗೂ ಕಾಲ್‌ಗಳನ್ನು ರಿಲೇ ಮಾಡಿ ಮತ್ತು ಸೀಲ್ ಮಾಡಿದ ಕಳುಹಿಸುವವರು ಹೊಸ ಚಾಟ್‌ಗಳಿಗೆ ಡೀಫಾಲ್ಟ್ ಟೈಮರ್ ನೀವು ಆರಂಭಿಸಿದ ಎಲ್ಲ ಹೊಸ ಚಾಟ್‌ಗಳಿಗೆ ಡೀಫಾಲ್ಟ್ ಕಣ್ಮರೆಯಾಗುವ ಮೆಸೇಜ್ ಟೈಮರ್ ಅನ್ನು ನಿಗದಿಸಿ. @@ -4106,7 +4177,7 @@ ಈಗಲ್ಲ - ಪ್ರತಿಸ್ಪಂದನೆಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ + ಪ್ರತಿಕ್ರಿಯೆಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ ಎಮೋಜಿ ಬದಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ ಮರುಹೊಂದಿಸಿ ಉಳಿಸಿ @@ -4176,9 +4247,9 @@ ಸಂಪರ್ಕ ವಿವರಗಳು ಸುರಕ್ಷತಾ ಸಂಖ್ಯೆ ನೋಡಿ ನಿರ್ಬಂಧಿಸಿ - ಗುಂಪನ್ನು ನಿರ್ಬಂಧಿಸಿ + ಗ್ರೂಪ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಿ ನಿರ್ಬಂಧ ತೆಗೆಯಿರಿ - ಗುಂಪನ್ನು ಅನ್‌ಬ್ಲಾಕ್ ಮಾಡಿ + ಗ್ರೂಪ್‌ನ ನಿರ್ಬಂಧ ತೆಗೆಯಿರಿ ಗುಂಪಿಗೆ ಸೇರಿಸಿ ಎಲ್ಲವನ್ನು ನೋಡಿ ಸದಸ್ಯರನ್ನು ಸೇರಿಸಿ @@ -4188,7 +4259,7 @@ ಸಂಪರ್ಕವಾಗಿ ಸೇರಿಸಿ ಅನ್‌ಮ್ಯೂಟ್ ಮಾಡಿ %1$s ವರೆಗೆ ಸಂಭಾಷಣೆಯನ್ನು ಮ್ಯೂಟ್ ಮಾಡಲಾಗಿದೆ - ಸಂಭಾಷಣೆಯನ್ನು ಎಂದಿಗೂ ಮ್ಯೂಟ್ ಮಾಡಲಾಗಿದೆ + ಸಂಭಾಷಣೆಯನ್ನು ಶಾಶ್ವತವಾಗಿ ಮ್ಯೂಟ್ ಮಾಡಲಾಗಿದೆ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ಫೋನ್ ನಂಬರ್ ಅನ್ನು ನಕಲಿಸಲಾಗಿದೆ ದೂರವಾಣಿ ಸಂಖ್ಯೆ Signal ಅನ್ನು ಬೆಂಬಲಿಸುವ ಮೂಲಕ ನಿಮ್ಮ ಪ್ರೊಫೈಲ್‌ಗೆ ಬ್ಯಾಡ್ಜ್‌ಗಳನ್ನು ಪಡೆಯಿರಿ. ಇನ್ನಷ್ಟು ತಿಳಿಯಲು ಬ್ಯಾಡ್ಜ್ ಮೇಲೆ ಟ್ಯಾಪ್ ಮಾಡಿ. @@ -4242,7 +4313,7 @@ %1$s ಅವರನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ - %1$s ಅವರನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ. + %1$s ಅವರನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ %1$s ಅವರನ್ನು ತೆಗೆದುಹಾಕಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ @@ -4508,7 +4579,7 @@ ನೆಟ್‌ವರ್ಕ್ ದೋಷದಿಂದಾಗಿ ನಿಮ್ಮ ದೇಣಿಗೆಯನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ನಿಮ್ಮ ಸಂಪರ್ಕವನ್ನು ಪರಿಶೀಲಿಸಿ ಹಾಗೂ ಇನ್ನೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ. - %1$s ಅವರಿಗೆ ದೇಣಿಗೆ + %1$s ಅವರ ಪರವಾಗಿ ದೇಣಿಗೆ %1$s ಅವರು ನಿಮ್ಮ ಪರವಾಗಿ ಸಿಗ್ನಲ್‌ಗೆ ದೇಣಿಗೆ ನೀಡಿದ್ದಾರೆ @@ -4905,9 +4976,9 @@ ನಿಮ್ಮ ಸ್ಟೋರಿಯನ್ನು ಯಾರು ನೋಡಬಹುದು ಎಂದು ಆಯ್ಕೆ ಮಾಡಿ. ನೀವು ಈಗಾಗಲೇ ಕಳುಹಿಸಿದ ಸ್ಟೋರೀಸ್ ಮೇಲೆ ಬದಲಾವಣೆಗಳು ಪರಿಣಾಮ ಬೀರುವುದಿಲ್ಲ. - ಉತ್ತರಗಳು & ಪ್ರತಿಕ್ರಿಯೆಗಳು + ಉತ್ತರಗಳು ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆಗಳು - ಉತ್ತರಗಳು & ಪ್ರತಿಕ್ರಿಯೆಗಳನ್ನು ಅನುಮತಿಸಿ + ಉತ್ತರಗಳು ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆಗಳನ್ನು ಅನುಮತಿಸಿ ನಿಮ್ಮ ಸ್ಟೋರಿಯನ್ನು ವೀಕ್ಷಿಸಬಹುದಾದ ಜನರು ಪ್ರತಿಕ್ರಿಯಿಸಲಿ ಮತ್ತು ಉತ್ತರಿಸಲಿ @@ -5601,5 +5672,15 @@ ಯೂಸರ್‌ನೇಮ್ ಅಳಿಸಿ + + + h + + m + + ಹೊಂದಿಸಿ + + ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ವಯವಾಗುವ ಮುನ್ನ ಕನಿಷ್ಠ ಸಮಯ 1 ನಿಮಿಷ. + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 0b5a06f633..4c5631e2d6 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -14,6 +14,7 @@ + 아니요 @@ -96,11 +97,11 @@ 새로운 메시지를 확인 중입니다… - 차단된 사용자 - 차단된 사용자 추가 - 차단한 사용자는 나에게 전화 또는 메시지를 보낼 수 없습니다. - 차단된 사용자 없음 - 이 사용자를 차단하시겠습니까? + 차단한 사용자 + 차단한 사용자 추가 + 차단한 사용자는 나에게 전화를 걸거나 메시지를 보낼 수 없습니다. + 차단한 사용자 없음 + 이 사용자를 차단할까요? \'%1$s\' 님은 나에게 전화를 걸거나 메시지를 보낼 수 없습니다. 차단 @@ -138,8 +139,8 @@ 계속 - %1$s를 차단하고 나갈까요? - %1$s를 차단할까요? + %1$s을(를) 차단하고 나갈까요? + %1$s을(를) 차단할까요? 이제 이 그룹에서 메시지나 업데이트를 받을 수 없습니다. 그리고 멤버가 당신을 이 그룹에 추가할 수도 없습니다. 그룹 멤버가 당신을 이 그룹에 추가할 수 없습니다, 그룹 멤버는 당신을 그룹에 다시 초대 가능합니다. @@ -147,15 +148,15 @@ 서로 메시지를 보내고 전화를 걸 수 있으며 이름과 사진이 공유됩니다. 이제 서로 메시지를 할 수 있습니다. - 차단당한 사람들은 당신에게 전화를 걸거나 메세지를 보낼 수 없습니다. - 차단된 사람들은 당신에게 메시지를 보낼 수 없습니다. + 차단한 사용자는 나에게 전화를 걸거나 메세지를 보낼 수 없습니다. + 차단한 사용자는 나에게 메시지를 보낼 수 없습니다. - Signal의 업데이트와 새로운 소식을 받지 않기 + Signal의 업데이트와 새로운 소식을 차단합니다. Signal의 업데이트와 새로운 소식을 다시 받기 - %1$s를 차단 해제할까요? + %1$s을(를) 차단 해제할까요? 차단 - 그룹 나가고 차단하기 + 차단하고 나가기 스팸 신고 및 차단 @@ -366,7 +367,7 @@ 미디어 전송 오류 - 스팸으로 보고하고 차단하기 + 스팸으로 신고하고 차단했습니다. SMS 메시지를 현재 사용할 수 없습니다. 메시지를 휴대폰의 다른 앱으로 내보낼 수 있습니다. @@ -445,7 +446,7 @@ %1$s는 그룹 링크를 통해 그룹에 참가하거나 참가 요청을 보낼 수 없습니다. 하지만 그룹에 추가할 수 있습니다. - 요청이 차단되었습니다. + 요청 차단 취소 @@ -522,6 +523,15 @@ +%1$d + + 기기 다시 연결 + + 기기 등록을 해제하여 기기 연결이 해제됐습니다. 설정으로 가서 기기를 다시 연결하세요. + + 설정 열기 + + 나중에 + 멤버 선택 @@ -897,7 +907,7 @@ 멘션 알림 - 알림을 끈 대화에서 멘션되면 알림을 받으시겠습니까? + 알림을 끈 대화에서 멘션 될 경우 알림을 받아보시겠어요? 항상 알림받기 알림 받지않기 @@ -915,6 +925,16 @@ 사용자 이름을 만들었습니다. 사용자 이름을 복사했습니다. + + 사용자 이름을 삭제하지 못했습니다. 나중에 다시 시도하세요. + + 사용자 이름을 삭제했습니다. + + + + 사용자 이름에 문제가 생겼습니다. 이 사용자 이름이 더는 계정에 사용되지 않습니다. 다시 설정하려고 시도하거나 새로운 사용자 이름을 선택하세요. + + 지금 해결 @@ -1108,8 +1128,8 @@ 새 그룹 친구 초대 SMS 사용하기 - 디스플레이 - 사진 추가 + 채팅 색 + 프로필 사진 추가 회신 @@ -1414,10 +1434,10 @@ 차단 해제 %1$s 님에게 메시지를 보내고 이름과 사진을 공유할 수 있도록 하시겠습니까? 귀하가 수락할 때까지 귀하가 자신의 메시지를 본 것을 알지 못합니다. - %1$s 님에게 메시지를 보내고 이름과 사진을 공유할 수 있도록 하시겠습니까? 차단을 해제할 때까지 메시지를 받지 않습니다. + %1$s 님이 내게 메시지를 보내고 내 이름과 사진을 공유할 수 있도록 허용할까요? 차단을 해제할 때까지 모든 메시지를 차단합니다. - %1$s가 당신에게 메시지를 보낼 수 있도록 하시겠어요? 차단을 해제하기 전 까지 어떤 메시지도 받을 수 없습니다. - %1$s로부터 업데이트와 새로운 소식을 받으시겠어요? 차단을 해제하기 전 까지 어떤 업데이트도 받을 수 없습니다. + %1$s 님이 내게 메시지를 보내도록 허용할까요? 차단을 해제할 때까지 모든 메시지를 차단합니다. + %1$s의 업데이트와 메시지를 받아보시겠어요? 차단을 해제할 때까지 모든 업데이트를 차단합니다. 이 그룹에서 계속 대화하고 멤버들과 이름과 사진을 공유하시겠습니까? \@멘션 및 관리자와 같은 새로운 기능을 활성화하려면 그룹을 업그레이드하세요. 그룹에서 이름이나 사진을 공유하지 않은 멤버는 가입하도록 초대됩니다. 이 구형 그룹의 규모가 너무 커서 더 이상 사용할 수 없습니다. 최대 그룹 규모는 %1$d입니다. @@ -1425,7 +1445,7 @@ 그룹에 가입하고 멤버들과 이름과 사진을 공유하시겠습니까? 귀하가 수락할 때까지 귀하가 자신의 메시지를 본 것을 알지 못합니다. 이 그룹에 참가하여 그룹 멤버에게 내 이름과 사진을 공유하시겠어요? 수락하기 전까지는 멤버의 메시지가 표시되지 않습니다. 이 그룹에 참가하시겠습니까? 수락하기 전까지 상대는 내가 메시지를 보았는지 알 수 없습니다. - 이 그룹을 차단 해제하고 그룹 멤버에게 내 이름과 사진을 공유하시겠습니까? 차단을 해제하기 전까지는 메시지를 받지 않습니다. + 이 그룹을 차단 해제하고 그룹 멤버에게 내 이름과 사진을 공유할까요? 차단을 해제할 때까지 모든 메시지를 차단합니다. 보기 %1$s 멤버 @@ -1520,6 +1540,17 @@ 새로운 PIN 만들기 + + SMS 코드 보내기 + + Signal 등록 - Android 등록 PIN 관련 도움 + + PIN은 사용자가 만든 %1$d자리 이상의 영숫자 조합 코드입니다.\n\nPIN이 기억이 안 난다면 새로운 PIN을 만드세요. + + PIN이 기억이 안 난다면 새로운 PIN을 만드세요. + + PIN 시도 횟수를 초과했습니다. 하지만 새로운 PIN을 만들어 Signal 계정을 계속 이용할 수 있습니다. + 경고 PIN을 비활성화하면 수동으로 백업 및 복원하지 않는 한 Signal을 다시 등록할 때 모든 데이터가 손실됩니다. PIN이 비활성화되어 있는 동안에는 등록 잠금을 켤 수 없습니다. @@ -1644,7 +1675,7 @@ 알림 켜기 - 음소거 + 알림 끄기 전화벨 @@ -1656,12 +1687,12 @@ - %1$s 차단됨 + %1$s을(를) 차단함 자세한 정보 음성 또는 영상 통화를 더 이상 수신하지 않으며 차단된 사람도 나의 통화를 수신할 수 없습니다. %1$s 님의 음성 & 동영상 수신 불가 %1$s 님의 음성 및 동영상 수신 불가 - 이는 안전 번호 변경을 확인하지 않았거나, 기기에 문제가 있거나 사용자를 차단했기 때문일 수 있습니다. + 이는 해당 사용자가 안전 번호 변경을 확인하지 않았거나, 기기에 문제가 있거나 나를 차단했기 때문일 수 있습니다. 스와이프하여 화면 공유 보기 @@ -1698,11 +1729,18 @@ 친구를 추가하고 메시지를 보내려면 Signal에서 연락처와 미디어를 사용하도록 허용해야 합니다. 내 기기에 저장된 연락처는 Signal의 비공개 연락처 검색을 통해 업로드됩니다. 모든 연락처가 단대단 암호화 처리되기 때문에 Signal 서비스에는 절대 표시되지 않습니다. Signal에서 친구를 추가하려면 연락처 접근 권한을 허용해야 합니다. 내 기기에 저장된 연락처는 Signal의 비공개 연락처 검색을 통해 업로드됩니다. 모든 연락처가 단대단 암호화 처리되기 때문에 Signal 서비스에는 절대 표시되지 않습니다. 이 전화번호를 등록하려고 너무 많이 시도했습니다. 나중에 다시 시도하세요. + + 이 번호를 등록하려고 너무 많이 시도했습니다. %1$s 후에 다시 시도해 주세요. 서비스에 연결할 수 없습니다. 네트워크 연결을 확인 후 다시 시도해 주세요. 비표준 전화번호 형식 입력한 번호(%1$s)가 표준 형식이 아닙니다.\n\n혹시 입력하려던 번호가 %2$s인가요? Molly Android - 전화번호 형식 + 통화 요청함 + + SMS가 필요합니다. + + 확인 코드가 필요합니다. 디버그 로그를 보내기까지 %1$d단계 남았습니다. @@ -1721,6 +1759,16 @@ 전화 확인 코드 코드 다시 보내기 + + 등록에 어려움이 있나요? + + • SMS나 통화에 휴대폰에 셀룰러 데이터를 사용할 수 있는지 확인하세요.\n • 해당 번호로 전화를 받을 수 있는지 확인하세요.\n • 올바른 전화번호를 입력했는지 확인하세요. + + 자세한 내용은 다음 문제 해결 단계를 따르거나 지원팀에 문의하세요. + + 이 문제 해결 단계 + + 지원팀에 문의 등록 잠금을 켜시겠습니까? @@ -1887,6 +1935,10 @@ 결제 예약 메시지 + + 메시지 기록을 병합했습니다. + + %1$s번은 %2$s 님의 전화번호입니다. Molly 업데이트 @@ -1958,7 +2010,7 @@ 존재하지 않는 세션의 암호화 MMS 메시지 - 대화 알림 끄기 + 알림 끄기 가져오는 중 @@ -2015,14 +2067,16 @@ 비보안 SMS %1$s %2$s 연락처 - \'%2$s\'에 대해 %1$s로 리액션했습니다. - 동영상에 대해 %1$s로 리액션했습니다. - 이미지에 대해 %1$s로 리액션했습니다. - 나의 GIF에 %1$s 이모지로 반응했습니다. - 파일에 대해 %1$s로 리액션했습니다. - 오디오에 대해 %1$s로 리액션했습니다. - 조회 수 1회인 미디어에 %1$s으(로) 리액션했습니다. - 스티커에 대해 %1$s로 리액션했습니다. + \'%2$s\'에 %1$s 이모지로 반응했습니다. + 내 동영상에 %1$s 이모지로 반응했습니다. + 내 이미지에 %1$s 이모지로 반응했습니다. + 내 GIF에 %1$s 이모지로 반응했습니다. + 내 파일에 %1$s 이모지로 반응했습니다. + 내 오디오에 %1$s 이모지로 반응했습니다. + 한 번만 볼 수 있는 내 미디어에 %1$s 이모지로 반응했습니다. + + Reacted %1$s to your payment. + 내 스티커에 %1$s 이모지로 반응했습니다. 이 메시지는 삭제되었습니다. 연락처 인물이 Signal에 가입했다는 알림을 끄시겠습니까? Signal > 설정 > 알림에서 다시 활성화할 수 있습니다. @@ -2222,7 +2276,7 @@ 통화 알림 활성화 연락처 업데이트 - 요청이 차단되었습니다. + 요청 차단 공통된 그룹이 없습니다. 요청을 주의 깊게 검토하세요. 이 그룹에 연락처가 없습니다. 요청을 주의 깊게 검토하세요. 보기 @@ -2404,8 +2458,8 @@ 배송 문제 - %1$s에서 메시지, 스티커, 반응 또는 수신 확인을 전달할 수 없습니다. 사용자에게 직접 또는 그룹으로 전송하려고 했을 수 있습니다. - %1$s에서 메시지, 스티커, 반응 또는 수신 확인을 전달할 수 없습니다. + %1$s 님이 보낸 메시지, 스티커, 반응 또는 수신 확인을 받지 못했습니다. 사용자에게 직접 보내거나 그룹에서 보내려고 했을 수 있습니다. + %1$s 님이 보낸 메시지, 스티커, 반응 또는 수신 확인을 받지 못했습니다. 이름(필수) @@ -2472,7 +2526,7 @@ 대기 중 - 보냄 + 전송 대상 받음 전달됨 읽음 @@ -2591,9 +2645,9 @@ 주소록 사진 사용하기 가능할 경우 주소록에 있는 연락처 사진들을 보이기 - 음소거 채팅 보관 상태로 유지 + 알림 끈 채팅 보관 상태로 유지 - 보관된 음소거 채팅은 새 메시지가 도착해도 보관 상태로 유지됩니다. + 알림을 꺼서 보관한 채팅은 새 메시지가 도착해도 보관 상태로 유지됩니다. 링크 미리보기 생성 보내는 메시지에 첨부된 웹 사이트의 링크 미리보기를 불러옵니다. 암호 변경 @@ -2887,7 +2941,7 @@ 송금됨 결제 완료 결제 완료 %1$s - 블록 번호 + 번호 차단 이체 @@ -3207,6 +3261,8 @@ 번호 입력 계정용으로 생성한 번호를 입력하세요. SMS 인증 코드와는 다릅니다. + + 계정에 대해 만든 PIN을 입력하세요. 영숫자 번호 입력 숫자 번호 입력 잘못된 번호입니다. 다시 시도해 주세요. @@ -3305,7 +3361,10 @@ 백업에 대용량 파일이 포함되어 있습니다. 이 파일을 삭제하고 새로운 백업을 만드세요. 탭하여 백업을 관리하세요. 번호가 잘못되었나요? + 나한테 전화(%1$02d:%2$02d) + + 코드 다시 보내기(%1$02d:%2$02d) Signal 지원 팀에 문의 Signal 등록 - 안드로이드용 인증 코드 올바르지 않은 코드 @@ -3313,6 +3372,18 @@ 알 수 없음 내 전화번호 보기 전화번호로 나를 찾기 + + 전화번호 + + 내 전화번호를 볼 수 있고, Molly에서 내 전화번호로 나한테 연락할 수 있는 사람을 선택하세요. + + 내 전화번호를 볼 수 있는 사람 + + 아무도 Molly에서 내 전화번호를 볼 수 없습니다. + + 전화번호로 나를 찾을 수 있는 사람 + + 내가 메시지를 보낸 사람 또는 그룹에 내 전화번호가 표시됩니다. 내 번호를 휴대전화 연락처에 저장한 사람도 Molly에서 나를 볼 수 있습니다. 모두 내 연락처 아무도 @@ -3522,9 +3593,9 @@ %1$s/%2$s - \'%1$s\' 님이 차단되었습니다. - \'%1$s\' 차단 실패 - \'%1$s\' 님이 차단 해제되었습니다. + \'%1$s\' 님을 차단했습니다. + \'%1$s\' 님을 차단하지 못했습니다. + \'%1$s\' 님을 차단 해제했습니다. 멤버 검토 @@ -3572,17 +3643,17 @@ Wi-Fi 신호가 약합니다. 셀룰러 데이터로 전환하세요. - 계정을 삭제하면: + 계정을 삭제할 경우: 전화번호를 입력하세요. 계정 삭제 - 계정 정보 및 프로필 사진 삭제 - 모든 메시지 삭제 + 계정 정보와 프로필 사진을 삭제합니다. + 모든 메시지를 삭제합니다. 결제 계정에서 %1$s 삭제 지정된 국가 코드가 없습니다. 지정된 전화번호가 없습니다. 입력한 전화번호가 내 계정과 일치하지 않습니다. 계정을 삭제하시겠습니까? - 이후 Signal 계정이 삭제되고 애플리케이션이 재설정됩니다. 프로세스가 완료되면 앱이 종료됩니다. + 이 작업을 수행하면 Signal 계정을 삭제하고 애플리케이션을 초기화합니다. 프로세스를 완료하면 앱을 종료합니다. 로컬 데이터를 삭제하지 못했습니다. 시스템 애플리케이션 설정에서 수동으로 제거할 수 있습니다. 앱 설정 시작 @@ -3688,12 +3759,12 @@ 지갑 비활성화 잔액 - 결제를 비활성화하기 전에 다른 지갑 주소로 자금을 이체하는 것이 좋습니다. 지금 자금을 이체하지 않기로 선택하면 결제를 다시 활성화하면 Molly에 연결된 지갑에 남아 있게 됩니다. + 결제를 비활성화하기 전에 다른 지갑 주소로 자금을 이체하는 것이 좋습니다. 지금 자금을 이체하지 않기로 선택할 경우 결제를 다시 활성화하면 자금이 Molly에 연결된 지갑에 남아 있게 됩니다. 잔액 이체 전송하지 않고 비활성화 비활성화 이전하지 않고 비활성화하시겠습니까? - 결제를 다시 활성화하기로 선택한 경우 잔액은 Molly에 연결된 지갑에 남아 있습니다. + 결제를 다시 활성화하기로 선택할 경우 잔액이 Molly에 연결된 지갑에 남아 있게 됩니다. 지갑을 비활성화하는 동안 오류가 발생했습니다. @@ -3911,7 +3982,7 @@ 메시징 사라지는 메시지 애플리케이션 보안 - 최근 사용 목록/개요 및 앱 내 스크린샷 차단 + 최근 사용 목록과 앱 내 스크린샷 차단 신호 메시지 및 통화, 항상 중계 통화 및 봉인된 발신자 새 채팅을 위한 기본 타이머 내가 새로 시작한 모든 채팅에 기본적으로 사라지는 메시지 타이머를 적용합니다. @@ -4007,7 +4078,7 @@ 나중에 - 반응 사용자 정의 + 반응 사용자 지정 이모티콘을 바꾸려면 탭하세요. 초기화 저장 @@ -4077,9 +4148,9 @@ 연락처 안전 번호 보기 차단 - 그룹 차단하기 + 그룹 차단 차단 해제 - 그룹 차단 해제하기 + 그룹 차단 해제 그룹에 추가 모두 보기 멤버 추가 @@ -4088,8 +4159,8 @@ 그룹 링크 연락처로 추가 알림 켜기 - %1$s까지 대화 음소거 - 대화가 영원히 음소거됨 + %1$s까지 대화 알림을 끕니다. + 대화 알림을 계속 끕니다. 전화번호를 클립보드에 복사했습니다. 전화번호 Signal을 후원하고 배지를 받으세요. 배지를 탭하여 자세히 알아보세요. @@ -4105,7 +4176,7 @@ 누가 메시지를 전송할 수 있습니까? - 대화 알림 끄기 + 알림 끄기 알림 꺼지지 않음 언급 항상 알림 @@ -4227,7 +4298,7 @@ 스토리에 추가 메시지 추가 답장 추가 - 다음으로 보내기 + 받는 사람 한 번 보기 메시지 하나 이상의 항목이 너무 큽니다. 하나 이상의 항목이 유효하지 않습니다. @@ -4362,7 +4433,7 @@ 결제를 처리할 수 없어 월간 기부를 취소했습니다. 배지가 더는 프로필에 표시되지 않습니다. 월간 기부를 취소했습니다. %1$s %2$s 배지가 더는 프로필에 표시되지 않습니다. - Signal을 계속 사용할 수는 있지만 앱을 지지하고 배지를 재활성화하려면 지금 갱신하세요. + Signal을 계속 사용할 수는 있지만 앱을 지원하고 배지를 재활성화하려면 지금 갱신하세요. 구독 갱신 Google Pay로 이동 @@ -4405,7 +4476,7 @@ 네트워크 오류로 인해 기부를 보내지 못했습니다. 연결을 확인하고 다시 시도하세요. - %1$s 님에게 기부 + %1$s 님 대신 기부 %1$s 님이 귀하를 대신해 Signal에 기부했습니다. @@ -4800,7 +4871,7 @@ 답장 및 반응 허용 - 스토리를 볼 수 있는 사용자의 반응 및 답장 허용 + 내 스토리를 볼 수 있는 사용자의 반응 및 답장 허용 Signal 커넥션 @@ -4949,9 +5020,9 @@ %1$s 님의 스토리에 반응했습니다. - 내 스토리에 반응함 + 내 스토리에 반응했습니다. - 스토리에 반응함 + 스토리에 반응했습니다. @@ -4975,7 +5046,7 @@ 기부를 확인해 주세요. - 다음으로 보내기 + 받는 사람 받는 사람에게 1대1 메시지로 기부 알림이 발송됩니다. 아래에 나만의 메시지를 추가하세요. @@ -5476,5 +5547,15 @@ 사용자 이름 삭제 + + + 시간 + + + + 설정 + + 화면 잠금을 적용하려면 최소 1분이 필요합니다. + diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml index 09ffd49516..8605f9ab88 100644 --- a/app/src/main/res/values-ky/strings.xml +++ b/app/src/main/res/values-ky/strings.xml @@ -14,6 +14,7 @@ + Жөнөтүү Жок @@ -522,6 +523,15 @@ +%1$d + + Түзмөктөрүңүздү кайра байланыштырыңыз + + Түзмөгүңүздү кайра каттап жатканда, буга чейин кошкон түзмөктөрүңүз ажырап калган. Аларды кайра байланыштыруу үчүн параметрлерге өтүңүз. + + Параметрлерди ачуу + + Азыр эмес + Мүчөлөрдү тандаңыз @@ -915,6 +925,16 @@ Колдонуучу аты түзүлдү Колдонуучу аты көчүрүлдү + + Колдонуучу аты өчкөн жок. Бир аздан соң кайталаңыз. + + Колдонуучу аты өчтү + + + + Колдонуучу атыңызда маселе жаралып, аккаунтуңузга байланбай калды. Дагы бир жолу кайталап көрүңүз же жаңысын түзүңүз. + + Оңдоо @@ -1108,8 +1128,8 @@ Жаңы топ Досторду чакыруу SMS колдонуу - Тышкы көрүнүшү - Сүрөт кошуу + Маектин түсү + Профиль сүрөтүн кошуу Жооптор @@ -1417,7 +1437,7 @@ %1$s сиз менен жазышып, атыңыз менен сүрөтүңүз ага көрүнө берсинби? Аны бөгөттөн чыгармайынча, билдирүүлөрдү албайсыз. %1$s сиз менен жазыша берсинби? Аны бөгөттөн чыгармайынча, билдирүүлөрдү албайсыз. - Get updates and news from %1$s ? Аны бөгөттөн чыгармайынча, билдирүүлөрдү албайсыз. + %1$s жаңыртууларын жана жаңылыктарын аласызбы? Аны бөгөттөн чыгармайынча, билдирүүлөрдү албайсыз. Бул топ менен сүйлөшүүнү улантып, атыңыз менен сүрөтүңүз топтогуларга көрүнө берсинби? \@айтыпөтүүлөр жана топтордун администраторлору сыяктуу жаңы функцияларды иштетүү үчүн бул топту жаңыртыңыз. Атын же сүрөтүн ушул топтогуларга ачыктай элек мүчөлөр топко кошулуу чакыруусун алышат. Бул эски топ өтө чоң болгондуктан, аны колдоно албайсыз. Топтун көлөмү %1$d ашпаш керек. @@ -1520,6 +1540,17 @@ Жаңы PIN код түзүү + + SMS аркылуу код жөнөтүү + + Signal кызматына катталуу - Android түзмөгүнө PIN кодду кайра каттоодо жардам берүү + + PIN кодуңуз %1$d жана өзүңүз түзгөн санарип код сандардан же сан менен тамгалардан турушу мүмкүн.\n\nЭгер PIN кодуңузду унутуп койсоңуз, жаңысын түзсөңүз болот. + + Эгер PIN кодуңузду унутуп койсоңуз, жаңысын түзсөңүз болот. + + PIN кодду эстегенге өтө көп жолу аракет кылдыңыз, эми Signal аккаунтуңузга жаңы PIN код түзүп кириңиз. + Эскертүү PIN кодду өчүрүп салсаңыз, Signal колдонмосун кайра орноткондо аккаунтуңуздагы бардык нерселер өчүп калат (эгер алардын көчүрмөсүн кол менен башка жерге сактап койбосоңуз). PIN код өчүк болгондо, Катталууга жол бербөө функциясын иштете албай каласыз. @@ -1698,11 +1729,18 @@ Досторуңуз менен байланышып, жазышуу мүмкүнчүлүгүн берүү үчүн Signal колдонмосуна байланыштарыңыз менен медиафайлдарыңыз керек. Байланыштарыңыз Signal\'дын байланыштарды купуя издеген технологиясы аркылуу жүктөлүп, баштан аяк шифрленгендиктен, Signal кызматына эч качан көрүнбөйт. Досторуңуз менен байланышуу мүмкүнчүлүгүн берүү үчүн Signal колдонмосуна байланыштарыңыз керек. Байланыштарыңыз Signal\'дын байланыштарды купуя издеген технологиясы аркылуу жүктөлүп, баштан аяк шифрленгендиктен, Signal кызматына эч качан көрүнбөйт. Бул номерди каттоого ашыкча көп ирет катар аракет жасадыңыз. Сураныч, кийинчерээк дагы сынап көрүңүз. + + Бул номерди каттаганга өтө көп жолу аракет кылдыңыз. %1$s кийин кайталап көрүңүз. Сервиске туташылбай жатат. Тармактык туташууну текшерип, дагы бир жолу аракет кылып көрүңүз. Телефон номери туура эмес Телефон номерин туура эмес киргиздиңиз окшойт (%1$s).\n\nМындай эмес беле %2$s? Molly Android - Телефон номеринин форматы + Чалуу суралды + + SMS аркылуу код сурадыңыз + + Ырастоо кодун сурадыңыз Сиз мүчүлүштүктөрдү оңдоо журналын тапшыруудан %1$dкадамдасыз. @@ -1721,6 +1759,16 @@ Чалуу Текшерүү коду Кодду кайра жөнөтүү + + Каттала албай жатасызбы? + + • SMS билдирүү алуу же чалуу үчүн телефонуңузда байланыш болушу керек\n • Адамдар телефон номериңизге чала алышы керек\n • Телефон номериңизди туура киргизиңиз. + + Кененирээк маалымат алуу үчүн төмөнкү кадамдарды аткарыңыз же Кардарларды тейлеген кызматка кайрылыңыз + + ушул кадамдарды + + Кардарларды колдоо кызматына кайрылуу Катталууну Кулпулоо колдонулсунбу? @@ -1880,13 +1928,17 @@ Төшбелги алдыңыз - Окуяңызга %1$s деген реакция кылды + Окуяңызга %1$s деген сезимин билдирди - Окуясына %1$s деген реакция кылды + Окуясына %1$s деген сезимин билдирди Төлөм Пландалган билдирүү + + Билдирүүлөрүңүздүн таржымалы бириктирилди + + %1$s номери %2$s деген кишиге таандык Molly\'ды жаңыртуу @@ -2015,14 +2067,16 @@ Корголбогон SMS %1$s %2$s Байланыш - Төмөнкүгө %1$s деп реакция кылды: \"%2$s\". - Видеоңузга %1$s деп реакция кылды. - Сүрөтүңүзгө %1$s деп реакция кылды. - GIF анимацияңызга %1$s деп реакция кылды. - Файлыңызга %1$s деп реакция кылды. - Аудиоңузга %1$s деп реакция кылды. - Бир жолу көрүлүүчү медиафайлыңызга %1$s деп реакция кылды. - Стикериңизге %1$s деп реакция кылды. + Төмөнкүгө %1$s деген сезимин билдирди: \"%2$s\". + Видеоңузга %1$s деген сезимин билдирди. + Сүрөтүңүзгө %1$s деген сезимин билдирди. + GIF анимацияңызга %1$s деген сезимин билдирди. + Файлыңызга %1$s деген сезимин билдирди. + Аудиоңузга %1$s деген сезимин билдирди. + Бир жолу көрүлүүчү медиа файлыңызга %1$s деген сезимин билдирди. + + Төлөмүңүзгө %1$s деген киши сезимин билдирди. + Стикериңизге %1$s деген сезимин билдирди. Бул билдирүү өчүрүлгөн. «Signal\'га байланыш кошулду» деген билдирмени өчүрөсүзбү? Аны кайра Signal\'дан > Тууралоо > Билдирмелер бөлүмүнөн иштете аласыз. @@ -2404,8 +2458,8 @@ Ката кетти - %1$s жөнөткөн билдирүү, стикер, реакция же окулду деген билдирме сизге жетпей калды. Ал бул нерселерди түздөн-түз сизге же кандайдыр бир топко жөнөткөнгө аракет кылган. - %1$s жөнөткөн билдирүү, стикер, реакция же окулду деген билдирме сизге жетпей калды. + %1$s жөнөткөн билдирүү, стикер, билдирген сезими же окулду деген билдирме сизге жетпей калды. Ал бул нерселерди түздөн-түз сизге же кандайдыр бир топко жөнөткөнгө аракет кылган. + %1$s жөнөткөн билдирүү, стикер, билдирген сезими же окулду деген билдирме сизге жетпей калды. Атыңыз (талап кылынат) @@ -2887,7 +2941,7 @@ Төлөм жөнөтүлдү Төлөм алынды Төлөм аяктады %1$s - Блоктун номери + Номерди бөгөттөө Которуу @@ -3207,6 +3261,8 @@ PIN-кодду киргизиңиз Жеке кабинетиңиз үчүн түзгөн PIN кодду киргизиңиз. Бул код SMS аркылуу келген текшерүү кодуңуздан айырмаланып турат. + + Аккаунтуңуздун PIN кодун киргизиниз. Тамгалык-цифралык PIN-кодду киргизиңиз Цифралык PIN-кодду киргизиңиз Туура эмес PIN-код. Кайра кайталаңыз. @@ -3305,7 +3361,10 @@ Камдык көчүрмөңүздө өтө көлөмдүү файл болгондуктан, ал түзүлгөн жок. Аны өчүрүп туруп, жаңысын түзүңүз. Камдык көчүрмөлөрдү башкаруу үчүн басыңыз. Номер туура эмеспи? + Мага чал (%1$02d:%2$02d) + + Код кайра жөнөтүлөт (%1$02d:%2$02d) Signal\'дын кардарларды колдоо кызматы менен байланышыңыз Signal\'ды каттоо - Android үчүн текшерүү коду Код туура эмес @@ -3313,6 +3372,18 @@ Белгисиз Менин телефон номеримди карай алат Мени телефон номери боюнча табуу + + Телефон номериңиз + + Телефон номериңиз кимдерге көрүнүп, Molly\'да сиз менен кимдер байланыша алышат. + + Менин номерим кимге көрүнөт + + Номериңиз Molly\'да эч кимге көрүнбөйт + + Номерим аркылуу мени ким таба алат + + Телефон номериңиз ким менен жазышсаңыз ошолорго көрүнөт. Байланыштар тизмесинде камтылган телефон номери Molly кызматында да көрүнөт. Баары Менин байланыштарым Эч ким @@ -3572,17 +3643,17 @@ Wi-Fi начар болгондуктан, мобилдик Интернетке которулдуңуз. - Аккаунтуңузду жок кылуу: + Аккаунтуңуз өчкөндө: Телефон номериңизди киргизиңиз Аккаунтту өчүрүү - Аккаунтуңуздун маалыматын жана профиль сүрөтүңүздү жок кылуу - Бардык билдирүүлөрүңүздү жок кылуу + Аккаунтуңуздун маалыматы жана профилдин сүрөтү жок болот + Бардык билдирүүлөрүңүз жок болот Төлөм аккаунтуңуздан %1$s жок кылуу Эч кандай өлкө коду көрсөтүлгөн эмес Эч кандай номер көрсөтүлгөн жок Сиз киргизген телефон номери аккаунтуңузга дал келбейт. Аккаунтуңузду чын эле жок кылгыңыз келеби? - Бул аракет Signal аккаунтуңузду жок кылат жана колдонмону баштапкы абалга келтирет. Процесс аяктагандан кийин колдонмо жабылат. + Ушуну менен, Signal аккаунтуңуз өчүп, колдонмо баштапкы абалга келет. Баары бүткөндөн кийин колдонмо жабылат. Жергиликтүү берилмелер жок кылынбай калды. Сиз аны кол менен системанын колдонмо тууралоосунан тазалай аласыз. Колдонмо тууралоосун баштоо @@ -3688,12 +3759,12 @@ Капчыкты өчүрүү Балансыңыз - Төлөмдөрдү өчүрүүдөн мурун акчаңызды башка капчык дарегине которуу сунушталат. Эгерде сиз акчаңызды азыр которбоону чечсеңиз, төлөмдөрдү кайра жандандырсаңыз, алар Molly менен байланышкан капчыгыңызда кала берет. + Төлөмдөрдү өчүрүүдөн мурун акчаңызды башка капчыкка которуп коюңуз. Эгер акчаңызды азыр которбосоңуз, төлөмдөрдү кайра иштеткенде, алар Molly\'га байланган капчыгыңызда кала берет. Калган балансты которуу Которуусуз өчүрүү Деактивдештирүү Которуусуз өчүрүлсүнбү? - Төлөмдөрдү кайра жандандырууну тандасаңыз, балансыңыз Molly менен байланышкан капчыгыңызда кала берет. + Төлөмдөрдү кайра иштеткениңизде, каражаттар Molly\'га байланган капчыгыңызда кала берет. Капчыкты өчүрүү катасы. @@ -3911,7 +3982,7 @@ Билдирүү алмашуу Жоголуп кетүүчү билдирүүлөр Колдонмонун коопсуздугу - Акыркы колдонмолор тизмесинде жана колдонмолордун ичинде скриншотторду кулпулайсыз + Соңку колдонмолор тизмесинде жана колдонмолордун ичинде скриншотторду кулпулайсыз Signal билдирүүлөрү жана чалуулар, чалууларды багыттоо жана жашыруун жөнөтүүчү Жаңы маектер үчүн демейки таймер Сиз баштаган бардык жаңы маектер үчүн демейки жоголуп кетүүчү билдирүүлөр таймерин коюңуз. @@ -4007,7 +4078,7 @@ Азыр эмес - Реакцияларды ыңгайлаштыруу + Сезимдер топтомун ыңгайлаштыруу Быйтыкчаны алмаштыруу үчүн басыңыз Баштапкы абалга келтирүү Сактоо @@ -4227,7 +4298,7 @@ Окуяга кошуу Билдирүү кошуу Жооп кошуу - Жөнөтүү + Кимге Бир жолу көрүлүүчү билдирүү Бир же бир нече нерсе өтө чоң болуп чыкты Бир же бир нече нерсе жараксыз @@ -4350,7 +4421,7 @@ Айлык колдоо жокко чыгарылды Колдоочу төшбелгисинин мөөнөтү бүтүп, профилиңизде көрүнбөй калды. - Бир жолу салым кошуп, колдоочу төшбелгиңизди дагы 30 күнгө иштете аласыз. + Бир жолу салым кошуп, колдоочу төшбелгиңизди дагы 30 күнгө узарта аласыз. Signal\'ды колдоно берсеңиз болот, бирок сиздей адамдар үчүн иштелип чыккан технологияга колдоо көрсөтүү үчүн ай сайын салым кошуп, Signal\'дын Колдоочусу болуп албайсызбы? Колдоочу болуңуз @@ -4362,7 +4433,7 @@ Төлөмүңүз алынбагандыктан, ай сайын кошуп турган салымыңыз токтотулду. Төшбелгиңиз профилиңизде мындан ары көрүнбөйт. Ай сайын кошуп турган салымыңыз токтотулду. %1$s Сиздин %2$s төшбелгиңиз профилиңизде мындан ары көрүнбөйт. - Signal\'ды колдоно берсеңиз болот, бирок колдонмону колдоп, төшбелгиңизди кайра иштетүү үчүн аны азыр жаңыртыңыз. + Signal\'ды колдоно берсеңиз болот, бирок колдонмону колдоп, төшбелгиңизди кайра иштетүү үчүн аны жаңыртышыңыз керек. Жазылууну жаңыртуу Google Pay\'ге өтүү @@ -4405,7 +4476,7 @@ Интернет жок болгондуктан, колдооңуз жөнөтүлгөн жок. Байланышыңызды текшерип, кайталап көрүңүз. - %1$s үчүн колдоо көрсөтүү + %1$s атынан колдоо көрсөттү %1$s сиздин атыңыздан Signal\'га колдоо көрсөттү @@ -4747,13 +4818,13 @@ Бул топтон чыгып кеткениңизден улам, бул окуяга жооп бере албайсыз. - Окуяга реакция кылды + Окуяга сезимин билдирди Көрүүлөр Жооптор - Бул окуяга реакция кылуу + Бул окуяга сезимди билдирүү %1$s үчүн жеке жооп берүүдө @@ -4796,11 +4867,11 @@ Окуяңызды кимдер көрө аларын тандаңыз. Киргизилген өзгөрүүлөр буга чейин жөнөтүлгөн окуяларга таасир этпейт. - Жооптор жана реакциялар + Жооптор жана сезимдер - Жоопторго жана реакцияларга уруксат берүү + Жоопторго жана сезимдерге уруксат берүү - Окуяңызды көрө алган адамдарга жооп берип, реакция кылуу үчүн мүмкүнчүлүк бериңиз + Окуяңызды көрө алган адамдарга жооп берип, сезимдерин билдиргенге мүмкүнчүлүк бериңиз Signal байланыштары @@ -4947,11 +5018,11 @@ - Сиз %1$s окуясына реакция кылдыңыз + %1$s түзгөн окуяга сезимиңизди билдирдиңиз - Окуяңызга реакция кылды + Окуяңызга сезимин билдирди - Окуяга реакция кылды + Окуяга сезимин билдирүү @@ -4975,7 +5046,7 @@ Колдоону ырастоо - Жөнөтүү + Кимге Алуучу колдоо тууралуу билдирүү алат. Төмөн жакка бир нерсе деп коюңуз. @@ -5476,5 +5547,15 @@ Колдонуучу атын өчүрүү + + + с + + м + + Коюу + + Экран кулпуланганга чейинки убакыт кеминде 1 мүнөт болушу керек. + diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index a6b5b3af0c..068ad0f675 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -14,13 +14,14 @@ + Taip Ne Ištrinti Palaukite… Įrašyti - Pastabos sau + Priminimas sau @@ -98,7 +99,7 @@ Užblokuoti naudotojai Pridėti užblokuotą naudotoją - Užblokuoti naudotojai negalės jums skambinti ar siųsti žinutes. + Užblokuoti naudotojai negalės tau skambinti ar siųsti žinučių. Nėra užblokuotų naudotojų Užblokuoti naudotoją? „%1$s“ negalės jums skambinti ar siųsti žinučių. @@ -147,16 +148,16 @@ Galėsite vieni kitiems rašyti žinutes bei skambinti, o jūsų vardas ir nuotrauka bus bendrinami su naudotoju. Galėsite vienas kitam siųsti žinutes. - Užblokuoti žmonės negalės jums skambinti ar siųsti žinučių. - Užblokuoti žmonės negalės siųsti jums žinučių. + Užblokuoti žmonės negalės tau skambinti ar siųsti žinučių. + Užblokuoti žmonės negalės tau siųsti žinučių. - Blokuoti ir nebegauti Signal atnaujinimų bei naujienų. + Blokuoti ir nebegauti „Signal“ atnaujinimų bei naujienų. Pratęsti ir gauti Signal atnaujinimus bei naujienas. Atblokuoti %1$s? Užblokuoti Užblokuoti ir išeiti - Pranešti apie brukalą ir užblokuoti + Pranešti apie šlamštą ir užblokuoti Šiandien @@ -366,7 +367,7 @@ Klaida siunčiant mediją - Pranešta apie brukalą ir užblokuota. + Pranešta apie šlamštą ir užblokuota. SMS žinučių siuntimas šiuo metu išjungtas. Gali eksportuoti savo žinutes į kitą programėlę savo telefone. @@ -524,16 +525,16 @@ Atsegti - Išjungti pranešimus - Išjungti pranešimus - Išjungti pranešimus - Išjungti pranešimus + Nutildyti + Nutildyti + Nutildyti + Nutildyti - Įjungti pranešimus - Įjungti pranešimus - Įjungti pranešimus - Įjungti pranešimus + Įjungti garsą + Įjungti garsą + Įjungti garsą + Įjungti garsą Pasirinkti @@ -582,6 +583,15 @@ +%1$d + + Iš naujo susiek savo įrenginius + + Įrenginiai, kuriuos pridėjai, buvo atsieti išregistravus tavo įrenginį. Eik į nustatymus ir iš naujo susiek įrenginius. + + Atverti nustatymus + + Vėliau + Pasirinkti narius @@ -1011,7 +1021,7 @@ Pranešti apie paminėjimus - Gauti pranešimus, kai jus pamini pokalbiuose, kuriuose pranešimai yra išjungti? + Gauti pranešimus, kai tave pamini nutildytuose pokalbiuose? Visada pranešti Nepranešti @@ -1029,6 +1039,16 @@ Naudotojo vardas sukurtas Naudotojo vardas nukopijuotas + + Nepavyko ištrinti naudotojo vardo. Pabandyk vėliau. + + Naudotojo vardas ištrintas + + + + Kažkas nutiko su tavo naudotojo vardu, jis nebėra priskirtas tavo paskyrai. Gali pabandyti jį nustatyti dar kartą arba pasirinkti naują. + + Tvarkyti dabar @@ -1252,8 +1272,8 @@ Nauja grupė Pakviesti draugus Naudokite SMS - Išvaizda - Pridėti nuotrauką + Pokalbio spalvos + Pridėti profilio nuotrauką Atsakymai @@ -1588,10 +1608,10 @@ Atblokuoti Ar norite leisti, kad %1$s rašytų jums žinutes, ir bendrinti savo vardą bei nuotrauką su šiuo naudotoju? Naudotojas nežinos, kad matėte jo žinutę tol, kol nesutiksite. - Ar norite leisti, kad %1$s rašytų jums žinutes, ir bendrinti savo vardą bei nuotrauką su šiuo naudotoju? Jūs negausite jokių žinučių tol, kol neatblokuosite šio naudotojo. + Ar nori leisti, kad %1$s rašytų tau žinutes, ir bendrinti savo vardą bei nuotrauką su šiuo žmogumi? Negausi žinučių, kol neatblokuosi. - Ar norite leisti, kad %1$s siųstų jums žinutes? Jūs negausite jokių žinučių tol, kol neatblokuosite šio žmogaus. - Ar gauti atnaujinimus ir naujienas iš %1$s? Jūs nebegausite jokių atnaujinimų tol, kol jų neatblokuosite. + Ar nori leisti, kad %1$s siųstų tau žinutes? Negausi žinučių, kol neatblokuosi. + Ar gauti atnaujinimus ir naujienas iš %1$s? Negausi jokių atnaujinimų, kol neatblokuosi. Tęsti jūsų pokalbį su šia grupe ir bendrinti jūsų vardą ir nuotrauką su jos nariais? Naujinkite šią grupę, norėdami aktyvuoti tokias naujas ypatybes kaip @paminėjimai ir administratoriai. Nariai, kurie šioje grupėje nebendrino savo vardo ar nuotraukos, bus pakviesti prie jos prisijungti. Pasenusi grupė daugiau nebegali būti naudojama, nes yra per didelė. Didžiausias grupės dydis yra %1$d. @@ -1599,7 +1619,7 @@ Prisijungti prie šios grupės ir bendrinti jūsų vardą bei nuotrauką su grupės nariais? Jie nežinos, kad matėte jų žinutes tol, kol nesutiksite. Prisijungti prie šios grupės ir bendrinti jūsų vardą bei nuotrauką su grupės nariais? Jūs nematysite jų žinučių tol, kol nesutiksite. Prisijungti prie šios grupės? Kiti asmenys nežinos, kad matėte jų žinutes tol, kol nesutiksite. - Atblokuoti šią grupę ir bendrinti jūsų vardą bei nuotrauką su grupės nariais? Jūs negausite jokių žinučių tol, kol jų neatblokuosite. + Atblokuoti šią grupę ir bendrinti tavo vardą bei nuotrauką su grupės nariais? Negausi jokių žinučių, kol neatblokuosi. Rodyti %1$s grupės narys @@ -1712,9 +1732,20 @@ Sukurti naują PIN kodą + + Siųsti SMS kodą + + „Signal“ registracija – reikia pagalbos perregistruojant PIN kodą „Android“ įrenginiui + + Tavo PIN yra %1$d+ skaitmenų kodas, kurį susikūrei iš skaitmenų arba iš skaitmenų ir raidžių.\n\nJei neprisimeni savo PIN kodo, gali susikurti naują. + + Jei neprisimeni savo PIN kodo, gali susikurti naują. + + Išnaudojai PIN kodo spėjimus, bet dar gali pasiekti savo „Signal“ paskyrą, jei susikursi naują PIN. + Įspėjimas - Jei išjungsite PIN kodą, tuomet registruodamiesi iš naujo Signal programėlėje, prarasite visus duomenis, nebent rankiniu būdu pasidarysite atsarginę kopiją ir ją atkursite. Tol, kol PIN kodas yra išjungtas, negalėsite įjungti Registracijos užrakto. + Jei išjungsi PIN kodą, registruojantis „Signal“ iš naujo bus prarasti visi duomenys, nebent rankiniu būdu padarysi atsarginę kopiją ir atkursi. Negalėsi įjungti registracijos užrakto, kol PIN kodas išjungtas. Išjungti PIN kodą @@ -1849,9 +1880,9 @@ Kamera - Įjungti pranešimus + Įjungti garsą - Be garso + Nutildyti Skambinti @@ -1866,12 +1897,12 @@ - Naudotojas %1$s yra užblokuotas + Naudotojas %1$s užblokuotas Daugiau informacijos Jūs nematysite ir negirdėsite naudotojo, o jis nematys ir negirdės jūsų. Negalima gauti garso ir vaizdo iš %1$s Negalima gauti garso ir vaizdo iš %1$s - Taip gali būti dėl to, kad šis naudotojas nepatvirtino jūsų saugumo numerio pasikeitimo, atsirado problemų su naudotojo įrenginiu arba jis jus užblokavo. + Taip gali būti dėl to, kad šis žmogus nepatvirtino tavo saugumo numerio pasikeitimo, turi problemų su įrenginiu arba tave užblokavo. Perbraukite norėdami matyti ekrano bendrinimą @@ -1908,11 +1939,18 @@ Signal programėlei reikia leidimo gauti prieigą prie jūsų adresatų ir medijos, kad padėtų jums susisiekti su draugais ir siųsti žinutes. Jūsų adresatai yra išsiunčiami naudojant Signal privatų adresatų atradimą, o tai reiškia, kad adresatai yra šifruoti ištisiniu būdu ir niekada nebus matomi Signal paslaugai. Signal programėlei reikia leidimo gauti prieigą prie jūsų adresatų, kad padėtų jums susisiekti su draugais. Jūsų adresatai yra išsiunčiami naudojant Signal privatų adresatų atradimą, o tai reiškia, kad adresatai yra šifruoti ištisiniu būdu ir niekada nebus matomi Signal paslaugai. Atlikote per daug bandymų užregistruoti šį numerį. Vėliau bandykite dar kartą. + + Atlikai per daug bandymų užregistruoti šį numerį. Pabandyk už %1$s. Nepavyksta prisijungti prie paslaugos. Patikrinkite tinklo ryšį ir bandykite dar kartą. Nestandartinis numerio formatas Atrodo, kad jūsų įvestas numeris (%1$s) yra nestandartinio formato.\n\nAr turėjote omenyje %2$s? Molly „Android“ - Telefono numerio formatas + Užklausta skambučio + + Paprašei SMS + + Paprašei patvirtinimo kodo Dabar jums liko %1$d žingsnis iki derinimo žurnalo pateikimo. Dabar jums liko %1$d žingsniai iki derinimo žurnalo pateikimo. @@ -1934,6 +1972,16 @@ Skambinti Patvirtinimo kodas Siųsti kodą iš naujo + + Nepavyksta prisiregistruoti? + + • Įsitikink, kad telefone yra mobilusis ryšys ir gali gauti SMS ar priimti skambutį.\n • Patvirtink, kad gali atsiliepti į skambutį šiuo numeriu.\n • Patikrink, ar teisingai įvedei savo telefono numerį. + + Jei reikia daugiau informacijos, atlik šiuos nesklandumų šalinimo veiksmus arba kreipkis į aptarnavimo komandą + + šiuos nesklandumų šalinimo veiksmus + + Susisiekti su aptarnavimo komanda Įjungti registracijos užraktą? @@ -2093,13 +2141,17 @@ Tu pasiėmei ženklelį - Sureagavo į tavo istoriją „%1$s“ + Sureagavo į tavo istoriją %1$s - Sureagavai į istoriją „%1$s“ + Sureagavai į istoriją %1$s Mokėjimas Suplanuota žinutė + + Tavo pranešimų istorija buvo sujungta + + %1$s priklauso %2$s Molly atnaujinimas @@ -2174,7 +2226,7 @@ MMS žinutė užšifruota seansui, kurio nėra - Išjungti pranešimus + Nutildyti pranešimus Importavimas eigoje @@ -2231,14 +2283,16 @@ Neapsaugota SMS %1$s %2$s Adresatas - Sureagavo %1$s į: „%2$s“. - Sureagavo į jūsų vaizdo įrašą: %1$s. - Sureagavo į jūsų paveikslą: %1$s. - Sureagavo į jūsų GIF: %1$s. - Sureagavo į jūsų failą: %1$s. - Sureagavo į jūsų garso įrašą: %1$s. - Sureagavo į jūsų vienkartinės peržiūros mediją: %1$s - Sureagavo į jūsų lipduką: %1$s. + Sureagavo %1$s į „%2$s“. + Sureagavo į tavo vaizdo įrašą %1$s. + Sureagavo į tavo nuotrauką %1$s. + Sureagavo į tavo GIF %1$s. + Sureagavo į tavo failą %1$s. + Sureagavo į tavo garso įrašą %1$s. + Sureagavo į tavo vienkartinės peržiūros įrašą %1$s. + + Sureagavo į tavo mokėjimą %1$s. + Sureagavo į tavo lipduką %1$s. Ši žinutė buvo ištrinta. Išjungti pranešimus apie tai, kad adresatai prisijungė prie Signal? Galėsite ir vėl įjungti šiuos pranešimus, perėję į Signal > Nustatymai > Pranešimai. @@ -2653,8 +2707,8 @@ Pristatymo problema - Nepavyko jums nuo %1$s pristatyti žinutės, lipduko, reakcijos ar pranešimo apie žinutės skaitymą. Gali būti, kad šis žmogus bandė siųsti jums tiesiogiai arba grupėje. - Nepavyko jums nuo %1$s pristatyti žinutės, lipduko, reakcijos ar pranešimo apie žinutės skaitymą. + Nepavyko tau pristatyti žinutės, lipduko, reakcijos ar perskaitymo pranešimo nuo %1$s. Gali būti, kad šis žmogus tau bandė siųsti tiesiogiai arba grupėje. + Nepavyko tau pristatyti žinutės, lipduko, reakcijos ar perskaitymo pranešimo nuo %1$s. Vardas (būtinas) @@ -2801,10 +2855,10 @@ Naudoti numatytąjį Naudoti tinkintą - Išjungti 1 valandai - Išjungti 8 valandoms - Išjungti 1 dienai - Išjungti 7 dienoms + Nutildyti 1 valandai + Nutildyti 8 valandoms + Nutildyti 1 dienai + Nutildyti 7 dienoms Visada Pagal nustatymų numatymą @@ -2843,9 +2897,9 @@ Naudoti adresų knygos nuotraukas Jei prieinama, rodyti adresatų nuotraukas iš jūsų adresų knygos - Palikti pokalbius su išjungtais pranešimais archyvuotus + Laikyti nutildytus pokalbius suarchyvuotus - Archyvuoti pokalbiai su išjungtais pranešimais, gavus naują žinutę, taip ir liks archyvuoti. + Suarchyvuoti nutildyti pokalbiai gavus naują žinutę liks suarchyvuoti. Generuoti nuorodų peržiūras Gauti savo siunčiamoms žinutėms nuorodų peržiūras tiesiogiai iš internetinių svetainių. Pakeisti slaptafrazę @@ -3298,10 +3352,10 @@ - Įjungti pranešimus + Įjungti garsą - Išjungti pranešimus + Nutildyti pranešimus Grupės nustatymai @@ -3471,6 +3525,8 @@ Įveskite savo PIN kodą Įveskite savo paskyrai sukurtą PIN kodą. Tai yra kas kita, nei jūsų SMS patvirtinimo kodas. + + Įvesk PIN kodą, kurį susikūrei savo paskyrai. Įvesti tekstinį PIN kodą Įvesti skaitinį PIN kodą Neteisingas PIN kodas. Bandykite dar kartą. @@ -3584,7 +3640,10 @@ Tavo atsarginė kopija turi itin didelį failą, kurio negalima nukopijuoti. Ištrink jį ir sukurk naują atsarginę kopiją. Bakstelėkite norėdami tvarkyti atsargines kopijas. Neteisingas numeris? + Skambinti man (%1$02d:%2$02d) + + Siųsti kodą iš naujo (%1$02d:%2$02d) Susisiekti su Signal palaikymu Signal registracija - Patvirtinimo kodas, skirtas „Android“ Neteisingas kodas @@ -3592,6 +3651,18 @@ Nežinoma Matyti mano telefono numerį Rasti mane pagal telefono numerį + + Telefono numeris + + Pasirink, kas gali matyti tavo tel. numerį, ir kas gali juo su tavimi susisiekti per „Molly“. + + Kas gali matyti mano tel. numerį + + Niekas nematys tavo tel. numerio programėlėje „Molly“ + + Kas gali mane rasti pagal numerį + + Tavo tel. numeris bus matomas tiems, kam rašysi, ir grupėms, kuriose rašysi žinutes. Žmonės, turintys tavo numerį tarp adresatų telefone, matys jį ir programėlėje „Molly“. Visi Mano adresatai Niekas @@ -3801,9 +3872,9 @@ %1$s/%2$s - Naudotojas „%1$s“ užblokuotas. - Nepavyko užblokuoti naudotojo „%1$s“ - Naudotojas „%1$s“ atblokuotas. + Naudotojas %1$s užblokuotas. + Nepavyko užblokuoti naudotojo %1$s + Naudotojas %1$s atblokuotas. Peržiūrėti narius @@ -3857,11 +3928,11 @@ Silpnas Wi-Fi. Perjungta į mobilųjį ryšį. - Ištrinant jūsų paskyrą bus: + Ištrynus tavo paskyrą bus: Įveskite savo telefono numerį Ištrinti paskyrą - Ištrinti savo paskyros informaciją ir profilio nuotraukas - Ištrinti visas savo žinutes + Ištrinta tavo paskyros informacija ir profilio nuotraukos + Ištrintos visos tavo žinutės Ištrinti %1$s iš mokėjimų paskyros Nenurodytas joks šalies kodas Nenurodytas joks numeris @@ -3976,12 +4047,12 @@ Pasyvinti piniginę Jūsų likutis - Rekomenduojama, kad prieš pasyvindami mokėjimus, pervestumėte savo lėšas į kitos piniginės adresą. Jei dabar pasirinksite nepervesti lėšų, jos išliks jūsų piniginėje susietos su Molly tam atvejui, jei pasirinksite iš naujo aktyvuoti mokėjimus. + Prieš deaktyvinant mokėjimus rekomenduojama pervesti savo lėšas į kitos piniginės adresą. Jei dabar pasirinksi nepervesti savo lėšų, jos liks su „Molly“ susietoje tavo piniginėje, jei iš naujo aktyvinsi mokėjimus. Pervesti likusį likutį Pasyvinti be pervedimo Pasyvinti Pasyvinti be pervedimo? - Jūsų likutis išliks jūsų piniginėje susietas su Molly tam atvejui, jei pasirinksite iš naujo aktyvuoti mokėjimus. + Tavo likutis liks su „Molly“ susietoje tavo piniginėje, jei pasirinksi iš naujo aktyvinti mokėjimus. Klaida pasyvinant piniginę. @@ -4202,7 +4273,7 @@ Susirašinėjimas Išnykstančios žinutės Programėlės saugumas - Blokuoti ekrano kopijas paskiausiųjų sąraše ir programėlės viduje + Blokuoti ekrano kopijas paskiausiųjų sąraše ir programėlėje Signal žinutės ir skambučiai, visada retransliuoti skambučius ir užantspauduotas siuntėjas Numatytasis laikmatis naujuose pokalbiuose Nustatyti numatytąjį išnykstančių žinučių laikmatį visiems jūsų pradedamiems naujiems pokalbiams. @@ -4363,9 +4434,9 @@ Skambinti - Išj. pranešimus + Nutildyti - Pranešimai išj. + Nutildyta Ieškoti Išnykstančios žinutės @@ -4384,9 +4455,9 @@ Prašymai ir pakvietimai Grupės nuoroda Pridėti kaip adresatą - Įjungti pranešimus - Pokalbio pranešimai išjungti iki %1$s - Pokalbio pranešimai išjungti visam laikui + Įjungti garsą + Pokalbis nutildytas iki %1$s + Pokalbis nutildytas visam laikui Telefono numeris nukopijuotas į iškarpinę. Telefono numeris Gauk ženklelių savo profiliui už paramą „Signal“. Bakstelėk ženklelį ir sužinok daugiau. @@ -4402,8 +4473,8 @@ Kas gali siųsti žinutes? - Išjungti pranešimus - Pranešimai neišjungti + Nutildyti pranešimus + Nenutildyta Paminėjimai Visada pranešti Nepranešti @@ -4659,7 +4730,7 @@ Kasmėnesinė parama nutraukta Tavo rėmėjo ženklelis nebegalioja ir nebėra matomas tavo profilyje. - Gali pratęsti rėmėjo ženklelį dar 30-čiai dienų, jei skirsi vienkartinę paramą. + Gali pratęsti savo rėmėjo ženklelį dar 30-čiai dienų, jei skirsi vienkartinę paramą. Gali ir toliau naudoti „Signal“, bet jei nori palaikyti tau kuriamą technologiją, apsvarstyk galimybę tapti rėmėju ir skirti kasmėnesinę paramą. Tapti rėmėju @@ -4714,7 +4785,7 @@ Tinklo klaida sutrukdė nusiųsti tavo paramą. Patikrink interneto ryšį ir bandyk dar kartą. - Parama skiriama: %1$s + Parama %1$s vardu %1$s skyrė „Signal“ paramą tavo vardu @@ -5851,5 +5922,15 @@ Ištrinti naudotojo vardą + + + val. + + min. + + Nustatyti + + Trumpiausias laikas prieš įsijungiant ekrano užraktui yra 1 minutė. + diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 86d8875deb..101d4d65f1 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -14,6 +14,7 @@ + @@ -147,15 +148,15 @@ Jūs varēsiet sūtīt ziņas un zvanīt viens otram kā arī jūsu vārds un fotogrāfija tiks kopīgota Jūs varēsiet sūtīt ziņas viens otram. - Bloķēti lietotāji nevarēs Jums piezvanīt vai nosūtīt ziņojumu. + Bloķētie cilvēki nevarēs jums piezvanīt vai nosūtīt ziņas. Bloķētie cilvēki nevarēs sūtīt jums ziņas. - Bloķēt Signal jaunumu un ziņu saņemšanu + Bloķēt Signal jaunumu un ziņu saņemšanu. Atjaunot Signal jaunumu un ziņu saņemšanu - Atbloķēt %1$s? + Vai atbloķēt %1$s? Bloķēt - Bloķēt un iziet + Bloķēt un pamest Ziņot par surogātpastu un bloķēt @@ -453,7 +454,7 @@ %1$s ieslēgts - Bloķēt pieprasījumu? + Vai bloķēt pieprasījumu? %1$s vairs nevarēs pievienoties vai lūgt pievienoties šai grupai caur grupas saiti. Manuāla pievienošana grupai vēl arvien būs iespējama. @@ -490,14 +491,14 @@ %1$d sarunas pārvietotas uz iesūtni - Lasīts + Lasīti Lasīts - Lasīts + Lasīti - Nelasīts + Nelasīti Nelasīts - Nelasīts + Nelasīti Piespraust @@ -510,9 +511,9 @@ Atspraust - Izslēgt - Izslēgt - Izslēgt + Apklusināt + Apklusināt + Apklusināt Ieslēgt @@ -562,6 +563,15 @@ +%1$d + + Atkārtoti piesaistiet savas ierīces + + Jūsu pievienotās ierīces tika atsaistītas, kad ierīce tika atreģistrēta. Atveriet sadaļu Iestatījumi, lai atkārtoti piesaistītu visas ierīces. + + Atvērt iestatījumus + + Vēlāk + Atlasiet grupas dalībniekus @@ -973,7 +983,7 @@ Paziņot man par Pieminējumiem - Vai saņemt paziņojumus, kad esat pieminēts sarunās, kurām izslēgti paziņojumi? + Vai saņemt paziņojumus, kad jūs piemin sarunās, kurām izslēgti paziņojumi? Vienmēr man paziņot Nevajag paziņot @@ -991,6 +1001,16 @@ Lietotājvārds izveidots Lietotājvārds nokopēts + + Nevarēja izdzēst lietotājvārdu. Lūdzu, mēģiniet vēlāk. + + Lietotājvārds izdzēsts + + + + Radās problēma ar jūsu lietotāja vārdu, tas vairs nav piešķirts jūsu kontam. Varat mēģināt iestatīt to vēlreiz vai izvēlēties jaunu. + + Labot @@ -1204,8 +1224,8 @@ Jauna grupa Uzaicināt draugus Izmantot SMS - Izskats - Pievienot fotoattēlu + Sarunas krāsas + Pievienojiet profila attēlu Atbildes @@ -1530,10 +1550,10 @@ Atbloķēt Vai atļaut %1$s ar jums sazināties un kopīgot jūsu vārdu un fotoattēlu? Viņi nezinās, vai jūs izlasījāt ziņas, kamēr neapstiprināsiet. - Vai atļaut %1$s ar jums sazināties un kopīgot jūsu vārdu un fotoattēlu? Jūs nesaņemsiet nevienu ziņu, kamēr šo grupu neatbloķēsiet. + Vai atļaut lietotājam %1$s ar jums sazināties un kopīgot jūsu vārdu un fotoattēlu? Jūs nesaņemsiet ziņas līdz viņu neatbloķēsiet. - Ļaut %1$s sūtīt jums ziņas? Jūs nesaņemsiet ziņas līdz neatbloķēsiet viņu. - Saņemt junumus un ziņas no %1$s? Jūs nesaņemsiet nevienu ziņu, kamēr šo lietotāju neatbloķēsiet. + Vai atļaut lietotājam %1$s ar jums sazināties? Jūs nesaņemsiet ziņas līdz viņu neatbloķēsiet. + Vai saņemt jaunumus un ziņas no lietotāja %1$s? Jūs nesaņemsiet ziņas līdz viņu neatbloķēsiet. Vai vēlaties turpināt sarunu ar šo grupu un rādīt savu vārdu un fotoattēlu šīs grupas dalībniekiem? Jauniniet šo grupu, lai aktivizētu jaunas funkcijas, piemēram, @pieminējumi un administratori. Dalībnieki, kuri šajā grupā nav kopīgojuši savu vārdu vai fotoattēlu, tiks uzaicināti pievienoties. Šo vecā tipa grupu vairs nevar izmantot, jo tā ir pārāk liela. Maksimālais grupas lielums ir %1$d. @@ -1541,7 +1561,7 @@ Vai pievienoties šai grupai un kopīgot jūsu vārdu un fotoattēlu? Grupas dalībnieki nezinās, vai jūs izlasījāt ziņas, kamēr neapstiprināsiet uzaicinājumu. Pievienoties šai grupai un kopīgot jūsu vārdu un profila attēlu ar tās dalībniekiem? Jūs neredzēsiet ziņas no viņiem, līdz to neapstiprināsiet. Vai pievienoties šai grupai? Dalībnieki nezinās, vai jūs izlasījāt ziņas, kamēr jūs neapstiprināsiet uzaicinājumu. - Atbloķēt šo grupu un dalīties ar savu vārdu un attēlu ar šiem dalībniekiem? Jūs nesaņemsiet nevienu ziņu, kamēr šo grupu neatbloķēsiet. + Vai atbloķēt šo grupu un dalīties ar savu vārdu un attēlu ar šiem dalībniekiem? Jūs nesaņemsiet ziņas, kamēr šo grupu neatbloķēsiet. Skatīt %1$s dalībnieks @@ -1648,6 +1668,17 @@ Izveidojiet jaunu PIN + + Sūtīt SMS kodu + + Signal reģistrācija — nepieciešama palīdzība ar atkārtotas reģistrācijas Android PIN kodu + + PIN ir jūsu izveidots %1$d+ ciparu kods, kas var sastāvēt no cipariem vai no burtiem un cipariem.\n\nJa neatceraties savu PIN, varat izveidot jaunu. + + Ja neatceraties savu PIN, varat izveidot jaunu. + + Ir sasniegts maksimālais PIN minējumu skaits, taču joprojām varat piekļūt savam Signal kontam, izveidojot jaunu PIN kodu. + Brīdinājums Ja atspējosiet PIN, jūs zaudēsiet visus datus, atkārtoti reģistrējoties lietotnē Signal, ja vien manuāli tos nedublēsiet un neatjaunosiet. Reģistrācijas bloķēšanu nevar ieslēgt, kamēr PIN ir atspējots. @@ -1838,11 +1869,18 @@ Signal nepieciešama pieeja kontaktiem un failiem, lai palīdzētu Jums atrast draugus un sūtīt ziņas. Jūsu kontaktu saraksts tiks augšupielādēts lietojot Signal privāto kontaktu atklāšanu, kas nozīmē, ka tie būs šifrēti, un nekad nebūs redzami Signal servisam. Signal nepieciešama pieeja kontaktiem, lai palīdzētu Jums atrast draugus. Jūsu kontaktu saraksts tiks augšupielādēts lietojot Signal privāto kontaktu atklāšanu, kas nozīmē, ka tie būs šifrēti, un nekad nebūs redzami Signal servisam. Pārāk daudz šī numura reģistrēšanas mēģinājumu. Vēlāk mēģiniet vēlreiz. + + Pārāk daudz šī numura reģistrēšanas mēģinājumu. Mēģiniet vēlreiz pēc %1$s. Nevar izveidot savienojumu ar pakalpojumu. Lūdzu, pārbaudiet tīkla savienojumu un mēģiniet vēlreiz. Nestandarta numura formāts Izskatās, ka numurs, kuru ievadījāt (%1$s), ir nestandarta formātā.\n\nVai domājāt %2$s? Molly Android - Telefona Numura Formāts + Pieprasīts zvans + + Pieprasīta SMS + + Pieprasīts verifikācijas kods Jūs tagad esiet %1$d soļus no atkļūdošanas žurnāla iesniegšanas Jūs tagad esiet %1$d soli no atkļūdošanas žurnāla iesniegšanas @@ -1863,6 +1901,16 @@ Zvanīt Verifikācijas kods Saņemt kodu vēlreiz + + Vai jums ir problēmas ar reģistrāciju? + + • Pārbaudiet, vai tālrunim ir mobilā tīkla signāls, lai saņemtu īsziņu vai zvanu \n • Pārbaudiet, vai varat uz šo numuru saņemt tālruņa zvanu\n • Pārbaudiet, vai esat pareizi ievadījis tālruņa numuru. + + Lai iegūtu vairāk informācijas, veiciet šīs problēmu novēršanas darbības vai sazinieties ar atbalstu + + šīs problēmu novēršanas darbības + + Sazināties ar atbalstu Ieslēgt reģistrācijas bloķēšanu? @@ -2029,6 +2077,10 @@ Maksājums Ieplānotā ziņa + + Jūsu ziņu vēsture ir apvienota + + %1$s ir kontakta %2$s tālruņa numurs Molly atjauninājums @@ -2159,14 +2211,16 @@ Nedroša SMS %1$s %2$s Kontakts - Reaģēja %1$s uz “%2$s” - Reaģēja %1$s uz jūsu video. - Reaģēja %1$s uz jūsu attēlu - Uz jūsu GIF reaģēja ar %1$s - Reaģēja %1$s uz jūsu failu. - Reaģēja %1$s uz jūsu audio - Uz jūsu vienreiz skatāmo multivides vienumu reaģēja %1$s. - Reaģēja %1$s uz jūsu uzlīmi. + Reakcija uz “%2$s”: %1$s. + Reakcija uz jūsu video: %1$s. + Reakcija uz jūsu attēlu: %1$s. + Reakcija uz jūsu GIF: %1$s. + Reakcija uz jūsu failu: %1$s. + Reakcija uz jūsu audio: %1$s. + Reakcija uz jūsu vienreiz skatāmo multivides vienumu: %1$s. + + Reakcija uz jūsu maksājumu: %1$s. + Reakcija uz jūsu uzlīmi: %1$s. Ziņojums ir izdzēsts. Vai izslēgt paziņojumus par kontaktpersonām, kas sākušās lietot Signal? Paziņojumus var atkal iespējot sadaļā Signal > Iestatījumi > Paziņojumi. @@ -2759,9 +2813,9 @@ Izmantot adrešu grāmatas foto Parādiet kontaktpersonu fotoattēlus no adrešu grāmatas, ja pieejama - Keep Muted Chats Archived + Saglabāt apklusinātās sarunas arhivētas - Muted chats that are archived will remain archived when a new message arrives. + Arhivētas apklusinātās sarunas paliks arhivētas, kad tiks saņemta jauna ziņa. Ģenerēt saišu priekšskatījumus Izgūstiet saišu priekšskatījumus nosūtītajām ziņām tieši no tīmekļa vietnēm. Nomainīt paroli @@ -3055,7 +3109,7 @@ Nosūtīts maksājums Saņemts maksājums Maksājums pabeigts %1$s - Bloka numurs + Bloķēt numuru Pārsūtīt @@ -3383,6 +3437,8 @@ Ievadiet jūsu PIN Ievadiet sava konta PIN kodu. Tas atšķiras no jūsu SMS verifikācijas koda. + + Ievadiet savam kontam izveidoto PIN. Ievadiet no burtiem un cipariem sastāvošu PIN Izveido ciparisku PIN Nepareizs PIN. Mēģiniet vēlreiz. @@ -3491,7 +3547,10 @@ Jūsu rezerves kopija satur ļoti lielu failu, ko nav iespējams dublēt. Lūdzu, izdzēsiet to un izveidojiet jaunu rezerves kopiju. Pieskarties, lai pārvaldību rezerves kopijas Vai nepareizs numurs? + Piezvaniet man (%1$02d.%2$02d) + + Atkārtoti nosūtīt kodu (%1$02d:%2$02d) Sazināties ar Signal tehnisko atbalstu Signal reģistrācija - Android ierīces verifikācijas kods Nepareizs kods @@ -3499,6 +3558,18 @@ Nezināms Redzēt manu telefona # Meklēt mani pēc telefona # + + Tālruņa numurs + + Izvēlieties, kas var redzēt jūsu tālruņa numuru un kas var ar jums sazināties lietotnē Molly, izmantojot to. + + Kas var redzēt manu numuru + + Neviens neredzēs jūsu tālruņa numuru lietotnē Molly + + Kas var mani atrast pēc numura + + Jūsu tālruņa numurs būs redzams cilvēkiem un grupām, kurām sūtāt ziņu. Cilvēki, kuru tālruņa katalogā ir jūsu numurs, to redzēs arī lietotnē Molly. Ikviens Manas kontaktpersonas Neviens @@ -3772,7 +3843,7 @@ Nav norādīts numurs Ievadītais tālruņa numurs neatbilst jūsu kontā norādītajam. Vai tiešām vēlaties dzēst savu kontu? - Veicot šo darbību, tiks dzēsts jūsu Signal konts un lietotne tiks atiestatīta. Pēc šī procesa pabeigšanas lietotne tiks aizvērta. + Veicot šo darbību, jūsu Signal konts tiks dzēsts un lietotne tiks atiestatīta. Pēc šī procesa pabeigšanas lietotne tiks aizvērta. Neizdevās izdzēst lokālos datus. Tos var manuāli dzēst sistēmas lietojumprogrammas iestatījumos. Atvērt lietotnes iestatījumus @@ -4105,7 +4176,7 @@ Ziņapmaiņa Gaistošās ziņas Lietotnes aizsardzība - Bloķēt ekrānuzņēmumus pēdējo aplikāciju sarakstā un pašā aplikācijā + Bloķēt ekrānuzņēmumus pēdējo sarunu sarakstā un pašā lietotnē Signal ziņas un zvani, pastāvīga zvanu pārsūtīšana un šifrēts sūtītājs Noklusējuma taimeris jaunām sarunām Iestatiet noklusējuma ziņu dzēšanas taimeri visām jaunajām jūsu sāktajām sarunām. @@ -4205,7 +4276,7 @@ Ne tagad - Pielāgot emociju izpausmes + Pielāgot reakcijas Pieskarieties, lai aizvietotu emocijzīmi Atstatīt Saglabāt @@ -4266,7 +4337,7 @@ Izslēgt - Izslēgta + Izslēgti Meklēt Gaistošās ziņas @@ -4286,8 +4357,8 @@ Grupas saite Pievienot kā kontaktpersonu Ieslēgt - Saruna izslēgta līdz %1$s - Saruna izslēgta uz visiem laikiem. + Sarunas paziņojumi izslēgti līdz %1$s + Sarunas paziņojumi izslēgti uz visiem laikiem Tālruņa numurs ir nokopēts starpliktuvē. Tālruņa numurs Iegūstiet nozīmītes savam profilam, atbalstot Signal. Pieskarieties nozīmītei, lai uzzinātu vairāk. @@ -4304,7 +4375,7 @@ Izslēgt paziņojumus - Nav izslēgtas + Nav izslēgti Pieminējumi Vienmēr paziņot Neziņot @@ -4341,7 +4412,7 @@ %1$s ir noņemts - %1$s ir bloķēta + %1$s ir bloķēts Nevar noņemt %1$s @@ -4556,7 +4627,7 @@ Ikmēneša ziedojums atcelts Jūsu Atbalsta nozīmītei ir beidzies termiņš, un tā vairs nav redzama uz jūsu profila. - Jūs varat atkārtoti aktivizēt jūsu Atbalsta nozīmīti uz nākamajām 30 dienām ar vienreizēju ziedojumu. + Jūs varat atkārtoti aktivizēt jūsu Sponsora nozīmīti uz nākamajām 30 dienām ar vienreizēju ziedojumu. Jūs varat turpināt izmantot Signal, bet, lai atbalstītu tehnoloģiju, kas izveidota jums, apsveriet iespēju kļūt par uzturētāju veicot ikmēneša ziedojumu. Kļūstiet par Uzturētāju @@ -4611,7 +4682,7 @@ Jūsu ziedojumu neizdevās nosūtīt tīkla kļūdas dēļ. Pārbaudiet savienojumu un mēģiniet vēlreiz. - Ziedojums lietotāja %1$s vārdā + Ziedojums lietotāja %1$s vārdā %1$s ziedoja Signal jūsu vārdā @@ -5726,5 +5797,15 @@ Dzēst lietotājvārdu + + + h + + min + + Iestatīt + + Minimālais laiks pirms ekrāna bloķēšanas ir 1 minūte. + diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 9dc3af57ee..b68618973b 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -14,6 +14,7 @@ + Да Не @@ -36,7 +37,7 @@ Да ги исклучам Molly пораките и повиците? Исклучи ги пораките и повиците од Molly со дерегистрирање од серверот. Ќе мора повторно да го регистрирате телефонскиот број за да може повторно да користите пораки и повици. Грешка при поврзување со серверот! - PIN-от е потребен за заклучување на регистрацијата. За да го исклучите PIN-от, Ве молиме прво исклучете го заклучувањето на регистрацијата. + PIN-от е потребен за заклучување на регистрацијата. За да го исклучите PIN-от, ве молиме прво исклучете го заклучувањето на регистрацијата. PIN-от е создаден. PIN-от е исклучен. Запиши резервна фраза на плаќањата @@ -97,10 +98,10 @@ Блокирани корисници - Додај блокиран корисник - Блокираните корисници нема да можат да Ви се јават и да Ви испраќаат пораки. + Додајте блокиран корисник + Блокираните корисници нема да можат да ви се јавуваат или да ви испраќаат пораки. Нема блокирани корисници - Да го блокирам корисникот? + Сакате да го блокирате корисникот? \"%1$s\" нема да може да Ви се јави или да Ви испраќа пораки. Блокирај @@ -138,8 +139,8 @@ Продолжи - Да ја блокирам и напуштам %1$s? - Блокирај го/ја %1$s? + Сакате да ја блокирате и напуштите групата %1$s? + Да се блокира %1$s? Повеќе нема да примате пораки од оваа група и членовите нема да можат повторно да Ве додадат во оваа група. Членовите на групата нема да можат повторно да Ве додадат во оваа група. Членовите на групата ќе можат повторно да Ве додадат во оваа група. @@ -147,13 +148,13 @@ Ќе можете да испраќате пораки и да се јавувате и Вашето име и слика ќе бидат споделени со нив. Ќе можете да се испраќате пораки. - Блокираните луѓе нема да можат да Ви се јават и да Ви испраќаат пораки. - Блокираните луѓе нема да можат да Ви испраќаат пораки. + Блокираните луѓе нема да можат да ви се јавуваат и да ви испраќаат пораки. + Блокираните луѓе нема да можат да ви испраќаат пораки. Блокирај вести од Signal. Продолжи со примање вести од Signal - Одблокирај го/ја %1$s? + Да се деблокира %1$s? Блокирај Блокирај и напушти Пријави спам и блокирај @@ -318,7 +319,7 @@ Signal порака Ајде да се префрлиме на Molly %1$s Ве молиме изберете контакт - Одблокирај + Деблокирај Прилогот ги надминува ограничувањата на големината за видот на пораката што ја испраќате. Не може да се снима аудио! Не можете да испраќате пораки на оваа група бидејќи повеќе не сте член. @@ -455,7 +456,7 @@ Откажи - Блокиран/а + Барањето е блокирано Отстрани го филтерот @@ -481,11 +482,11 @@ Прочитано - Прочитано + Прочитани Непрочитано - Непрочитано + Непрочитани Закачи @@ -542,6 +543,15 @@ +%1$d + + Повторно поврзете ги вашите уреди + + Се прекина поврзаноста со уредите кои ги додадовте кога се отстрани регистрацијата на вашиот уред. Одете во поставувања за повторно да ги поврзете уредите. + + Отвори поставувања + + Подоцна + Изберете членови @@ -935,7 +945,7 @@ Извести кога некој ќе ме спомне - Дали да примате известувања кога некој ќе Ве спомне во разговори со исклучени известувања? + Дали сакате да примате известувања кога некој ќе ве спомне во разговори со исклучени известувања? Извести ме секогаш Не ме известувај @@ -953,6 +963,16 @@ Корисничкото име е создадено Корисничкото име е копирано + + Бришењето на корисничкото име е неуспешно. Обидете се повторно подоцна. + + Корисничкото име е избришано + + + + Нешто не е во ред со вашето корисничко име, повеќе не ја назначува вашата сметка. Можете да се обидете да го поставите одново или да изберете ново. + + Поправи сега @@ -1156,8 +1176,8 @@ Нова група Покани пријатели Користи SMS - Изглед - Додај слика + Боја на разговор + Додајте слика на профилот Одговори @@ -1469,13 +1489,13 @@ Продолжи Избриши Блокирај - Одблокирај + Деблокирај Дали дозволувате %1$s да Ви испраќа пораки и да го гледа Вашето име и слика? Контактот нема да знае дека ја имате видено оваа порака додека не прифатите. - Дали дозволувате %1$s да Ви испраќа пораки и да го гледа Вашето име и слика? Нема да примате пораки додека не го/ја одблокирате. + Сакате да дозволите %1$s да може да ви пишува и да ви ги гледа името и сликата? Нема да добивате пораки додека не го дебклокирате овој контакт. - Дали дозволувате %1$s да Ви испраќа пораки? Нема да примате пораки додека не го/ја одблокирате. - Дали сакате да добивате ажурирања и новости од %1$s? Нема да примате пораки додека не го/ја одблокирате. + Сакате да дозволите %1$s да може да ви пишува? Нема да добивате пораки додека не го дебклокирате овој контакт. + Сакате да добивате новости од %1$s? Нема да добивате новости додека не го деблокирате овој контакт. Дали сакате да го продолжите разговорот во оваа група и да го споделите Вашето име и слика со нејзините членови? Надградете ја оваа група за да активирате нови опции како @спомнувања и администратори. Членови кои не го споделиле нивното име и слика со оваа група, ќе бидат поканети да се приклучат. Ова е застарена група и не може да се користи повеќе бидејќи е преголема. Максималната големина на група е %1$d. @@ -1483,7 +1503,7 @@ Дали сакате да и се приклучите на оваа група и да ги споделите Вашето име и слика со нејзните членови? Тие нема да знаат дека сте ги виделе нивните пораки додека не прифатите. Сакате да се придружите на оваа група и да ги споделите името и сликата со нејзините членови? Нема да можете да ги видите нивните пораки додека не прифатите. Дали сакате да и се приклучите на оваа група?Тие нема да знаат дека сте ги виделе нивните пораки додека не прифатите. - Дали сакате да ја одблокирате оваа група и да ги споделите Вашето име и фотографија со нејзините членови? Нема да примате пораки додека не ја одблокирате. + Сакате да ја деблокирате оваа група и да ги споделите вашето име и слика со нејзините членови? Нема да добивате пораки додека не ја деблокирате групата. Поглед Член на %1$s @@ -1584,6 +1604,17 @@ Создади нов PIN + + Испрати SMS код + + Signal регистрација - Потребна ми е помош со повторна регистрација на PIN за Android + + Вашиот PIN е код што го имате создадено со %1$d+ цифри што можат да бидат нумерички или алфанумерички.Ако не можете да го запаметите вашиот PIN, можете да содадете нов. + + Ако не можете да го запаметите вашиот PIN, можете да содадете нов. + + Немате повеќе обиди за да го внесете точниот PIN, но можете да добиете пристап до вашата Signal сметка со создавање на нов PIN. + Предупредување Ако го исклучите PIN-от, ќе ги изгубите сите податоци кога повторно ќе се регистрирате на Signal, освен ако рачно направите резервна копија и ги вратите податоците. Не можете да вклучите „Заклучување на регистрација“ додека PIN-от е исклучен. @@ -1617,7 +1648,7 @@ Блокирај - Одблокирај + Деблокирај @@ -1711,9 +1742,9 @@ Камера - Вклучи + Вклучи известувања - Исклучи + Исклучи известувања Ѕвони @@ -1726,12 +1757,12 @@ - %1$s е блокиран/а + Контактот %1$s е блокиран Повеќе информации Нема да примате аудио и видео и тие нема да го примаат Вашето аудио и видео. Не можам да примам аудио и видео од %1$s Не можам да примам аудио и видео од %1$s - Ова може да се случи бидејќи тие го немаат проверено вашиот безбедносен број по промената, има некаков проблем со нивниот уред или Ве имаат блокирано. + Ова може да се случи бидејќи тие ја немаат потврдено проментата на вашиот безбедносен број, има некаков проблем со нивниот уред, или ве имаат блокирано. Повелчете за да го видите споделениот екран @@ -1768,11 +1799,18 @@ Signal има потреба од дозвола до контакти и склад за да Ви помогне да се поврзете со пријателите и да испраќате пораки. Вашите контакти се прикачуваат преку приватното откривање на контакти на Signal, ова значи дека се шифрирани од крај до крај и никогаш не се видливи за Signal услугата. Signal има потреба од дозвола до контакти за да Ви помогне да се поврзете со пријателите. Вашите контакти се прикачуваат преку приватното откривање на контакти на Signal, ова значи дека се шифрирани од крај до крај и никогаш не се видливи за Signal услугата. Направивте премногу обиди за регистрација на овој број. Ве молиме обидете се повторно подоцна. + + Направивте премногу обиди за регистрација на овој број. Ве молиме обидете се повторно за %1$s. Не може да се поврзе со услугата. Ве молиме проверете ја Вашата интернет конекција и обидете се повторно. Нестандарден формат на број Бројот што го внесовте (%1$s) изгледа дека е нестандарден формат.\n\nДали мислевте на %2$s? Molly Android - Формат на телефонски број + Повикот е побаран + + Пратено е барање за SMS + + Пратено е барање за код за потврда Сега сте %1$d чекор оддалечени од испраќање на записот за отстранување грешки. Сега сте %1$d чекори оддалечени од испраќање на записот за отстранување грешки. @@ -1792,6 +1830,16 @@ Повикај Код за потврда Препрати код + + Имате проблеми при регистрација? + + • Проверете дали вашиот телефон има сигнал за да ја прими пораката или повикот\n• Проверете дали можете да примите повик на овој број\n• Проверете дали точно го внесовте телефонскиот број. + + За повеќе информации, следете ги овие чекори за решавање на проблеми или контактирајте го тимот за корисничка поддршка + + овие чекори за решавање на проблеми + + Контактирајте го тимот за корисничка поддршка Да вклучам заклучување на регистрација? @@ -1958,6 +2006,10 @@ Плаќања Закажана порака + + Историјата на вашиот разговор е споена + + %1$s припаѓа на %2$s Molly ажурирање @@ -2088,13 +2140,15 @@ %1$s %2$s Контакт Реагираше со %1$s на „%2$s“. - Реагираше со %1$s на Вашето видео. - Реагираше со %1$s на Вашата слика. - Реагираше со %1$s на Вашиот GIF. - Реагираше со %1$s на Вашата датотека. - Реагираше со %1$s на Вашето аудио. - Реагираше со %1$s на Вашата еднократно видлива медиумска датотека. - Реагираше со %1$s на Вашиот стикер. + Реагираше со %1$s на вашето видео. + Реагираше со %1$s на вашата слика. + Реагираше со %1$s на вашиот GIF. + Реагираше со %1$s на вашата датотека. + Реагираше со %1$s на вашето аудио. + Реагираше со %1$s на вашата еднократно видлива медиумска датотека. + + Реагираше со %1$s на вашето плаќање. + Реагираше со %1$s на вашата налепница. Оваа порака е избришана. Дали да се исклучат известувања кога контакт ќе се приклучи на Signal? Можете да ги вклучите повторно во Signal > Поставувања > Известувања. @@ -2487,8 +2541,8 @@ Проблем со испорака - Порака, налепница, реакција или потврда за прочитани пораки не може да Ви се достави од %1$s. Можеби се обиделе да Ви испратат директно или во група. - Порака, налепница, реакција или потврда за прочитани пораки не може да Ви се достави од %1$s. + Не можеше да се испрати до вас порака, налепница, реакција или потврда за прочитана порака од %1$s. Или се обиделе да ви ја испратат директно или во група. + Не можеше да се испрати до вас порака, налепница, реакција или потврда за прочитана порака од %1$s. Име (задолжително) @@ -2635,10 +2689,10 @@ Користи стандардно Користи прилагодено - Исклучи на 1 час - Исклучи на 8 часа - Исклучи на 1 ден - Исклучи на 7 дена + Исклучи ги известувањата на 1 час + Исклучи ги известувањата на 8 часа + Исклучи ги известувањата на 1 ден + Исклучи ги известувањата на 7 дена Засекогаш Стандардни поставувања @@ -2675,9 +2729,9 @@ Користи слики од именик Прикажи слики на контакти од именик ако се достапни - Keep Muted Chats Archived + Задржи ги архивирани тивките разговори - Muted chats that are archived will remain archived when a new message arrives. + Архивираните разговори со исклучени известувања ќе останат архивирани и кога ќе стигне нова порака. Генерирај прегледи за линкови Преземај прегледи за линкови директно од веб страниците за пораките што ги испраќате. Промена на лозинка @@ -2757,7 +2811,7 @@ Напредни поставувања за PIN Бесплатни приватни пораки и повици кон Signal корисниците Испрати запис за отстранување грешки - Избриши сметка + Избришете ја вашата корисничка сметка „Повикување преку WiFi“ режим на компатибилност Овозможи доколку уредот користи SMS/MMS испорака преку WiFi (овозможи само кога „Повикување преку WiFi“ е вклучено на уредот) Инкогнито тастатура @@ -2971,7 +3025,7 @@ Испратено плаќање Примено плаќање Плаќањето е завршено %1$s - Блокирај број + Блокирај го бројот Трансфер @@ -3056,7 +3110,7 @@ Нова порака за… - Блокирај корисник + Блокирај го корисникот Додај во група @@ -3295,6 +3349,8 @@ Внесете го Вашиот PIN Го внесете PIN кодот што го создадовте за Вашата сметка. Овој код е различен од Вашиот SMS код за проверка. + + Внесете го ПИН-от што го создадовте за вашата сметка. Внесете алфанумерички PIN Внесете нумерички PIN Погрешен PIN. Обидете се повторно. @@ -3398,7 +3454,10 @@ Вашата резервна копија содржи многу голема датотека од која не може да се направи резервна копија. Ве молиме избришете ја и создајте нова резервна копија. Допрете за управување со резервните копии. Погрешен број? + Јави ми се (%1$02d:%2$02d) + + Препраќање на код (%1$02d:%2$02d) Контактирај Signal Поддршка Регистрација на Signal - Код за проверка за Android Погрешен код @@ -3406,6 +3465,18 @@ Непознато Да го видат мојот телефонски број Да ме најдат преку мојот телефонски број + + Телефонски број + + Одберете кој може да го види вашиот телефонски број и да ве контактира на Molly со тој број. + + Кој може да го види мојот број + + Никој нема да го види вашиот телефонски број на Molly + + Кој може да ме пронајде со мојот телефонски број + + Вашиот телефонски број ќе биде видлив за луѓето и групите со кои се допишувате. Лицата кои го имаат вашиот број во контакти исто така ќе можат да го видат на Molly. Сите Мои контакти Никој @@ -3563,7 +3634,7 @@ Блокирај - Одблокирај + Деблокирај Додај во контакти Не е пронајдена апликација која може да отвори контакти. @@ -3615,9 +3686,9 @@ %1$s/%2$s - „%1$s“ е блокиран/а. - Не успеав да го/ја блокирам „%1$s“ - „%1$s“ е одблокиран/а. + Корисникот „%1$s“ е блокиран. + Не е успешно блокирањето на „%1$s“ + Корисникот „%1$s“ е деблокиран. Прегледај членови @@ -3667,11 +3738,11 @@ Слаба Wi-Fi конекција. Префрлени сте на мобилен интернет. - Бришењето на Вашата сметка ќе: + Со бришењето на вашата сметка: Внесете го вашиот телефонски број - Избриши сметка - Избришете информации за вашата сметка и профилна слика - Избришете ги сите ваши пораки + Ќе се избрише вашата корисничка сметка + Ќе се избришат информациите за вашата сметка и профилна слика + Ќе се избришат сите ваши пораки Избришете %1$s од вашата сметка за плаќања Не е внесен повикувачки број на државата Не е внесен број @@ -3784,12 +3855,12 @@ Деактивирај го паричникот Вашиот баланс - Препорачливо е да ги префрлите Вашите средства на адресата на друг паричник пред да ги деактивирате плаќањата. Ако изберете да не ги префлите Вашите средства сега, тие ќе останат на Вашиот паричник врзан со Molly, во случај да ги реактивирате плаќањата пак. + Се препорачува да ги префрлите средствата до друга адреса на паричник пред да ги деактивирате плаќањата. Ако решите да не ги префрлите средствата сега, тие ќе останат во вашиот паричник кој е поврзан за Molly ако ги реактивирате плаќањата. Префли го преостанатиот баланс Деактивирај без пренос Деактивирај Да деактивирам без пренос? - Вашата состјоба ќе остане на Вашиот паричник врзан со Molly ако изберете да ги реактивирате плаќањата. + Средствата на вашатата сметка ќе останат во вашиот паричник врзан со Molly ако изберете да ги реактивирате плаќањата. Грешка при деактивирање на паричникот. @@ -4003,12 +4074,12 @@ Создај профил - Блокирани + Барањето е блокирано %1$d контакти Пораки Исчезнувачки пораки Безбедност на апликацијата - Блокирај слики од екранот во листата на последни апликации и во самата апликација + Блокирај слики од екранот во листата на неодамнешни разговори и во самата апликација Signal пораки и повици. Секогаш префрлај повици и запечатен испраќач. Стандарден тајмер за нови разговори Поставете стандарден тајмер за пораки што исчезнуваат за сите нови разговори започнати од Вас. @@ -4165,9 +4236,9 @@ Повикај - Исклучи + Исклучи известувања - Исклучен звук + Исклучени известувања Барај Исчезнувачки пораки @@ -4176,9 +4247,9 @@ Детали за контактот Види безбедносен број Блокирај - Блокирај група - Одблокирај - Одблокирај група + Блокирај ја групата + Деблокирај + Деблокирај ја групата Додај во група Види сѐ Додај членови @@ -4205,7 +4276,7 @@ Исклучи известувања - Звукот не е исклучен + Известувањата не се исклучени Спомнувања Секогаш известувај Не известувај @@ -4242,7 +4313,7 @@ %1$s беше отстранет - %1$s беше блокиран + Корисникот %1$s е блокиран Не може да се отстрани %1$s @@ -4508,7 +4579,7 @@ Вашата донација не можеше да се испрати поради мрежна грешка. Проверете ја интернет врската и обидете се повторно. - Донација за %1$s + Донација во име на %1$s %1$s донираше на Signal во ваше име @@ -5601,5 +5672,15 @@ Избришете го корисничкото име + + + ч + + м + + Постави + + Минималното време кое треба да помине пред да се примени заклучувањето на екранот е 1 минута. + diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 9fb69572d4..2130cf9320 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -14,6 +14,7 @@ + അതെ/ഉവ്വ് അല്ല/ഇല്ല @@ -96,13 +97,13 @@ സന്ദേശങ്ങൾക്കായി പരിശോധിക്കുന്നു… - തടഞ്ഞ ഉപയോക്താക്കൾ - തടഞ്ഞ ഉപയോക്താവിനെ ചേർക്കുക - തടഞ്ഞ ഉപയോക്താക്കൾക്ക് നിങ്ങളെ വിളിക്കാനോ നിങ്ങൾക്ക് സന്ദേശങ്ങള്‍ അയയ്ക്കാനോ കഴിയില്ല. - തടഞ്ഞ ഉപയോക്താക്കളൊന്നുമില്ല - ഉപയോക്താവിനെ തടയണോ? + ബ്ലോക്ക് ചെയ്ത ഉപയോക്താക്കൾ + ബ്ലോക്ക് ചെയ്ത ഉപയോക്താവിനെ ചേർക്കുക + ബ്ലോക്ക് ചെയ്ത ഉപയോക്താക്കൾക്ക് നിങ്ങളെ വിളിക്കാനോ നിങ്ങൾക്ക് സന്ദേശങ്ങള്‍ അയയ്ക്കാനോ കഴിയില്ല. + ബ്ലോക്ക് ചെയ്ത ഉപയോക്താക്കളൊന്നുമില്ല + ഉപയോക്താവിനെ ബ്ലോക്ക് ചെയ്യണോ? \"%1$s\" എന്നയാൾക്ക് നിങ്ങളെ വിളിക്കാനോ നിങ്ങൾക്ക് സന്ദേശങ്ങള്‍ അയയ്ക്കാനോ കഴിയില്ല. - തടയുക + ബ്ലോക്ക് ചെയ്യുക @@ -138,8 +139,8 @@ തുടരുക - %1$s എന്നത് തടഞ്ഞ ശേഷം പുറത്തുപോകണോ? - %1$s എന്നതിനെ തടയണോ ? + %1$s എന്നത് ബ്ലോക്ക് ചെയ്ത ശേഷം പുറത്തുപോകണോ? + %1$s എന്നതിനെ ബ്ലോക്ക് ചെയ്യണോ? ഈ ഗ്രൂപ്പിൽ നിന്ന് നിങ്ങൾക്ക് മേലിൽ സന്ദേശങ്ങളോ അപ്‌ഡേറ്റുകളോ ലഭിക്കില്ല, അംഗങ്ങൾക്ക് നിങ്ങളെ ഈ ഗ്രൂപ്പിലേക്ക് വീണ്ടും ചേർക്കാൻ കഴിയില്ല. ഗ്രൂപ്പ് അംഗങ്ങൾക്ക് നിങ്ങളെ വീണ്ടും ഈ ഗ്രൂപ്പിലേക്ക് ചേർക്കാൻ കഴിയില്ല. ഗ്രൂപ്പ് അംഗങ്ങൾക്ക് നിങ്ങളെ വീണ്ടും ഈ ഗ്രൂപ്പിലേക്ക് ചേർക്കാൻ കഴിയും. @@ -147,16 +148,16 @@ നിങ്ങൾക്ക് പരസ്പരം സന്ദേശമയയ്‌ക്കാനും വിളിക്കാനും കഴിയും, കൂടാതെ നിങ്ങളുടെ പേരും ഫോട്ടോയും അവരുമായി പങ്കിടും. നിങ്ങൾക്ക് പരസ്‌പരം സന്ദേശം അയയ്ക്കാൻ കഴിയും. - തടഞ്ഞ ആളുകൾക്ക് നിങ്ങളെ വിളിക്കാനോ നിങ്ങൾക്ക് സന്ദേശങ്ങൾ അയയ്ക്കാനോ കഴിയില്ല. - നിങ്ങൾക്ക് സന്ദേശങ്ങൾ അയയ്ക്കാൻ തടയപ്പെട്ടിരിക്കുന്നവർക്ക് സാധിക്കില്ല. + ബ്ലോക്ക് ചെയ്ത ആളുകൾക്ക് നിങ്ങളെ വിളിക്കാനോ നിങ്ങൾക്ക് സന്ദേശങ്ങൾ അയയ്ക്കാനോ കഴിയില്ല. + ബ്ലോക്ക് ചെയ്ത ആളുകൾക്ക് നിങ്ങൾക്ക് സന്ദേശങ്ങൾ അയയ്ക്കാൻ കഴിയില്ല. - Signal അപ്ഡേറ്റുകളും വാർത്തകളും ലഭിക്കുന്നത് തടയുക. + Signal അപ്ഡേറ്റുകളും വാർത്തകളും ലഭിക്കുന്നത് ബ്ലോക്ക് ചെയ്യുക. Signal അപ്‌ഡേറ്റുകളും വാർത്തകളും ലഭിക്കുന്നത് പുനരാരംഭിക്കുക. - %1$s-നെ തടഞ്ഞത് മാറ്റണോ? - തടയുക - തടഞ്ഞ ശേഷം പുറത്തുകടക്കുക - സ്പാമെന്ന് അടയാളപ്പെടുത്തി തടയുക + %1$s എന്നതിനെ ബ്ലോക്ക് ചെയ്തത് മാറ്റണോ? + ബ്ലോക്ക് ചെയ്യുക + ബ്ലോക്ക് ചെയ്ത ശേഷം പുറത്തുകടക്കുക + സ്പാമെന്ന് അടയാളപ്പെടുത്തി ബ്ലോക്ക് ചെയ്യുക ഇന്ന് @@ -318,7 +319,7 @@ Signal സന്ദേശം നമുക്ക് Molly-ലേക്ക് മാറാം %1$s ഒരു കോൺ‌ടാക്റ്റ് തിരഞ്ഞെടുക്കുക - തടഞ്ഞത് മാറ്റുക + അൺബ്ലോക്ക് ചെയ്യുക അറ്റാച്ച്മെന്റ് നിങ്ങൾ അയയ്‌ക്കുന്ന സന്ദേശ ഇനത്തിന്റെ വലുപ്പ പരിധി കവിഞ്ഞിരിക്കുന്നു. ഓഡിയോ റെക്കോർഡുചെയ്യാനായില്ല! നിലവിൽ അംഗമല്ലാത്തതിനാൽ നിങ്ങൾക്ക് ഈ ഗ്രൂപ്പിലേക്ക് സന്ദേശങ്ങൾ അയയ്ക്കാനാകില്ല. @@ -447,15 +448,15 @@ %1$s സജീവം - അഭ്യര്‍ത്ഥന തടയണോ? + അഭ്യര്‍ത്ഥന ബ്ലോക്ക് ചെയ്യണോ? %1$s എന്നയാൾക്ക് ഈ ഗ്രൂപ്പിൽ ചേരാനോ ഗ്രൂപ്പ് ലിങ്ക് വഴി ചേരാൻ അഭ്യർത്ഥിക്കാനോ കഴിയില്ല. അവരെ ഇപ്പോഴും ഗ്രൂപ്പിൽ നേരിട്ട് ചേർക്കാവുന്നതാണ്. - അഭ്യര്‍ത്ഥന തടയുക + അഭ്യര്‍ത്ഥന ബ്ലോക്ക് ചെയ്യുക റദ്ദാക്കൂ - തടഞ്ഞു + ബ്ലോക്ക് ചെയ്തു ഫിൽട്ടർ മായ്‌ക്കുക @@ -496,12 +497,12 @@ അൺപിൻ ചെയ്യുക - നിശബ്ദമാക്കുക - നിശബ്ദമാക്കുക + മ്യൂട്ട് ചെയ്യുക + മ്യൂട്ട് ചെയ്യുക - ശബ്‌ദിപ്പിക്കുക - ശബ്‌ദിപ്പിക്കുക + അൺമ്യൂട്ട് ചെയ്യുക + അൺമ്യൂട്ട് ചെയ്യുക തിരഞ്ഞെടുക്കൂ @@ -542,6 +543,15 @@ +%1$d + + നിങ്ങളുടെ ഉപകരണങ്ങൾ റീലിങ്ക് ചെയ്യുക + + നിങ്ങളുടെ ഉപകരണം അൺരജിസ്റ്റർ ചെയ്തപ്പോൾ നിങ്ങൾ ചേർത്ത ഉപകരണങ്ങൾ അൺലിങ്ക് ചെയ്തു. ഉപകരണങ്ങൾ റീലിങ്ക് ചെയ്യാൻ ക്രമീകരണത്തിലേക്ക് പോക്കുക. + + ക്രമീകരണങ്ങൾ തുറക്കുക + + പിന്നീട് + അംഗങ്ങളെ തിരഞ്ഞെടുക്കൂ @@ -935,7 +945,7 @@ സൂചനകൾ എന്നെ അറിയിക്കുക - നിശബ്ദമാക്കിയ ചാറ്റുകളിൽ നിങ്ങളെ സൂചിക്കുമ്പോൾ അറിയിപ്പുകൾ ലഭിക്കണോ? + മ്യൂട്ട് ചെയ്ത ചാറ്റുകളിൽ നിങ്ങളെ പരാമർശിക്കുമ്പോൾ അറിയിപ്പുകൾ ലഭിക്കണോ? എന്നെ അറിയിക്കുക എന്നെ അറിയിക്കരുത് @@ -953,6 +963,16 @@ ഉപയോക്തൃനാമം സൃഷ്‌ടിച്ചു ഉപയോക്തൃനാമം പകർത്തി + + ഉപയോക്തൃനാമം ഇല്ലാതാക്കാനായില്ല. പിന്നീട് വീണ്ടും ശ്രമിക്കുക. + + ഉപയോക്തൃനാമം ഇല്ലാതാക്കി + + + + നിങ്ങളുടെ ഉപയോക്തൃനാമവുമായി ബന്ധപ്പെട്ട് എന്തോ പിശകുണ്ടായി, ഇത് നിങ്ങളുടെ അക്കൗണ്ടിലേക്ക് അസൈൻ ചെയ്തിട്ടില്ല. നിങ്ങൾക്കത് വീണ്ടും സജ്ജമാക്കാൻ ശ്രമിക്കാം അല്ലെങ്കിൽ പുതിയൊരെണ്ണം തിരഞ്ഞെടുക്കാം. + + ഇപ്പോൾ പരിഹരിക്കുക @@ -1156,8 +1176,8 @@ പുതിയ ഗ്രൂപ്പ് സുഹൃത്തുക്കളെ ക്ഷണിക്കുക SMS ഉപയോഗിക്കുക - ദൃശ്യത - ഫോട്ടോ ചേർക്കുക + ചാറ്റ് നിറങ്ങൾ + ഒരു പ്രൊഫൈൽ ഫോട്ടോ ചേർക്കുക മറുപടികൾ @@ -1468,14 +1488,14 @@ സ്വീകരിക്കുക തുടരുക ഇല്ലാതാക്കൂ - തടയുക - തടഞ്ഞത് മാറ്റുക + ബ്ലോക്ക് ചെയ്യുക + അൺബ്ലോക്ക് ചെയ്യുക %1$s നിങ്ങൾക്ക് സന്ദേശം അയയ്‌ക്കാനും നിങ്ങളുടെ പേരും ഫോട്ടോയും അവരുമായി പങ്കിടട്ടെ? നിങ്ങൾ അംഗീകരിക്കുന്നതുവരെ നിങ്ങൾ അവരുടെ സന്ദേശം കണ്ടതായി അവർക്ക് അറിയില്ല. - നിങ്ങൾക്ക് %1$s മെസേജ് അയയ്ക്കാൻ അനുവദിക്കണോ? നിങ്ങൾ അവരെ അൺ‌ബ്ലോക്ക് ചെയ്യുന്നതു വരെ നിങ്ങൾക്ക് മെസേജ് ഒന്നും ലഭിക്കില്ല. + നിങ്ങൾക്ക് മെസേജ് അയയ്ക്കാൻ %1$s എന്നയാളെ അനുവദിക്കണോ? നിങ്ങൾ അവരെ അൺബ്ലോക്ക് ചെയ്യുന്നത് വരെ നിങ്ങൾക്ക് മെസേജ് ഒന്നും ലഭിക്കില്ല. %1$s എന്നയാളെ നിങ്ങൾക്ക് സന്ദേശം അയയ്ക്കാൻ അനുവദിക്കണോ? നിങ്ങൾ അൺബ്ലോക്ക് ചെയ്യുന്നത് വരെ നിങ്ങൾക്ക് സന്ദേശങ്ങളൊന്നും ലഭിക്കില്ല. - %1$s നിന്ന് സമകാലികവിവരങ്ങളും വാര്‍ത്തകളും ലഭിക്കണോ? നിങ്ങള്‍ അവരെ അണ്‍ബ്ലോക്ക് ചെയുന്നതുവരെ സമകാലികവിവരങ്ങളെന്നും ലഭിക്കില്ല. + %1$s എന്നതിൽ നിന്ന് സമകാലികവിവരങ്ങളും വാര്‍ത്തകളും ലഭിക്കണോ? നിങ്ങള്‍ അവരെ അണ്‍ബ്ലോക്ക് ചെയുന്നതു വരെ സമകാലിക വിവരങ്ങളൊന്നും ലഭിക്കില്ല. ഈ ഗ്രൂപ്പുമായുള്ള സംഭാഷണം തുടരുകയും നിങ്ങളുടെ പേരും ഫോട്ടോയും അതിന്റെ അംഗങ്ങളുമായി പങ്കിടുകയും ചെയ്യണോ? \@സൂചനകളും അഡ്‌മിനുകളും പോലുള്ള പുതിയ സവിശേഷതകൾ സജീവമാക്കുന്നതിന് ഈ ഗ്രൂപ്പ് അപ്‌ഗ്രേഡുചെയ്യുക. ഈ ഗ്രൂപ്പിൽ അവരുടെ പേരോ ഫോട്ടോയോ പങ്കിടാത്ത അംഗങ്ങളെ ചേരാൻ ക്ഷണിക്കും. ഈ ലെഗസി ഗ്രൂപ്പ് ഇനി ഉപയോഗിക്കാൻ കഴിയില്ല കാരണം ഇത് വളരെ വലുതാണ്. പരമാവധി ഗ്രൂപ്പ് വലുപ്പം %1$d ആണ്. @@ -1483,7 +1503,7 @@ ഈ ഗ്രൂപ്പിൽ ചേര്‍ൻ, നിങ്ങളുടെ പേരും ഫോട്ടോയും അതിന്റെ അംഗങ്ങളുമായി പങ്കിടണോ? നിങ്ങൾ സ്വീകരിക്കുന്നതുവരെ നിങ്ങൾ അവരുടെ സന്ദേശങ്ങൾ കണ്ടതായി അവർക്ക് അറിയില്ല. ഈ ഗ്രൂപ്പിൽ ചേർന്ന് അതിലെ അംഗങ്ങൾക്ക് നിങ്ങളുടെ പേരും ഫോട്ടോയും പങ്കിടണോ? നിങ്ങൾ അംഗീകരിക്കുന്നത് വരെ അവരുടെ സന്ദേശങ്ങൾ നിങ്ങൾക്ക് കാണാനാവില്ല. ഈ ഗ്രൂപ്പിൽ ചേരണോ? നിങ്ങൾ സ്വീകരിക്കുന്നതുവരെ അവരുടെ സന്ദേശങ്ങൾ കണ്ടതായി അവർക്ക് അറിയില്ല. - ഈ ഗ്രൂപ്പിനെ തടഞ്ഞത് മാറ്റുകയും നിങ്ങളുടെ പേരും ഫോട്ടോയും അതിന്റെ അംഗങ്ങളുമായി പങ്കിടണോ? നിങ്ങൾ അൺ‌ബ്ലോക്ക് ചെയ്യുന്നതു വരെ നിങ്ങൾക്ക് സന്ദേശങ്ങളൊന്നും ലഭിക്കില്ല. + ഈ ഗ്രൂപ്പിനെ അൺബ്ലോക്ക് ചെയ്ത് നിങ്ങളുടെ പേരും ഫോട്ടോയും അതിലെ അംഗങ്ങളുമായി പങ്കിടണോ? നിങ്ങൾ അൺബ്ലോക്ക് ചെയ്യുന്നത് വരെ നിങ്ങൾക്ക് സന്ദേശങ്ങളൊന്നും ലഭിക്കില്ല. കാണുക %1$s അംഗം @@ -1584,9 +1604,20 @@ പുതിയ PIN സൃഷ്ടിക്കുക + + SMS കോഡ് അയയ്ക്കുക + + Signal രജിസ്ട്രേഷൻ - Android-ൽ PIN വീണ്ടും രജിസ്റ്റർ ചെയ്യാൻ സഹായം വേണം + + നിങ്ങൾ സൃഷ്ടിച്ച %1$d+ അക്ക കോഡാണ് നിങ്ങളുടെ പിൻ, അത് ന്യൂമറിക്കോ ആൽഫാന്യൂമറിക്കോ ആയേക്കാം.\n\nനിങ്ങളുടെ PIN ഓർമ്മയില്ലെങ്കിൽ, പുതിയൊരെണ്ണം സൃഷ്ടിക്കാനാകും. + + നിങ്ങളുടെ PIN ഓർമ്മയില്ലെങ്കിൽ, പുതിയൊരെണ്ണം സൃഷ്ടിക്കാനാകും. + + PIN ഊഹിക്കലുകൾക്കുള്ള ഊഴം കഴിഞ്ഞു, പക്ഷെ പുതിയൊരു PIN സൃഷ്ടിച്ച് നിങ്ങൾക്ക് ഇപ്പോഴും നിങ്ങളുടെ Signal അക്കൗണ്ട് ആക്‌സസ് ചെയ്യാനാകും. + മുന്നറിയിപ്പ് - നിങ്ങൾ‌ PIN (പിൻ) അപ്രാപ്‌തമാക്കുകയാണെങ്കിൽ‌, നിങ്ങൾ‌ സ്വമേധയാ ബാക്കപ്പുചെയ്‌ത് പുന.സ്ഥാപിച്ചില്ലെങ്കിൽ,‌ നിങ്ങൾ‌ സിഗ്നൽ‌ വീണ്ടും രജിസ്റ്റർ‌ ചെയ്യുമ്പോൾ‌ എല്ലാ ഡാറ്റയും നഷ്‌ടപ്പെടും. PIN (പിൻ) പ്രവർത്തനരഹിതമാക്കിയിരിക്കുമ്പോൾ നിങ്ങൾക്ക് രജിസ്ട്രേഷൻ ലോക്ക് ഓണാക്കാൻ കഴിയില്ല. + നിങ്ങൾ PIN പ്രവർത്തനരഹിതമാക്കുകയാണെങ്കിൽ, നിങ്ങൾ നേരിട്ട് ബാക്കപ്പ് ചെയ്യുകയും പുനഃസ്ഥാപിക്കുകയും ചെയ്യാത്ത പക്ഷം, Signal വീണ്ടും രജിസ്റ്റർ ചെയ്യുമ്പോൾ എല്ലാ ഡാറ്റയും നഷ്ടപ്പെടും. PIN പ്രവർത്തനരഹിതമാക്കിയിരിക്കുമ്പോൾ നിങ്ങൾക്ക് രജിസ്ട്രേഷൻ ലോക്ക് ഓണാക്കാൻ കഴിയില്ല. PIN അപ്രാപ്‌തമാക്കുക @@ -1616,8 +1647,8 @@ എന്റെ സ്റ്റോറി - തടയുക - തടഞ്ഞത് മാറ്റുക + ബ്ലോക്ക് ചെയ്യുക + അൺബ്ലോക്ക് ചെയ്യുക @@ -1711,9 +1742,9 @@ ക്യാമറ - ശബ്‌ദിപ്പിക്കുക + അൺമ്യൂട്ട് ചെയ്യുക - നിശബ്ദമാക്കുക + മ്യൂട്ട് ചെയ്യുക റിംഗ് @@ -1726,7 +1757,7 @@ - %1$s - നെ തടഞ്ഞിരിക്കുന്നു + %1$s എന്നയാളെ ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു കൂടുതൽ വിവരങ്ങൾ നിങ്ങൾക്ക് അവരുടെ ഓഡിയോ വീഡിയോ ലഭിക്കില്ല, അവർക്ക് നിങ്ങളുടേതും . %1$s നിന്ന് ഓഡിയോയും വീഡിയോയും സ്വീകരിക്കാൻ കഴിയുന്നില്ല  @@ -1768,11 +1799,18 @@ സുഹൃത്തുക്കളുമായി ബന്ധപ്പെടാനും സന്ദേശങ്ങള്‍ അയക്കാനും നിങ്ങളെ സഹായിക്കുന്നതിന് Signal-ന് കോൺ‌ടാക്റ്റുകൾ, മീഡിയ അനുമതികൾ ആവശ്യമാണ്. Signal-ന്റെ സ്വകാര്യ കോൺടാക്റ്റ് കണ്ടെത്തൽ ഉപയോഗിച്ചാണ് നിങ്ങളുടെ കോൺ‌ടാക്റ്റുകൾ അപ്‌ലോഡ് ചെയ്‌തിരിക്കുന്നത്, അതിനർത്ഥം അവ ആദ്യാവസാന എൻക്രിപ്റ്റ് ചെയ്‌തതും Signal സേവനത്തിന് ഒരിക്കലും ദൃശ്യമാകാത്തതുമാണ്. സുഹൃത്തുക്കളുമായി ബന്ധപ്പെടാൻ നിങ്ങളെ സഹായിക്കുന്നതിന് Signal-ന് കോൺ‌ടാക്റ്റുകൾ അനുമതി ആവശ്യമാണ്. Signal-ന്റെ സ്വകാര്യ കോൺടാക്റ്റ് കണ്ടെത്തൽ ഉപയോഗിച്ചാണ് നിങ്ങളുടെ കോൺ‌ടാക്റ്റുകൾ അപ്‌ലോഡ് ചെയ്‌തിരിക്കുന്നത്, അതിനർത്ഥം അവ ആദ്യാവസാന എൻക്രിപ്റ്റ് ചെയ്‌തതും Signal സേവനത്തിന് ഒരിക്കലും ദൃശ്യമാകാത്തതുമാണ്. ഈ നമ്പർ രജിസ്റ്റർ ചെയ്യുന്നതിന് നിങ്ങൾ വളരെയധികം ശ്രമങ്ങൾ നടത്തി. പിന്നീട് വീണ്ടും ശ്രമിക്കുക. + + ഈ നമ്പർ രജിസ്റ്റർ ചെയ്യാൻ നിങ്ങൾ നിരവധി ശ്രമങ്ങൾ നടത്തി. %1$s സമയത്തിന് ശേഷം വീണ്ടും ശ്രമിക്കുക. സേവനത്തിലേക്ക് കണക്റ്റുചെയ്യാനായില്ല. നെറ്റ്‌വർക്ക് കണക്ഷൻ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക. അംഗീകൃതമല്ലാത്ത നമ്പര്‍ രൂപം നിങ്ങൾ നൽകിയ നമ്പർ (%1$s) അംഗീകൃതമല്ലാത്ത നമ്പര്‍ രൂപത്തില്‍ ആണെന്ന് തോന്നുന്നു.\n\nനിങ്ങൾ ഉദ്ദേശിച്ചത് %2$s എന്നാണോ? Molly ആൻഡ്രോയിഡ് - ഫോണ്‍ നമ്പര്‍ രുപം + കോള്‍ അഭ്യർത്ഥിച്ചു + + SMS അഭ്യർത്ഥിച്ചു + + പരിശോധിച്ചുറപ്പിക്കൽ കോഡ് അഭ്യർത്ഥിച്ചു ഒരു ഡീബഗ് ലോഗ് സമർപ്പിക്കുന്നതിൽ നിന്ന് നിങ്ങൾ ഇപ്പോൾ %1$d പടി അകലെയാണ്. ഒരു ഡീബഗ് ലോഗ് സമർപ്പിക്കുന്നതിൽ നിന്ന് നിങ്ങൾ ഇപ്പോൾ %1$d ഘട്ടങ്ങൾ അകലെയാണ്. @@ -1792,6 +1830,16 @@ വിളിക്കുക പരിശോധിച്ചുറപ്പിക്കൽ കോഡ് കോഡ് വീണ്ടും അയയ്‌ക്കുക + + രജിസ്റ്റർ ചെയ്യുന്നതിൽ ബുദ്ധിമുട്ട് നേരിടുന്നുണ്ടോ? + + • SMS-ഓ കോളോ ലഭിക്കാൻ നിങ്ങളുടെ ഫോണിൽ സെല്ലുലാർ സേവനമുണ്ടെന്ന് ഉറപ്പാക്കുക\n • ആ നമ്പറിലേക്ക് നിങ്ങൾക്ക് ഫോൺ കോൾ ലഭിക്കുമെന്ന് സ്ഥിരീകരിക്കുക • നിങ്ങളുടെ ഫോൺ നമ്പർ ശരിയായാണോ നൽകിയിരിക്കുന്നത് എന്ന് പരിശോധിക്കുക. + + കൂടുതൽ വിവരങ്ങൾക്ക്, ഈ ട്രബിൾഷൂട്ടിംഗ് ഘട്ടങ്ങൾ പാലിക്കുക അല്ലെങ്കിൽ പിന്തുണയുമായി ബന്ധപ്പെടുക + + ഈ ട്രബിൾഷൂട്ടിംഗ് ഘട്ടങ്ങൾ + + പിന്തുണയുമായി ബന്ധപ്പെടുക രജിസ്ട്രേഷൻ ലോക്ക് ഓണാക്കണോ? @@ -1958,6 +2006,10 @@ പേയ്മെന്റ് ഷെഡ്യൂൾ ചെയ്‌ത സന്ദേശങ്ങൾ + + നിങ്ങളുടെ സന്ദേശ ചരിത്രം മെർജ് ചെയ്തു + + %1$s %2$s എന്നയാളുടെ നമ്പറാണ് Molly അപ്‌ഡേറ്റ് @@ -2030,7 +2082,7 @@ നിലവിലില്ലാത്ത സെഷനായി MMS സന്ദേശം എൻ‌ക്രിപ്റ്റ് ചെയ്തു - അറിയിപ്പുകൾ നിശബ്ദമാക്കുക + അറിയിപ്പുകൾ മ്യൂട്ട് ചെയ്യുക ഇറക്കുമതി പുരോഗതിയിലാണ് @@ -2087,14 +2139,16 @@ സുരക്ഷിതമല്ലാത്ത SMS %1$s %2$s കോൺ‌ടാക്റ്റ് - \"%2$s\"- നോട് %1$s പ്രതികരിച്ചു - നിങ്ങളുടെ വീഡിയോയോട് %1$s പ്രതികരിച്ചു. - നിങ്ങളുടെ ചിത്രത്തോട് %1$s പ്രതികരിച്ചു. - നിങ്ങളുടെ GIF-നോട് %1$s എന്ന് പ്രതികരിച്ചു. - നിങ്ങളുടെ ഫയലിനോട് %1$s പ്രതികരിച്ചു. - നിങ്ങളുടെ ഓഡിയോയോട് %1$s പ്രതികരിച്ചു. - നിങ്ങളുടെ ഒരിക്കൽ മാത്രം കാണാവുന്ന മീഡിയയിലേക്കുള്ള പ്രതികരണം %1$s ആണ് - നിങ്ങളുടെ സ്റ്റിക്കറിനോട് %1$s പ്രതികരിച്ചു. + \"%2$s\" എന്നതിനോട് %1$s നൽകി പ്രതികരിച്ചു + നിങ്ങളുടെ വീഡിയോയോട് %1$s നൽകി പ്രതികരിച്ചു. + നിങ്ങളുടെ ചിത്രത്തോട് %1$s നൽകി പ്രതികരിച്ചു. + നിങ്ങളുടെ GIF-നോട് %1$s നൽകി പ്രതികരിച്ചു. + നിങ്ങളുടെ ഫയലിനോട് %1$s നൽകി പ്രതികരിച്ചു. + നിങ്ങളുടെ ഓഡിയോയോട് %1$s നൽകി പ്രതികരിച്ചു. + ഒരിക്കൽ മാത്രം കാണാവുന്ന മീഡിയയോട് %1$s നൽകി പ്രതികരിച്ചു. + + നിങ്ങളുടെ പേയ്മെന്‍റിനോട് %1$s എന്ന് പ്രതികരിച്ചു. + നിങ്ങളുടെ സ്റ്റിക്കറിനോട് %1$s നൽകി പ്രതികരിച്ചു. ഈ സന്ദേശം ഇല്ലാതാക്കി. \"കോൺ‌ടാക്റ്റ് Signal-ഇൽ (സിഗ്‌നലിൽ)‌ ചേർ‌ന്നു\" എന്ന അറിയിപ്പുകൾ ഓഫാക്കണോ? നിങ്ങൾക്ക് അവ വീണ്ടും പ്രവർത്തനക്ഷമമാക്കാം: Signal-ഇൽ (സിഗ്നലിൽ) > ക്രമീകരണങ്ങൾ > അറിയിപ്പുകൾ. @@ -2298,7 +2352,7 @@ കോൾ അറിയിപ്പുകൾ പ്രാപ്തമാക്കുക കോൺ‌ടാക്റ്റ് പുതുക്കുക - അഭ്യര്‍ത്ഥന തടയുക + അഭ്യര്‍ത്ഥന ബ്ലോക്ക് ചെയ്യുക പൊതുവായി ഗ്രൂപ്പുകളൊന്നുമില്ല. അഭ്യർത്ഥനകൾ ശ്രദ്ധാപൂർവ്വം അവലോകനം ചെയ്യുക. ഈ ഗ്രൂപ്പിൽ കോൺടാക്റ്റുകളൊന്നുമില്ല. അഭ്യർത്ഥനകൾ ശ്രദ്ധാപൂർവ്വം അവലോകനം ചെയ്യുക. കാണുക @@ -2487,8 +2541,8 @@ ഡെലിവറി പ്രശ്നം - %1$s-ൽ നിന്ന് ഒരു സന്ദേശം, സ്റ്റിക്കർ, പ്രതികരണം, അല്ലെങ്കിൽ വായിച്ച രസീത് എന്നിവ നിങ്ങൾക്ക് വേണ്ടി ഡെലിവർ ചെയ്യാൻ കഴിഞ്ഞില്ല. അവർ അത് നേരിട്ടോ അല്ലെങ്കിൽ ഒരു ഗ്രൂപ്പിലോ നിങ്ങൾക്ക് അയയ്ക്കാൻ ശ്രമിച്ചിരിക്കാം. - %1$s-ൽ നിന്ന് ഒരു സന്ദേശം, സ്റ്റിക്കർ, പ്രതികരണം, അല്ലെങ്കിൽ വായിച്ച രസീത് എന്നിവ നിങ്ങൾക്ക് വേണ്ടി ഡെലിവർ ചെയ്യാൻ കഴിഞ്ഞില്ല. + %1$s എന്നയാളിൽ നിന്ന് സന്ദേശം, സ്റ്റിക്കർ, പ്രതികരണം, അല്ലെങ്കിൽ വായിച്ചെന്ന രസീത് എന്നിവ നിങ്ങൾക്ക് ലഭിക്കില്ല. അവർ അത് നേരിട്ടോ അല്ലെങ്കിൽ ഒരു ഗ്രൂപ്പിലോ നിങ്ങൾക്ക് അയയ്ക്കാൻ ശ്രമിച്ചിരിക്കാം. + %1$s എന്നയാളിൽ നിന്ന് സന്ദേശം, സ്റ്റിക്കർ, പ്രതികരണം, അല്ലെങ്കിൽ വായിച്ചെന്ന രസീത് എന്നിവ നിങ്ങൾക്ക് ലഭിക്കില്ല. പേരിൻ്റെ ആദ്യ ഭാഗം (ആവശ്യമാണ്) @@ -2555,7 +2609,7 @@ ശേഷിക്കുന്നു - അയച്ചത് + ഇനിപ്പറയുന്നയാൾക്ക് അയച്ചു അയച്ചത് കൈമാറിയത് വായിച്ചത് @@ -2635,10 +2689,10 @@ സ്ഥിരസ്ഥിതി ഉപയോഗിക്കുക ഇഷ്‌ടാനുസൃതം ഉപയോഗിക്കുക - 1 മണിക്കൂറിന് നിശബ്ദമാക്കുക - 8 മണിക്കൂർ നിശബ്ദമാക്കുക - 1 ദിവസത്തേക്ക് നിശബ്ദമാക്കുക - 7 ദിവസത്തേക്ക് നിശബ്ദമാക്കുക + 1 മണിക്കൂർ നേരത്തേക്ക് മ്യൂട്ട് ചെയ്യുക + 8 മണിക്കൂർ നേരത്തേക്ക് മ്യൂട്ട് ചെയ്യുക + 1 ദിവസത്തേക്ക് മ്യൂട്ട് ചെയ്യുക + 7 ദിവസത്തേക്ക് മ്യൂട്ട് ചെയ്യുക എല്ലായ്പ്പോഴും ക്രമീകരണ സ്ഥിരസ്ഥിതി @@ -2675,9 +2729,9 @@ അഡ്രസ്സ് ബുക്കിലെ ഫോട്ടോകൾ ഉപയോഗിക്കുക നിങ്ങളുടെ അഡ്രസ്സ് ബുക്കിൽ നിന്നുള്ള ഫോട്ടോസ് ലഭ്യമെങ്കിൽ ഡിസ്പ്ളേ ചെയ്യുക - മ്യൂട്ട് ചെയ്ത ചാറ്റുകൾ ആർക്കൈവായി തന്നെ വയ്ക്കുക + മ്യൂട്ട് ചെയ്ത ചാറ്റുകൾ ആർക്കൈവായി വയ്ക്കുക - ആർക്കൈവിലുള്ളള മ്യൂട്ട് ചെയ്ത ചാറ്റുകൾ പുതിയ സന്ദേശം വരുമ്പോൾ ആർക്കൈവായി തന്നെ തുടരും. + ആർക്കൈവിലുള്ളള മ്യൂട്ട് ചെയ്ത ചാറ്റുകൾ പുതിയ സന്ദേശം വരുമ്പോൾ ആർക്കൈവായി തന്നെ തുടരും. ലിങ്ക് പ്രിവ്യൂകൾ സൃഷ്ടിക്കുക നിങ്ങൾ അയയ്‌ക്കുന്ന സന്ദേശങ്ങൾക്കായി വെബ്‌സൈറ്റുകളിൽ നിന്ന് ലിങ്ക് പ്രിവ്യൂകൾ നേരിട്ട് വീണ്ടെടുക്കുക. രഹസ്യവാചകം മാറ്റുക @@ -2971,7 +3025,7 @@ പേയ്മെന്റ് അയച്ചു പേയ്മെന്റ് ലഭിച്ചു പേയ്മെന്റ് പൂർത്തിയായി %1$s - നമ്പർ തടയുക + നമ്പർ ബ്ലോക്ക് ചെയ്യുക കൈമാറ്റം @@ -3056,7 +3110,7 @@ പുതിയ സന്ദേശം… - ഉപയോക്താവിനെ തടയുക + ഉപയോക്താവിനെ ബ്ലോക്ക് ചെയ്യുക ഗ്രൂപ്പിലേക്ക് ചേർക്കുക @@ -3128,10 +3182,10 @@ - ശബ്‌ദിപ്പിക്കുക + അൺമ്യൂട്ട് ചെയ്യുക - അറിയിപ്പുകൾ നിശബ്ദമാക്കുക + അറിയിപ്പുകൾ മ്യൂട്ട് ചെയ്യുക ഗ്രൂപ്പ് ക്രമീകരണങ്ങൾ @@ -3295,6 +3349,8 @@ നിങ്ങളുടെ PIN നൽകുക നിങ്ങളുടെ അക്കൗണ്ടിനായി നിങ്ങൾ സൃഷ്‌ടിച്ച പിൻ നൽകുക. ഇത് നിങ്ങളുടെ SMS പരിശോധന കോഡിൽ നിന്ന് വ്യത്യസ്തമാണ്. + + നിങ്ങളുടെ അക്കൗണ്ടിനായി സൃഷ്ടിച്ച PIN നൽകുക. ആൽഫാന്യൂമെറിക് പിൻ നൽകുക സംഖ്യാ പിൻ നൽകുക PIN തെറ്റാണ്. വീണ്ടും ശ്രമിക്കുക. @@ -3398,7 +3454,10 @@ ബാക്കപ്പ് ചെയ്യാൻ കഴിയാത്ത വളരെ വലിയ ഒരു ഫയൽ നിങ്ങളുടെ ബാക്കപ്പിൽ ഉൾപ്പെട്ടിട്ടുണ്ട്. അത് ഇല്ലാതാക്കിയ ശേഷം പുതിയ ഒരു ബാക്കപ്പ് സൃഷ്‌ടിക്കുക. ബാക്കപ്പുകൾ നിയന്ത്രിക്കാൻ തൊടുക. നമ്പർ തെറ്റാണോ? + എന്നെ വിളിക്കൂ (%1$02d%2$02d) + + കോഡ് വീണ്ടും അയയ്ക്കുക (%1$02d:%2$02d) Signal പിന്തുണ ബന്ധപ്പെടുക Signal രജിസ്ട്രേഷൻ - Android-നായുള്ള സ്ഥിരീകരണ കോഡ് കോഡ് തെറ്റാണ് @@ -3406,6 +3465,18 @@ അജ്ഞാതം എന്റെ ഫോൺ നമ്പർ കാണുക ഫോൺ നമ്പർ ഉപയോഗിച്ച് എന്നെ കണ്ടെത്തുക + + ഫോൺ നമ്പർ + + നിങ്ങളുടെ ഫോൺ നമ്പർ ആർക്കൊക്കെ കാണാനാകുമെന്നും ആർക്കൊക്കെ നിങ്ങളെ Molly-ൽ ബന്ധപ്പെടാനാകുമെന്നും തിരഞ്ഞെടുക്കൂ. + + എന്റെ നമ്പർ ആർക്കൊക്കെ കാണാനാകും + + Molly-ൽ നിങ്ങളുടെ ഫോൺ നമ്പർ ആർക്കും കാണാനാകില്ല + + നമ്പർ ഉപയോഗിച്ച് എന്നെ ആർക്കൊക്കെ കണ്ടെത്താനാകും + + നിങ്ങൾ സന്ദേശമയയ്‌ക്കുന്ന ആളുകൾക്കും ഗ്രൂപ്പുകൾക്കും നിങ്ങളുടെ ഫോൺ നമ്പർ ദൃശ്യമാകും. ഫോൺ കോൺടാക്റ്റുകളിൽ നിങ്ങളുടെ നമ്പർ ഉള്ള ആളുകൾക്ക് ഈ നമ്പർ Molly-ലും കാണാനാകും. എല്ലാവരും എന്റെ കോൺടാക്റ്റുകൾ ആരുംതന്നെയില്ല @@ -3562,8 +3633,8 @@ - തടയുക - തടഞ്ഞത് മാറ്റുക + ബ്ലോക്ക് ചെയ്യുക + അൺബ്ലോക്ക് ചെയ്യുക കോൺടാക്റ്റുകളിൽ ചേർക്കൂ കോൺ‌ടാക്റ്റുകൾ തുറക്കാൻ കഴിയുന്ന ഒരു അപ്ലിക്കേഷൻ കണ്ടെത്താനായില്ല. @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" - നെ തടഞ്ഞു - \"%1$s\" - നെ തടയുന്നതിൽ പരാജയപെട്ടു - \"%1$s\"-നെ തടഞ്ഞത് മാറ്റി. + \"%1$s\" എന്നയാളെ ബ്ലോക്ക് ചെയ്തു + \"%1$s\" എന്നയാളെ ബ്ലോക്ക് ചെയ്യാനായില്ല + \"%1$s\" എന്നയാളെ അൺബ്ലോക്ക് ചെയ്തു. അംഗങ്ങളെ അവലോകനം ചെയ്യുക @@ -3644,7 +3715,7 @@ നിങ്ങളുടെ കോൺ‌ടാക്റ്റ് ഗ്രൂപ്പിൽ നിന്ന് നീക്കം ചെയ്യൂ കോൺ‌ടാക്റ്റ് പുതുക്കുക - തടയുക + ബ്ലോക്ക് ചെയ്യുക ഇല്ലാതാക്കൂ അടുത്തിടെ അവരുടെ പ്രൊഫൈൽ പേര് %1$s-ൽ നിന്ന് %2$s-ലേക്ക് മാറ്റി @@ -3669,15 +3740,15 @@ നിങ്ങളുടെ അക്കൗണ്ട് ഇല്ലാതാക്കുന്നത്: നിങ്ങളുടെ ഫോൺ നമ്പർ നൽകുക - അക്കൗണ്ട് ഇല്ലാതാക്കൂ - നിങ്ങളുടെ അക്കൗണ്ട് വിവരവും പ്രൊഫൈൽ ഫോട്ടോയും ഇല്ലാതാക്കൂ + അക്കൗണ്ട് ഇല്ലാതാക്കും + നിങ്ങളുടെ അക്കൗണ്ട് വിവരങ്ങളും പ്രൊഫൈൽ ഫോട്ടോയും ഇല്ലാതാക്കും നിങ്ങളുടെ എല്ലാ സന്ദേശങ്ങളും ഇല്ലാതാക്കൂ നിങ്ങളുടെ പേയ്മെന്റ് അക്കൗണ്ടിലെ %1$s ഇല്ലാതാക്കുക രാജ്യ കോഡുകളൊന്നും വ്യക്തമാക്കിയിട്ടില്ല നമ്പർ വ്യക്തമാക്കിയിട്ടില്ല നിങ്ങൾ നൽകിയ ഫോൺ നമ്പർ നിങ്ങളുടെ അക്കൗണ്ടുമായി പൊരുത്തപ്പെടുന്നില്ല. നിങ്ങളുടെ അക്കൗണ്ട് ഇല്ലാതാക്കണം എന്ന് ഉറപ്പാണോ? - ഇത് നിങ്ങളുടെ Signal അക്കൗണ്ട് ഇല്ലാതാക്കുകയും അപ്ലിക്കേഷൻ പുനഃസജ്ജമാക്കുകയും ചെയ്യും. പ്രക്രിയ പൂർത്തിയായ ശേഷം ആപ്പ് അടയ്‌ക്കും. + ഇത് നിങ്ങളുടെ Signal അക്കൗണ്ട് ഇല്ലാതാക്കുകയും ആപ്ലിക്കേഷൻ റീസെറ്റ് ചെയ്യുകയും ചെയ്യും. പ്രക്രിയ പൂർത്തിയായ ശേഷം ആപ്പ് അടയ്‌ക്കും. പ്രാദേശിക ഡാറ്റ ഇല്ലാതാക്കാനായില്ല. സിസ്റ്റം അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങളിൽ നിങ്ങൾക്ക് ഇത് നേരിട്ട് ഇല്ലാതാക്കാം. ആപ്പ് ക്രമീകരണങ്ങൾ തുറക്കുക @@ -3784,7 +3855,7 @@ വാലറ്റ് നിർജ്ജീവമാക്കുക നിങ്ങളുടെ ബാലൻസ് - പേയ്‌മെന്റുകൾ നിർജ്ജീവമാക്കുന്നതിന് മുമ്പ് നിങ്ങളുടെ ഫണ്ടുകൾ മറ്റൊരു വാലറ്റ് വിലാസത്തിലേക്ക് ട്രാൻസ്ഫർ ചെയ്യാൻ ശുപാർശ ചെയ്യുന്നു. നിങ്ങളുടെ ഫണ്ടുകൾ ഇപ്പോൾ ട്രാൻസ്ഫർ ചെയ്യാതിരിക്കാൻ നിങ്ങൾ തീരുമാനിക്കുകയാണെങ്കിൽ, നിങ്ങൾ പേയ്‌മെന്റുകൾ വീണ്ടും സജീവമാക്കുകയാണെങ്കിൽ, Molly-മായി ബന്ധിപ്പിച്ചിട്ടുള്ള നിങ്ങളുടെ വാലറ്റിൽ അവ തുടരും. + പേയ്‌മെന്റുകൾ നിർജ്ജീവമാക്കുന്നതിന് മുമ്പ് നിങ്ങളുടെ ഫണ്ടുകൾ മറ്റൊരു വാലറ്റ് വിലാസത്തിലേക്ക് ട്രാൻസ്ഫർ ചെയ്യാൻ ശുപാർശ ചെയ്യുന്നു. നിങ്ങളുടെ ഫണ്ടുകൾ ഇപ്പോൾ ട്രാൻസ്ഫർ ചെയ്യാതിരിക്കാൻ നിങ്ങൾ തീരുമാനിക്കുകയും, നിങ്ങൾ പേയ്‌മെന്റുകൾ വീണ്ടും സജീവമാക്കുകയുമാണെങ്കിൽ, Molly-മായി ബന്ധിപ്പിച്ചിട്ടുള്ള നിങ്ങളുടെ വാലറ്റിൽ അവ തുടരും. ബാക്കിയുള്ള ബാലൻസ് ട്രാൻസ്ഫർ ചെയ്യുക ട്രാൻസ്ഫർ ചെയ്യാതെ നിർജ്ജീവമാക്കുക നിർജ്ജീവമാക്കുക @@ -4003,12 +4074,12 @@ രൂപരേഖ സൃഷ്‌ടിക്കുക - തടഞ്ഞത് + ബ്ലോക്ക് ചെയ്തു %1$d കോൺ‌ടാക്റ്റുകൾ സന്ദേശ വിനിമയം അപ്രത്യക്ഷമാകുന്ന സന്ദേശങ്ങൾ ആപ്പ് സുരക്ഷ - റീസന്റ് ലിസ്റ്റിലും അപ്ലിക്കേഷനിലും സ്‌ക്രീൻഷോട്ടുകൾ തടയുക + റീസന്റ് ലിസ്റ്റിലും ആപ്പിനുള്ളിലും സ്‌ക്രീൻഷോട്ടുകൾ ബ്ലോക്ക് ചെയ്യുക Signal സന്ദേശങ്ങള്‍ കോളുകളും, എല്ലായ്പ്പോഴും കോളുകള് റിലേ ചെയ്യുക ഒപ്പം സീൽ ചെയ്ത അയച്ചയാൾ പുതിയ ചാറ്റുകൾക്കായുള്ള ഡിഫോൾട്ട് ടൈമർ നിങ്ങൾ ആരംഭിച്ച എല്ലാ പുതിയ ചാറ്റുകൾക്കും ഒരു സ്ഥിരസ്ഥിതി അപ്രത്യക്ഷമാകുന്ന സന്ദേശം ടൈമർ ക്രമീകരിക്കുക. @@ -4165,9 +4236,9 @@ കോൾ ചെയ്യുക - നിശബ്ദമാക്കുക + മ്യൂട്ട് ചെയ്യുക - നിശബ്ദമാക്കി + മ്യൂട്ട് ചെയ്തു തിരയുക അപ്രത്യക്ഷമാകുന്ന സന്ദേശങ്ങൾ @@ -4175,10 +4246,10 @@ കോൺടാക്റ്റ് അറിയിപ്പുകൾ സുരക്ഷാ നമ്പർ കാണുക - തടയുക - ഗ്രൂപ്പ് തടയുക - തടഞ്ഞത് മാറ്റുക - ഗ്രൂപ്പ് തടഞ്ഞത് മാറ്റുക + ബ്ലോക്ക് ചെയ്യുക + ഗ്രൂപ്പ് ബ്ലോക്ക് ചെയ്യുക + അൺബ്ലോക്ക് ചെയ്യുക + ഗ്രൂപ്പ് അൺബ്ലോക്ക് ചെയ്യുക ഒരു ഗ്രൂപ്പിലേക്ക് ചേർക്കുക എല്ലാം കാണുക അംഗങ്ങളെ ചേർക്കുക @@ -4186,9 +4257,9 @@ അഭ്യർത്ഥനകൾ & ക്ഷണങ്ങൾ ഗ്രൂപ്പിന്റെ ലിങ്ക് ഒരു കോൺടാക്റ്റായി ചേർക്കുക - ശബ്‌ദിപ്പിക്കുക + അൺമ്യൂട്ട് ചെയ്യുക %1$s വരെ സംഭാഷണം മ്യൂട്ട് ചെയ്തു - സംഭാഷണം എന്നെന്നേക്കുമായി മ്യുട് ചെയ്തു + സംഭാഷണം ശാശ്വതമായി മ്യൂട്ട് ചെയ്തു ക്ലിപ്ബോർഡിലേക്ക് ഫോൺ നമ്പർ പകർത്തി. ഫോൺ നമ്പർ Signal-നെ പിന്തുണച്ച് നിങ്ങളുടെ പ്രൊഫൈലിനായി ബാഡ്‌ജുകൾ കരസ്ഥമാക്കൂ. കൂടുതലറിയാൻ ഒരു ബാഡ്‌ജിൽ ടാപ്പ് ചെയ്യുക. @@ -4204,8 +4275,8 @@ ആർക്കാണ് സന്ദേശങ്ങള്‍ അയയ്ക്കാൻ കഴിയുക? - അറിയിപ്പുകൾ നിശബ്ദമാക്കുക - നിശബ്ദമാക്കിയിട്ടില്ല + അറിയിപ്പുകൾ മ്യൂട്ട് ചെയ്യുക + മ്യൂട്ട് ചെയ്തിട്ടില്ല സൂചനകൾ എപ്പോഴും അറിയിക്കുക അറിയിക്കരുത് @@ -4234,7 +4305,7 @@ നീക്കം ചെയ്യൂ - തടയുക + ബ്ലോക്ക് ചെയ്യുക %1$s എന്നയാളെ നീക്കം ചെയ്യണോ? @@ -4242,7 +4313,7 @@ %1$s എന്നയാളെ നീക്കം ചെയ്‌തു - %1$s എന്നയാളെ തടഞ്ഞു + %1$s എന്നയാളെ ബ്ലോക്ക് ചെയ്തു %1$s എന്നയാളെ നീക്കം ചെയ്യാനായില്ല @@ -4330,7 +4401,7 @@ സ്റ്റോറിയിലേക്ക് ചേർക്കുക ഒരു സന്ദേശം ചേര്‍ക്കുക ഒരു മറുപടി ചേർക്കുക - അയക്കുക + ഇനിപ്പറയുന്നയാൾക്ക് അയയ്ക്കുക ഒരിയ്ക്കല്‍ മാത്രം കാണാവുന്ന സന്ദേശം ഒന്നോ അതിലധികമോ ഇനങ്ങൾ വളരെ വലുതായിരുന്നു ഒന്നോ അതിലധികമോ ഇനങ്ങൾ അസാധുവാണ് @@ -4465,7 +4536,7 @@ നിങ്ങളുടെ പേയ്മെന്‍റ് പ്രോസസ് ചെയ്യാൻ കഴിയാത്തതിനാൽ നിങ്ങളുടെ പ്രതിമാസ സംഭാവന റദ്ദായി. നിങ്ങളുടെ ബാഡ്‍ജ് ഇനി പ്രൊഫൈലിൽ ലഭ്യമാകില്ല. നിങ്ങളുടെ ആവർത്തിക്കുന്ന പ്രതിമാസ സംഭാവന റദ്ദാക്കി. %1$s നിങ്ങളുടെ %2$s ബാഡ്‌ജ് ഇനിമുതൽ നിങ്ങളുടെ പ്രൊഫൈലിൽ ദൃശ്യമാകില്ല. - നിങ്ങൾക്ക് Signal ഉപയോഗിക്കുന്നത് തുടരാം, എന്നാൽ ആപ്പിനെ പിന്തുണയ്‌ക്കാനും നിങ്ങളുടെ ബാഡ്‌ജ് വീണ്ടും ആക്‌ടിവേറ്റ് ചെയ്യാനും, ഇപ്പോൾ പുതുക്കുക. + നിങ്ങൾക്ക് Signal ഉപയോഗിക്കുന്നത് തുടരാം, എന്നാൽ ആപ്പിനെ പിന്തുണയ്‌ക്കാനും നിങ്ങളുടെ ബാഡ്‌ജ് വീണ്ടും സജീവമാക്കാനും ഇപ്പോൾ പുതുക്കുക. സബ്‍സ്ക്രിപ്ഷൻ പുതുക്കുക Google Pay-യിലേക്ക് പോകുക @@ -4508,7 +4579,7 @@ ഒരു നെറ്റ്‌വർക്ക് പിശക് കാരണം നിങ്ങളുടെ സംഭാവന അയയ്‌ക്കാൻ കഴിഞ്ഞില്ല. നിങ്ങളുടെ കണക്ഷൻ പരിശോധിച്ച ശേഷം വീണ്ടും ശ്രമിക്കുക. - %1$s എന്നയാൾക്കുള്ള സംഭാവന + %1$s എന്നയാളുടെ പേരിലുള്ള സംഭാവന %1$s നിങ്ങളുടെ പേരിൽ Signal-ലേക്ക് സംഭാവന ചെയ്‌തു @@ -5087,7 +5158,7 @@ സംഭാവന സ്ഥിരീകരിക്കുക - അയക്കുക + ഇനിപ്പറയുന്നയാൾക്ക് അയയ്ക്കുക നേരിട്ടുള്ള സന്ദേശത്തിലൂടെ സ്വീകർത്താവിനെ സംഭാവനയെക്കുറിച്ച് അറിയിക്കും. നിങ്ങളുടെ സ്വന്തം സന്ദേശം ചുവടെ ചേർക്കുക. @@ -5601,5 +5672,15 @@ ഉപയോക്തൃനാമം ഇല്ലാതാക്കുക + + + മ. + + മി. + + സജ്ജമാക്കുക + + സ്ക്രീൻ ലോക്ക് ബാധകമാക്കുന്നതിന് മുമ്പുള്ള ഏറ്റവും കുറഞ്ഞ സമയം 1 മിനിറ്റാണ്. + diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml index b3c2dd9dc7..e7663be24d 100644 --- a/app/src/main/res/values-mr/strings.xml +++ b/app/src/main/res/values-mr/strings.xml @@ -14,13 +14,14 @@ + हो नाही हटवा कृपया थांबा… जतन करा - स्वतःला संदेश + स्वतःला नोंद @@ -138,7 +139,7 @@ सुरू ठेवा - %1$s अवरोधित करायचे आणि सोडायचे ? + %1$s अवरोधित करायचे आणि सोडायचे? %1$s ला अवरोधित करायचे? आपणास यापुढे या गटाकडून संदेश किंवा अद्यतने प्राप्त होणार नाहीत आणि सदस्य आपल्याला या गटामध्ये पुन्हा जोडण्यास सक्षम राहणार नाहीत. गट सदस्य आपल्याला या गटामध्ये पुन्हा जोडू शकणार नाहीत. @@ -150,10 +151,10 @@ अवरोधित लोक आपल्याला कॉल करण्यास किंवा संदेश पाठविण्यास सक्षम असणार नाहीत. अवरोधित केलेले लोक आपल्याला संदेश पाठवू शकणार नाहीत. - Signal अपडेट्स आणि बातम्या मिळवणे अवरोधित करा. + Signal अद्यतने आणि बातम्या मिळवणे अवरोधित करा. Signal अपडेट्स आणि बातम्या मिळवणे पुन्हा सुरू करा. - %1$s ला अनब्लॉक करायचे? + %1$s ला अनावरोधित करायचे? अवरोधित करा अवरोधित करा आणि सोडा स्पॅम म्हणून रिपोर्ट करा आणि अवरोधित करा @@ -318,7 +319,7 @@ Signal संदेश Molly %1$s वर स्विच करूया कृपया एक संपर्क निवडा - अनब्लॉक करा + अनावरोधित करा संलग्नचा आकार आपण पाठवत असलेल्या संदेशाच्या प्रकारासाठीच्या मर्यादेपेक्षा जास्त आहे ऑडिओ रेकॉर्ड करण्यास अक्षम! आपण या गटास संदेश पाठवू शकत नाही कारण आपण यापुढे सदस्‍य नाही. @@ -366,7 +367,7 @@ मिडिया पाठवण्यात त्रुटी - स्पॅम म्हणून रिपोर्ट केले आणि ब्लॉक केले. + स्पॅम म्हणून रिपोर्ट केले आणि अवरोधित केले. SMS संदेशन सध्या अक्षम केले आहे. आपण आपले संदेश आपल्या फोनवरील दुसऱ्या अ‍ॅपवर एक्सपोर्ट करू शकता. @@ -447,7 +448,7 @@ %1$s चालू - विनंती अवरोधित करायची ? + विनंती अवरोधित करायची? %1$s ग्रुप लिंकद्वारे या ग्रुटात सामील होऊ शकणार नाही किंवा त्यात सामील होण्याची विनंती करू शकणार नाही. ते अजूनही गटात स्वहस्ते व्यक्तिचलितपणे जाऊ शकतात. @@ -484,8 +485,8 @@ वाचले - वाचले नाही - वाचले नाही + न वाचलेले + न वाचलेले पिन करा @@ -496,8 +497,8 @@ अनपिन करा - म्यूट करा - म्यूट करा + मूक करा + मूक करा अनम्यूट करा @@ -542,6 +543,15 @@ +%1$d + + आपली डिव्हाइसेस पुन्हा लिंक करा + + आपले डिव्हाइस अनोंदणीकृत होते तेव्हा आपण जोडलेली डिव्हाइसेस अनलिंक केली जातात. कोणतेही डिव्हाइस पुन्हा लिंक करण्यासाठी सेटिंग्ज मध्ये जा. + + सेटिंग्ज उघडा + + नंतर + सदस्य निवडा @@ -935,7 +945,7 @@ उल्लेखा साठी मला सूचित करा - म्यूट केलेल्या चॅटमध्ये आपला उल्लेख केल्यानंतर सूचना प्राप्त करायच्या? + मूक केलेल्या चॅटमध्ये आपला उल्लेख केल्यानंतर अधिसूचना प्राप्त करायच्या? मला नेहमी सूचित करा मला सूचित करू नका @@ -953,6 +963,16 @@ वापरकर्तानाव तयार केले वापरकर्तानाव कॉपी केले + + वापरकर्तानाव हटवू शकत नाही. नंतर पुन्हा प्रयत्न करा. + + वापरकर्ता हटवला + + + + आपल्या वापरकर्ता नावासह काहीतरी चुकीचे झाले आहे, ते यापुढे आपल्या अकाऊंटसह नियुक्त केलेले नाही. आपण आता प्रयत्न करू शकता आणि पुन्हा सेट करू शकता किंवा नवीन निवडू शकता. + + आत्ता निश्चित करा @@ -1156,8 +1176,8 @@ नवीन गट मित्रांना आमंत्रित करा SMS वापरा - स्वरूप - फोटो जोडा + चॅट रंग + प्रोफाईल फोटो जोडा प्रत्युत्तरे @@ -1469,13 +1489,13 @@ सुरू ठेवा हटवा अवरोधित करा - अनब्लॉक करा + अनावरोधित करा %1$s ला आपल्याला संदेश देऊ द्या आणि आपले नाव आणि फोटो त्यांच्यासह शेअर करू द्यायचे? आपण स्वीकार करेपर्यंत आपण त्यांचा संदेश पाहिला आहे हे त्यांना कळणार नाही. - %1$s ला आपल्याला संदेश देऊ द्या आणि आपले नाव आणि फोटो त्यांच्यासह सामायिक करू द्या? आपण त्यांना अनब्लॉक करेपर्यंत आपल्याला कोणतेही संदेश प्राप्त होणार नाहीत. + %1$s ला आपल्याला संदेश पाठवू आणि आपले नाव आणि फोटो त्यांच्यासह सामायिक करू द्यायचे? आपण त्यांना अनावरोधित करेपर्यंत आपल्याला कोणतेही संदेश प्राप्त होणार नाहीत. - %1$s ला आपल्याला संदेश देऊ द्याचे ? जोपर्यंत आपण त्यांना अनब्लॉक करत नाही तोपर्यंत आपल्याला कोणतेही संदेश प्राप्त होणार नाहीत. - %1$s कडून अद्यतने आणि बातम्या मिळवायच्या ? आपण त्यांना अनब्लॉक करेपर्यंत आपल्याला कोणत्याही अपडेट्स मिळणार नाहीत. + %1$s ला आपल्याला संदेश पाठवू द्यायचे? जोपर्यंत आपण त्यांना अनावरोधित करत नाही तोपर्यंत आपल्याला कोणतेही संदेश प्राप्त होणार नाहीत. + %1$s कडून अद्यतने आणि बातम्या मिळवायच्या? आपण त्यांना अनावरोधित करेपर्यंत आपल्याला कोणतीही अद्यतने मिळणार नाहीत. आपले या गटासोबतचे संभाषण आणि आपले नाव आणि फोटो त्याच्या सदस्यांसह सामायिक करणे सुरू ठेवायचे? \@उल्लेख आणि अॅडमिन सारखे नवीन वैशिष्ट्ये सक्रिय करण्यासाठी गट श्रेणीसुधारित करा. ज्या सदस्यांनी त्यांचे नाव किंवा फोटो या गटात शेअर केलेले नाही त्यांना सामील होण्याचे आमंत्रण दिले जाईल. हा लेगसी गट वापरला जाऊ शकत नाही कारण तो खूप मोठा आहे. गटाचा कमाल आकार %1$d आहे. @@ -1483,7 +1503,7 @@ या गटात सामील व्हा आणि आपले नाव आणि फोटो त्याचा सदस्यांसह शेअर करू द्यायचे? आपण स्वीकार करेपर्यंत आपण त्यांचा संदेश पाहिला आहे हे त्यांना कळणार नाही. या गटात सामील व्हा आणि आपले नाव आणि फोटो त्याचा सदस्यांसह शेअर करा? आपण स्वीकार करेपर्यंत आपण त्यांचा संदेश पाहू शकणार नाही. या गटात सामील व्हायचे? आपण स्वीकार करेपर्यंत आपण त्यांचा संदेश पाहिला आहे हे त्यांना कळणार नाही. - हा गट अनब्लॉक करायचा आणि आपले नाव आणि फोटो त्याच्या सदस्यांसह सामायिक करायचे? आपण त्यांना अनब्लॉक करेपर्यंत आपल्याला कोणतेही संदेश प्राप्त होणार नाहीत. + हा गट अनावरोधित करायचा आणि आपले नाव आणि फोटो त्याच्या सदस्यांसह सामायिक करायचे? आपण त्यांना अनावरोधित करेपर्यंत आपल्याला कोणतेही संदेश प्राप्त होणार नाहीत. बघा %1$s चा सदस्य @@ -1584,9 +1604,20 @@ नवीन PIN तयार करा + + SMS कोड पाठवा + + Signal नोंदणी - अ‍ॅन्ड्रॉईड साठी पिन नोंदणी करण्यास मदतीची गरज आहे + + आपला पिन %1$d+ अंकांचा तुम्ही तयार केलेला कोड आहे जो अंकीय किंवा अल्फान्यूमेरिक असू शकतो.\n\nआपणाला आपला पिन लक्षात नसल्यास, आपण नवीन तयार करू शकता. + + आपणाला आपला पिन लक्षात नसल्यास, आपण नवीन तयार करू शकता. + + आपले पिन अंदाज संपले आहेत, पण तरीही नवीन पिन तयार करून आपण आपले Signal अकाऊंट अ‍ॅक्सेस करू शकता. + चेतावणी - आपण PIN अक्षम केल्यास, आपण व्यक्तिचलितरीत्या बॅक अप आणि पुनर्स्थापना केल्याशिवाय आपण Signal वर पुन्हा नोंदणी करताना आपण सर्व डेटा गमवाल. PIN अक्षम केलेले असताना आपण नोंदणी लॉक चालू करू शकत नाही. + आपण PIN अक्षम केल्यास, आपण व्यक्तिचलितरीत्या बॅक अप आणि पुनर्संचयन केल्याशिवाय आपण Signal वर पुन्हा नोंदणी करताना आपण सर्व डेटा गमवाल. PIN अक्षम केलेले असताना आपण नोंदणी लॉक चालू करू शकत नाही. PIN अक्षम करा @@ -1617,7 +1648,7 @@ अवरोधित करा - अनब्लॉक करा + अनावरोधित करा @@ -1713,7 +1744,7 @@ अनम्यूट करा - म्यूट करा + मूक करा रिंग @@ -1726,12 +1757,12 @@ - %1$s ब्लॉक केलेले आहे + %1$s अवरोधित केलेले आहे अधिक माहिती आपल्याला त्यांचे ऑडिओ किंवा व्हिडिओ प्राप्त होणार नाहीत आणि त्यांना तुमचे प्राप्त होणार नाहीत. %1$s कडून ऑडिओ आणि व्हिडिओ प्राप्त करू शकत नाही %1$s कडून ऑडिओ आणि व्हिडिओ प्राप्त करू शकत नाही - त्यांनी आपला सुरक्षितता नंबर बदल सत्यापित न केल्यामुळे, त्यांच्या डिव्हाईसमध्ये काहीतरी समस्या असल्यामुळे, किंवा त्यांनी आपल्याला ब्लॉक केल्यामुळे असे असू शकते. + त्यांनी आपला सुरक्षितता नंबर बदल सत्यापित न केल्यामुळे, त्यांच्या डिव्हाईसमध्ये काहीतरी समस्या असल्यामुळे, किंवा त्यांनी आपल्याला अवरोधित केल्यामुळे असे असू शकते. स्क्रीन शेअर पहाण्यासाठी स्वाइप करा @@ -1768,11 +1799,18 @@ Signal ला तुम्हाला तुमच्या मित्रांशी कनेक्ट करण्यासाठी आणि संदेश पाठवण्यासाठी संपर्क आणि मीडिया परवानग्या आवश्यक आहेत. तुमचे संपर्क Signal चे खाजगी संपर्क शोध वापरून अपलोड केले जातात, याचा अर्थ ते एंड-टू-एंड एनक्रिप्ट केलेले आहेत आणि Signal सेवेला ते कधीही दिसत नाहीत. Signal ला तुम्हाला तुमच्या मित्रांशी कनेक्ट करण्यासाठी संपर्क परवानगी आवश्यक आहेत. तुमचे संपर्क Signal चे खाजगी संपर्क शोध वापरून अपलोड केले जातात, याचा अर्थ ते एंड-टू-एंड एनक्रिप्ट केलेले आहेत आणि Signal सेवेला ते कधीही दिसत नाहीत. तुम्ही हा नंबर नोंदवण्यासाठी खूप प्रयत्न केले आहेत. कृपया नंतर पुन्हा प्रयत्न करा. + + आपण या क्रमांकांची नोंदणी करण्यास असंख्य प्रयत्न केले आहेत. कृपया %1$s मध्ये पुन्हा प्रयत्न करा. सेवेसोबत कनेक्ट करण्यात अक्षम. कृपया नेटवर्क कनेक्शन तपासा आणि पुन्हा प्रयत्न करा. अ-मानक क्रमांक स्वरूप तुम्ही प्रविष्‍ट केलेला क्रमांक (%1$s) हा अ-मानक स्वरूपाचा असल्याचे दिसते. \n\nतुम्हाला %2$s म्हणायचे होते का? Molly Android - फोन नंबर स्वरूप + कॉलची विनंती केली + + SMS ची विनंती करण्यात आली + + सत्यापन कोडची विनंती करण्यात आली आपण आता डीबग लॉग प्रविष्ट करण्याच्या %1$d पायरी दूर आहात. आपण आता डीबग लॉग प्रविष्ट करण्याच्या %1$d पायऱ्या दूर आहात. @@ -1792,6 +1830,16 @@ कॉल करा सत्यापन कोड कोड पुन्हा पाठवा + + नोंदणी करण्यामध्ये समस्या आली? + + • कृपया आपल्या फोनला आपले SMS किंवा कॉल प्राप्त करण्यास सेल्युलर सिग्नल असल्याची खात्री करा\n • तुम्ही या क्रमांकावर फोन कॉल प्राप्त करु शकता याची पुष्टी करा\n • आपण आपला फोन नंबर बरोबर प्रविष्ट केला आहे याची खात्री करा. + + अधिक माहितीसाठी, कृपया या निराकरण टप्प्यांची अंमलबजावणी करा किंवा सपोर्टशी संपर्क साधा + + हे निराकरणाचे टप्पे आहेत + + सपोर्टशी संपर्क साधा नोंदणी लॉक चालू करायचे? @@ -1953,11 +2001,15 @@ आपल्या स्टोरी वर %1$s प्रतिक्रिया दिली - त्यांच्या स्टोरीवर %1$s नी प्रतिक्रिया दिली + त्यांच्या स्टोरी वर %1$s नी प्रतिक्रिया दिली पेमेंट शेड्यूल केलेला संदेश + + आपला संदेश इतिहास एकत्र करण्यात आला आहे + + %1$s हा %2$s शी संबंधित आहे Molly अद्यतन @@ -2030,7 +2082,7 @@ अस्तित्वात नसलेल्या सत्रा करिता MMS संदेश एन्क्रिप्टेड आहे - म्यूट सूचना + अधिसूचना मूक करा आयात प्रगतीमध्ये @@ -2087,14 +2139,16 @@ असुरक्षित SMS %1$s %2$s संपर्क - %1$s प्रतिसाद दिला यास: \"%2$s\". - आपल्या व्हिडिओला %1$s प्रतिसाद दिला. - आपल्या चित्राला %1$s प्रतिसाद दिला. - %1$s प्रतिक्रिया तुमच्या GIF ला दिली. - आपल्या फाईलला %1$s प्रतिसाद दिला. - आपल्या ऑडिओला %1$s प्रतिसाद दिला. - आपल्या एकदा-बघा मिडियाला %1$s प्रतिसाद दिला. - आपल्या स्टिकरला %1$s प्रतिसाद दिला. + \"%2$s\" :ला %1$s प्रतिक्रिया दिली. + आपल्या व्हिडिओवर %1$s प्रतिक्रिया दिली. + आपल्या चित्रावर %1$s प्रतिक्रिया दिली. + आपल्या GIF वर %1$s प्रतिक्रिया दिली. + आपल्या फाईलवर %1$s प्रतिक्रिया दिली. + आपल्या ऑडिओवर %1$s प्रतिक्रिया दिली. + आपल्या एकदा-बघा मिडियावर %1$s प्रतिक्रिया दिली. + + आपल्या पेमेंट्स वर %1$s प्रतिक्रिया दिली. + आपल्या स्टिकरवर %1$s प्रतिक्रिया दिली. हा संदेश हटवला गेला. संपर्क Signal मध्ये सामील झाला सूचना बंद करायच्या? आपण त्यांना पुन्हा Signal > सेटिंग > सूचना मधून सक्षम करू शकता. @@ -2487,8 +2541,8 @@ डिलिव्हरी समस्या - %1$s पासून संदेश, स्टिकर, प्रतिक्रिया किंवा वाचल्याची पावती आपल्यापर्यंत पोहोचवली जाऊ शकली नाही. त्यांनी आपल्याला थेट किंवा गटात पाठवण्याचा प्रयत्न केला असेल. - %1$s पासून संदेश, स्टिकर, प्रतिक्रिया किंवा वाचल्याची पावती आपल्यापर्यंत पोहोचवली जाऊ शकली नाही. + %1$s कडून आलेल्या संदेश, स्टिकर, प्रतिक्रिया किंवा वाचल्याची पावती आपल्यापर्यंत पोहोचवली जाऊ शकली नाही. त्यांनी आपल्याला थेट किंवा गटात पाठवण्याचा प्रयत्न केला असेल. + आपल्यापर्यंत %1$s कडून आलेल्या संदेश, स्टिकर, प्रतिक्रिया किंवा वाचल्याची पावती पोहोचवली जाऊ शकली नाही. पहिले नाव (आवश्यक) @@ -2635,10 +2689,10 @@ पूर्वनिर्धारित वापरा सानुकूल वापरा - 1 तासासाठी म्यूट करा - 8 तासांसाठी म्यूट करा - 1 दिवसासाठी म्यूट करा - 7 दिवसांसाठी म्यूट करा + 1 तासासाठी मूक करा + 8 तासांसाठी मूक करा + 1 दिवसासाठी मूक करा + 7 दिवसांसाठी मूक करा नेहमी पूर्वनिर्धारित सेटिंग @@ -2675,9 +2729,9 @@ अॅड्रेस बुक फोटो वापरा उपलब्ध असल्यास आपल्या अॅड्रेस बुकमधून संपर्क फोटो दाखवा - म्यूट केलेले आर्काईव्ह केलेले चॅटस् ठेवा + मूक केलेले संग्रहित करून ठेवा - नवीन संदेश येईल तेव्हा आर्काईव्ह केलेले म्यूट केलेले चॅट्स हे आर्काईव्ह केलेलेच राहतील. + नवीन संदेश येतील तेव्हा संग्रहित केलेले मूक चॅट्स संग्रहित केलेलेच राहतील. लिंक पुनरावलोकन उत्पन्न करा तुम्ही पाठवलेल्या संदेशांसाठी थेट कोणत्याही वेबसाइटवरून लिंक पूर्वावलोकने प्राप्त करू शकता. पासफ्रेझ बदला @@ -3131,7 +3185,7 @@ अनम्यूट करा - सूचना म्यूट करा + अधिसूचना मूक करा गट सेटिंग @@ -3295,6 +3349,8 @@ आपला PIN प्रविष्ट करा आपल्या खात्यासाठी आपण तयार केलेला PIN प्रविष्ट करा. हे आपल्या SMS सत्यापन कोडपेक्षा वेगळे आहे. + + आपल्या अकाऊंटसाठी आपण तयार केलेला पिन प्रविष्ट करा. अल्फान्यूमेरिक PIN प्रविष्ट करा न्यूमेरिक PIN प्रविष्ट करा चुकीचा PIN.पुन्हा प्रयत्न करा. @@ -3398,7 +3454,10 @@ आपल्या बॅकअपमध्ये एक अतिशय मोठी फाईल आहे जिचा बॅकअप घेतला जाऊ शकत नाही. कृपया ती हटवा किंवा नवीन बॅकअप तयार करा. बॅकअप व्यवस्थापित करण्यासाठी टॅप करा. चुकीचा नंबर? + (%1$02d:%2$02d) मला कॉल करा + + (%1$02d:%2$02d) कोड पुन्हा पाठवा Signal समर्थनाशी संपर्क साधा Signal नोंदणी - Android करिता सत्यापन कोड चुकीचा कोड @@ -3406,6 +3465,18 @@ अज्ञात माझा फोन नंबर बघा फोन नंबरवरून मला शोधा + + फोन नंबर + + आपला फोन नंबर कोण पाहू शकते आणि त्याच्यासह Molly वर आपणाला कोण संपर्क करू शकते ते निवडा. + + माझा नंबर कोण पाहू शकते + + Molly वर कुणीही आपला फोन नंबर पाहणार नाही + + मला फोन नंबरद्वारे कोण शोधू शकते + + आपण संदेश पाठवलेल्या व्यक्ती आणि गटांना आपला फोन नंबर दृश्यमान असेल. ज्या व्यक्तींकडे आपला नंबर त्यांच्या फोन संपर्कामध्ये आहे ते देखील Molly वर तो पाहू शकतील. प्रत्येकजण माझे संपर्क कोणीही नाही @@ -3563,7 +3634,7 @@ अवरोधित करा - अनब्लॉक करा + अनावरोधित करा संपर्कात जोडा संपर्क उघडण्यास सक्षम अॅप सापडत नाही. @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" ब्लॉक केले गेले आहे. - \"%1$s\" ब्लॉक करण्यात अयशस्वी - \"%1$s\" अनब्लॉक केले गेले आहे. + \"%1$s\" अवरोधित केले गेले आहे. + \"%1$s\" अवरोधित करण्यात अयशस्वी + \"%1$s\" अनावरोधित केले गेले आहे. सदस्यांचे पुनरावलोकन करा @@ -3667,11 +3738,11 @@ कमकुवत Wi-Fi . सेल्युलरवर स्वीच केले. - आपले खाते हटवल्याने खालील गोष्टी होतील: + आपले अकाऊंट हटवल्याने खालील गोष्टी होतील: आपला फोन नंबर प्रविष्ट करा अकाऊंट हटवा आपली अकाऊंट माहिती आणि प्रोफाईल फोटो हटवला जाईल - आपले सर्व संदेश हटवले जातील + आपले सर्व संदेश हटवा आपल्या पेमेंट अकाऊंटमध्ये %1$s हटवा कुठलेही देश कोड निर्दिष्ट नाही कुठलेही नंबर निर्दिष्ट नाही @@ -3784,12 +3855,12 @@ वॉलेट निष्क्रिय करा आपला बॅलंस - पेमेंट निष्क्रिय करण्यापूर्वी आपण आपले फंड इतर वॉलेट पत्त्यावर स्थानांतरित करण्याची शिफारस केलेली आहे. आपण आपले फंड स्थानांतरित नाही करण्याचे निवडल्यास, आपण पेमेंट पुन्हा सक्रिय केल्यास ते आपल्या वॉलेट मध्ये Molly ला लिंक केलेले राहतील. + पेमेंट्स निष्क्रिय करण्यापूर्वी आपण आपले फंड इतर वॉलेट पत्त्यावर ट्रान्सफर करण्याची शिफारस केलेली आहे. आपण आपले फंड ट्रान्सफर न करण्याचे निवडल्यास, आपण पेमेंट्स पुन्हा सक्रिय केल्यास ते आपल्या वॉलेट मध्ये Molly ला लिंक केलेले राहतील. उर्वरित बॅलंस स्थानांतरित करा स्थानांतरित न करता निष्क्रिय करा निष्क्रिय करा स्थानांतरित न करता निष्क्रिय करायचा? - आपण पेमेंट पुन्हा सक्रिय करण्याचे निवडल्यास आपला बॅलंस आपल्या वॉलेट मध्ये Molly ला लिंक केलेला राहील. + आपण पेमेंट्स पुन्हा सक्रिय करण्याचे निवडल्यास आपली शिल्लक आपल्या वॉलेट मध्ये Molly ला लिंक केलेली राहील. वॉलेट निष्क्रिय करण्यात त्रुटी. @@ -4008,7 +4079,7 @@ संदेशन हरवणारे संदेश अॅप सुरक्षा - अलीकडील यादीमध्ये आणि अॅपच्या आत स्क्रीनशॉट अवरोधित करा + अलीकडील यादीमध्ये आणि अ‍ॅपच्या आत स्क्रीनशॉट अवरोधित करा Signal संदेश आणि कॉल, कॉल नेहमी रिले करा आणि सील केलेला प्रेषक नवीन चॅटसाठी पूर्वनिर्धारित टायमर आपल्याद्वारा चालू केलेल्या सर्व नवीन चॅटसाठी पूर्वनिर्धारित हरवणारे संदेश टायमर सेट करा. @@ -4165,9 +4236,9 @@ कॉल करा - म्यूट करा + मूक करा - म्यूट केलेले + मूक केलेले शोध हरवणारे संदेश @@ -4177,8 +4248,8 @@ सुरक्षितता नंबर बघा अवरोधित करा गट अवरोधित करा - अनब्लॉक करा - गट अनवरोधित करा + अनावरोधित करा + गट अनावरोधित करा गटात जोडा सर्व पहा सदस्य जोडा @@ -4187,8 +4258,8 @@ गट लिंक संपर्क म्हणून जोडा अनम्यूट करा - %1$s संभाषण म्यूट केले गेले - संभाषण कायमचे म्यूट केले गेले + %1$s संभाषण मूक केले गेले + संभाषण कायमचे मूक केले गेले फोन नंबर क्लिपबोर्डवर कॉपी केले गेले. फोन नंबर Signal ला समर्थन करून आपल्या प्रोफाइलसाठी बॅज मिळवा. अधिक जाणून घेण्यासाठी बॅज वर टॅप करा. @@ -4204,7 +4275,7 @@ संदेश कोण पाठवू शकतो? - सूचना म्यूट करा + अधिसूचना मूक करा म्यूट केलेले नाही उल्लेख नेहमी सूचित करा @@ -4465,7 +4536,7 @@ आम्ही आपल्या पेमेंटवर प्रक्रिया करू शकत नसल्याने आपली आवर्ती मासिक देणगी रद्द करण्यात आली. यापुढे आपल्या प्रोफाइलवर आपला बॅज दिसणार नाही. आपली आवर्ती मासिक देणगी रद्द करण्यात आली. यापुढे आपल्या प्रोफाइलवर %1$s आपला %2$s बॅज दृश्यमान होणार नाही. - आपण Signal वापरणे सुरू ठेवू शकता परंतु अ‍ॅपला समर्थन देण्यासाठी आणि आपला बॅज पुन्हा सक्रिय करण्यासाठी, आता नूतनीकरण करा. + आपण Signal वापरणे सुरू ठेवू शकता परंतु अ‍ॅपला सपोर्ट करण्यासाठी आणि आपला बॅज पुन्हा सक्रिय करण्यासाठी, आता नूतनीकरण करा. सदस्यत्वाचे नूतनीकरण करा Google Pay वर जा @@ -4508,7 +4579,7 @@ नेटवर्क त्रुटीमुळे आपली देणगी पाठवली जाऊ शकत नाही. आपले कनेक्शन तपासा आणि पुन्हा प्रयत्न करा. - %1$s ला देणगी + %1$s च्या वतीने देणगी %1$s ने आपल्यावतीने Signal ला देणगी दिली @@ -4905,11 +4976,11 @@ आपली स्टोरी कोण पाहू शकते ते निवडा. बदलांचा आपण आधीच पाठवलेल्या स्टोरीजवर परिणाम होणार नाही. - प्रत्त्युत्तरे & प्रतिक्रिया + प्रत्त्युत्तरे व प्रतिक्रिया - प्रत्त्युत्तरे & प्रतिक्रिया अनुमती द्या + प्रत्त्युत्तरे व प्रतिक्रिया अनुमती द्या - जे लोक तुमची स्टोरी पाहू शकतात त्यांना प्रतिक्रिया द्या आणि उत्तर द्या + जे लोक आपली स्टोरी पाहू शकतात त्यांना प्रतिक्रिया आणि उत्तर देऊ द्या Signal कनेक्शन्स @@ -5601,5 +5672,15 @@ वापरकर्ता नाव हटवा + + + ता + + मि + + सेट करा + + स्क्रिन लॉक लागू होण्याच्या आधीचा कमीत कमी वेळ 1 मिनिट आहे. + diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 43a92bffd4..590afb6295 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -14,6 +14,7 @@ + Ya Tidak @@ -96,11 +97,11 @@ Memeriksa mesej… - Pengguna-pengguna yang diblock - Tambah pengguna untuk diblock - Pengguna disekat tidak akan dapat memanggil atau menghantar mesej kepada anda. - Tiada pengguna yang diblock - Block pengguna? + Pengguna yang disekat + Tambah pengguna yang disekat + Pengguna yang disekat tidak akan dapat memanggil atau menghantar mesej kepada anda. + Tiada pengguna yang disekat + Sekat pengguna? \"%1$s\" tidak akan boleh untuk memanggil anda atau menghantar anda mesej. Sekat @@ -138,8 +139,8 @@ Teruskan - Block dan tinggalkan %1$s? - Block %1$s? + Sekat dan keluar dari %1$s? + Sekat %1$s? Anda tidak lagi akan menerima mesej atau update daripada group ini, dan ahli-ahli group tidak boleh untuk menambah anda kedalam group ini semula. Ahli-ahli group tidak akan boleh untuk menambah anda ke dalam group ini semula. Ahli-ahli kumpulan akan dapat menambah anda kepada kumpulan ini lagi. @@ -147,15 +148,15 @@ Anda akan dapat menghantar mesej dan membuat panggilan antara satu sama lain dan nama serta gambar anda akan dikongsi bersama mereka. Anda boleh mesej satu sama lain. - Orang-orang diblock tidak akan boleh untuk memanggil anda atau menghantar anda mesej. + Orang yang disekat tidak akan dapat menghubungi anda atau menghantar mesej kepada anda. Orang yang disekat tidak dapat menghantar mesej kepada anda. Sekat daripada menerima kemas kini dan berita Signal. Teruskan menerima kemas kini dan berita Signal. - Unblock %1$s? + Nyahsekat %1$s? Sekat - Block dan Tinggalkan + Sekat dan Keluar Laporkan spam dan sekat @@ -522,6 +523,15 @@ +%1$d + + Paut semula peranti anda + + Peranti yang anda tambahkan tidak dapat dipautkan apabila peranti anda tidak didaftarkan. Pergi ke Tetapan untuk memaut semula mana-mana peranti. + + Buka tetapan + + Nanti + Pilih ahli @@ -897,7 +907,7 @@ Maklumkan saya untuk Sebutan - Terima pemberitahuan apabila anda menyebut dalam sembang yang dibisukan? + Terima pemberitahuan apabila anda disebut dalam sembang yang diredam? Sentiasa memaklumkan saya Jangan maklumkan saya @@ -915,6 +925,16 @@ Nama pengguna dicipta Nama pengguna disalin + + Tidak dapat memadamkan nama pengguna. Cuba lagi kemudian. + + Nama pengguna dipadamkan + + + + Kesilapan telah berlaku dengan nama pengguna anda, ia tidak lagi diberikan kepada akaun anda. Anda boleh mencuba dan menetapkannya semula atau memilih yang baharu. + + Baiki sekarang @@ -1108,8 +1128,8 @@ Kumpulan baru Jemput rakan Gunakan SMS - Penampilan - Tambah foto + Warna sembang + Tambah foto profil Balasan @@ -1414,10 +1434,10 @@ Nyahsekat Membenarkan %1$s menghantar mesej kepada anda dan kongsikan nama dan foto anda dengan mereka? Mereka tidak akan tahu bahawa anda telah melihat mesej mereka sehingga anda menerimanya. - Membenarkan %1$s menghantar mesej kepada anda dan kongsikan nama dan foto anda dengan mereka? Anda tidak akan menerima sebarang mesej sehingga anda menyahsekatnya. + Benarkan %1$s menghantar mesej kepada anda dan kongsikan nama dan foto anda dengan mereka? Anda tidak akan menerima sebarang mesej sehingga anda menyahsekatnya. - Benarkan %1$s mesej anda? Anda tidak akan menerima sebarang mesej sehingga anda nyahsekat mereka. - Dapatkan kemas kini dan berita daripada %1$s? Anda tidak akan menerima sebarang kemas kini sehingga anda nyahsekat mereka. + Benarkan %1$s mesej anda? Anda tidak akan menerima sebarang mesej sehingga anda menyahsekat mereka. + Dapatkan kemas kini dan berita daripada %1$s? Anda tidak akan menerima sebarang kemas kini sehingga anda menyahsekat mereka. Teruskan perbualan anda dengan kumpulan ini dan kongsikan nama dan foto anda dengan ahli-ahlinya? Naik tarafkan kumpulan ini untuk mengaktifkan ciri-ciri baharu seperti @sebutan dan pentadbir. Ahli-ahli yang belum kongsikan nama atau foto mereka dalam kumpulan ini akan dijemput untuk menyertainya. Kumpulan Legasi ini tidak lagi dapat digunakan kerana terlalu besar. Saiz kumpulan maksimum ialah %1$d. @@ -1520,9 +1540,20 @@ Cipta PIN baharu + + Hantar kod SMS + + Pendaftaran Signal - Perlukan Bantuan pendaftaran semula PIN untuk Android + + PIN anda ialah %1$d+ kod digit yang anda buat sama ada angka atau abjad angka.\n\nJika anda tidak ingat PIN anda, anda boleh mencipta yang baharu. + + Jika anda tidak ingat PIN, anda boleh mencipta yang baharu. + + Anda telah kehabisan tekaan PIN, tetapi anda masih boleh mengakses akaun Signal anda dengan mencipta PIN baharu. + Amaran - Sekiranya anda menyahdayakan PIN, anda akan kehilangan semua data apabila anda mendaftar semula Signal melainkan anda membuat sandaran dan pemulihan secara manual. Anda tidak dapat menghidupkan Kunci Pendaftaran semasa PIN dinyahdayakan. + Sekiranya anda menyahdayakan PIN, anda akan kehilangan semua data apabila anda mendaftar semula Signal, melainkan anda membuat sandaran dan pemulihan secara manual. Anda tidak dapat menghidupkan Kunci Pendaftaran semasa PIN dinyahdayakan. Nyahdayakan PIN @@ -1644,7 +1675,7 @@ Nyahredam - Bisukan + Redamkan Panggil @@ -1661,7 +1692,7 @@ Anda tidak akan menerima audio atau video mereka dan mereka tidak akan menerima audio atau video anda. Tidak dapat menerima audio & video daripada %1$s Tidak dapat menerima audio dan video daripada %1$s - Hal ini mungkin disebabkan mereka belum mengesahkan perubahan nombor keselamatan anda, ada masalah dengan peranti mereka atau mereka telah menyekat anda. + Ini mungkin kerana mereka belum mengesahkan perubahan nombor keselamatan anda, terdapat masalah dengan peranti mereka atau mereka telah menyekat anda. Leret untuk melihat kongsi skrin @@ -1698,11 +1729,18 @@ Signal memerlukan kebenaran dari kenalan dan pihak media untuk membantu anda berhubung dengan rakan dan menghantar mesej. Kenalan anda dimuat naik menggunakan penemuan kenalan peribadi Signal, yang bermaksud mereka dienkripsikan dari hujung ke hujung dan tidak dipaparkan kepada perkhidmatan Signal. Signal memerlukan kebenaran perhubungan untuk membantu anda berhubung dengan kenalan anda. Maklumat kenalan anda akan dimuat naik menggunakan ciri penerokaan kenalan peribadi Signal, yang bermakna ia dienkripsi hujung ke hujung dan tidak ditunjukkan kepada perkhidmatan Signal. Anda telah membuat terlalu banyak percubaan untuk mendaftar nombor ini. Sila cuba lagi kemudian. + + Anda telah membuat terlalu banyak percubaan untuk mendaftar nombor ini. Sila cuba lagi kemudian dalam masa %1$s. Perkhidmatan tidak dapat disambungkan. Sila semak sambungan rangkaian dan cuba lagi. Format nombor bukan standard Nombor yang anda telah masukkan (%1$s) adalah dalam format bukan standard.\n\nAdakah anda maksudkan %2$s? Molly Android - Format Nombor Telefon + Panggilan diminta + + SMS diminta + + Kod pengesahan diminta Anda kini %1$d langkah daripada menghantar log nyahpepijat. @@ -1721,6 +1759,16 @@ Panggilan Kod pengesahan Hantar Semula Kod + + Menghadapi masalah mendaftar? + + • Pastikan telefon anda mempunyai isyarat selular untuk menerima SMS atau panggilan\n • Sahkan yang anda boleh menerima panggilan telefon ke nombor\n • Semak sama ada anda telah memasukkan nombor telefon anda dengan betul. + + Untuk mendapatkan maklumat lanjut, sila ikuti langkah penyelesaian masalah ini atau Hubungi Sokongan + + langkah penyelesaian masalah ini + + Hubungi Sokongan Hidupkan Kunci Pendaftaran? @@ -1880,13 +1928,17 @@ Anda telah menebus lencana - Menghantar reaksi %1$s kepada cerita anda + Memberi reaksi %1$s kepada cerita anda - Menghantar reaksi %1$s kepada cerita mereka + Memberi reaksi %1$s kepada cerita mereka Pembayaran Mesej yang dijadualkan + + Sejarah mesej anda telah digabungkan + + %1$s milik %2$s Kemas kini Molly @@ -1958,7 +2010,7 @@ Mesej MMS yang disulitkan untuk sesi yang tidak ada - Bisukan pemberitahuan + Redamkan pemberitahuan Import sedang berjalan @@ -2015,14 +2067,16 @@ SMS yang tidak selamat %1$s%2$s Kenalan - Memberikan reaksi %1$s kepada: \"%2$s\". - Memberikan reaksi %1$s kepada video anda. - Memberikan reaksi %1$s kepada imej anda. - Menunjukkan reaksi %1$s terhadap GIF anda. - Memberikan reaksi %1$s kepada fail anda. - Memberikan reaksi %1$s kepada audio anda. - Memberikan reaksi %1$s kepada media lihat sekali anda. - Memberikan reaksi %1$s kepada pelekat anda. + Memberi reaksi %1$s kepada: \"%2$s\". + Memberi reaksi %1$s kepada video anda. + Memberi reaksi %1$s kepada imej anda. + Memberi reaksi %1$s terhadap GIF anda. + Memberi reaksi %1$s kepada fail anda. + Memberi reaksi %1$s kepada audio anda. + Memberi reaksi %1$s kepada media lihat sekali anda. + + Beri reaksi %1$s kepada pembayaran anda. + Memberi reaksi %1$s kepada pelekat anda. Mesej ini telah dipadam. Matikan pemberitahuan kenalan telah menyertai Signal? Anda boleh mendayakannya lagi dalam Signal > Tetapan > Pemberitahuan. @@ -2404,8 +2458,8 @@ Isu penghantaran - Mesej, pelekat, reaksi, atau resit baca tidak dapat disampaikan kepada anda daripada %1$s. Mereka mungkin telah cuba menghantarnya kepada anda secara langsung, atau dalam kumpulan. - Mesej, pelekat, reaksi, atau resit baca tidak dapat disampaikan kepada anda daripada %1$s. + Mesej, pelekat, reaksi, atau resit baca tidak dapat disampaikan kepada anda daripada %1$s. Mereka mungkin telah cuba menghantarnya kepada anda secara terus, atau dalam kumpulan. + Mesej, pelekat, reaksi, atau resit baca tidak dapat dihantar kepada anda daripada %1$s. Nama pertama (diperlukan) @@ -2552,10 +2606,10 @@ Gunakan lalai Gunakan tersuai - Bisukan untuk 1 jam - Redam selama 8 jam - Bisukan untuk 1 hari - Bisukan untuk 7 hari + Redamkan selama 1 jam + Redamkan selama 8 jam + Redamkan selama 1 hari + Redamkan selama 7 hari Sentiasa Tetapan lalai @@ -2591,9 +2645,9 @@ Gunakan foto buku alamat Paparkan foto kenalan daripada buku alamat anda sekiranya tersedia - Kekalkan Sembang yang Disenyapkan Diarkibkan + Kekalkan Sembang yang Diredam Diarkibkan - Sembang yang disenyapkan yang telah diarkibkan akan kekal diarkibkan apabila mesej baru masuk. + Sembang yang diredam yang telah diarkibkan akan kekal diarkibkan apabila mesej baru masuk. Hasilkan pratonton pautan Dapatkan pratonton pautan terus daripada laman web untuk mesej yang anda hantar. Tukar frasa laluan @@ -3043,10 +3097,10 @@ - Nyahbisukan + Nyahredam - Bisukan pemberitahuan + Redamkan pemberitahuan Group settings @@ -3207,6 +3261,8 @@ Masukkan PIN anda Masukkan PIN yang anda buat untuk akaun anda. Ini berbeza dengan kod pengesahan SMS anda. + + Masukkan PIN yang anda buat untuk akaun anda. Masukkan PIN abjad angka Masukkan PIN angka PIN tidak betul. Cuba lagi. @@ -3305,7 +3361,10 @@ Sandaran anda mengandungi fail yang saiznya terlalu besar dan tidak boleh disandar. Sila padamkannya dan cipta sandaran baru. Ketik untuk menguruskan sandaran. Nombor salah? + Hubungi saya (%1$02d:%2$02d) + + Hantar semula Kod (%1$02d:%2$02d) Hubungi Sokongan Signal Pendaftaran Signal - Kod Pengesahan untuk Android Kod salah @@ -3313,6 +3372,18 @@ Tidak diketahui Lihat nombor telefon saya Cari saya dengan nombor telefon + + Nombor telefon + + Pilih siapa yang boleh melihat nombor telefon anda dan siapa yang boleh menghubungi nombor anda di Molly. + + Siapa boleh lihat nombor saya + + Tiada sesiapa yang akan nampak nombor telefon anda di Molly + + Siapa boleh cari saya dengan nombor + + Nombor telefon anda akan dapat dilihat oleh orang dan kumpulan yang bermesej dengan anda. Orang yang mempunyai nombor anda dalam kenalan telefon mereka juga dapat melihatnya di Molly. Semua orang Kenalan saya Tiada sesiapa @@ -3523,7 +3594,7 @@ %1$s/%2$s \"%1$s\" telah disekat. - Gagal menyekat \"%1$s\" + Gagal untuk menyekat \"%1$s\" \"%1$s\" telah dinyahsekat. @@ -3688,12 +3759,12 @@ Nyahaktifkan Dompet Baki anda - Sebaiknya pindahkan dana anda ke alamat dompet lain sebelum menyahaktifkan pembayaran. Sekiranya anda memilih untuk tidak memindahkan dana anda sekarang, dana akan kekal dalam dompet anda yang dipautkan dengan Molly jika anda mengaktifkan semula pembayaran. + Anda disyorkan agar memindahkan dana anda ke alamat dompet lain sebelum menyahaktifkan pembayaran. Jika anda memilih untuk tidak memindahkan dana anda sekarang, ia akan kekal dalam dompet anda yang dipautkan ke Molly jika anda mengaktifkan semula pembayaran. Pindah baki yang tinggal Nyahaktifkan tanpa pemindahan Nyahaktifkan Nyahaktifkan tanpa pemindahan? - Baki anda akan kekal dalam dompet anda yang dipautkan dengan Molly jika anda memilih untuk mengaktifkan semula pembayaran. + Baki anda akan kekal dalam dompet anda yang dipautkan ke Molly jika anda memilih untuk mengaktifkan semula pembayaran. Ralat semasa menyahaktifkan dompet. @@ -4066,9 +4137,9 @@ Panggilan - Bisukan + Redamkan - Diredamkan + Diredam Cari Mesej hilang @@ -4087,7 +4158,7 @@ Permintaan & jemputan Pautan kumpulan Tambah sebagai kenalan - Nyahbisukan + Nyahredam Perbualan diredam sehingga %1$s Perbualan diredam selama-lamanya Nombor telefon telah disalin ke papan keratan. @@ -4105,8 +4176,8 @@ Siapa yang boleh menghantar mesej? - Bisukan pemberitahuan - Tidak dibisukan + Redamkan pemberitahuan + Tidak diredam Sebutan Sentiasa maklumkan Jangan maklumkan @@ -4143,7 +4214,7 @@ %1$s telah dialih keluar - %1$s telah disekat. + %1$s telah disekat Tidak dapat mengalih keluar %1$s @@ -4405,7 +4476,7 @@ Derma anda tidak boleh dihantar kerana ralat rangkaian. Periksa sambungan anda dan cuba lagi. - Derma kepada %1$s + Derma bagi pihak %1$s %1$s menderma kepada Signal bagi pihak anda @@ -4747,13 +4818,13 @@ Anda tidak boleh membalas cerita ini kerana anda bukan lagi ahli kumpulan ini. - Telah menghantar reaksi kepada cerita + Memberi reaksi kepada cerita Tontonan Balasan - Telah menghantar reaksi kepada cerita ini + Memberi reaksi kepada cerita ini Balas secara peribadi kepada %1$s @@ -4800,7 +4871,7 @@ Benarkan balasan & reaksi - Benarkan orang yang boleh menonton cerita anda menghantar reaksi dan membalas + Benarkan orang yang boleh menonton cerita anda memberi reaksi dan membalas Hubungan Signal @@ -4947,11 +5018,11 @@ - Anda menghantar reaksi kepada cerita %1$s + Anda memberi reaksi kepada cerita %1$s - Menghantar reaksi kepada cerita anda + Memberi reaksi kepada cerita anda - Menghantar reaksi kepada cerita + Memberi reaksi kepada cerita @@ -5476,5 +5547,15 @@ Padam nama pengguna + + + j + + m + + Tetapkan + + Masa minimum sebelum kunci skrin digunakan ialah 1 minit. + diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index de76638cf8..2ad3d60243 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -14,6 +14,7 @@ + ဟုတ်ကဲ့ မလုပ်ပါ @@ -96,13 +97,13 @@ မက်ဆေ့ချ်များ စစ်နေပါသည်… - ဘလော့ထားသော သုံးစွဲသူများ - ဘလော့ထားသော သုံးစွဲသူကို ထပ်ပေါင်းပါ - ဘလော့ထားသော သုံးစွဲသူများသည် သင့်ကို ဖုန်းခေါ်ခြင်း သို့မဟုတ် မက်ဆေ့ချ်ပို့ခြင်းများ မလုပ်နိုင်ပါ။ - ဘလော့ထားသူ မရှိ - သုံးစွဲသူအား ဘလော့လိုက်မည်လား။ + ဘလော့ခ်ထားသော သုံးစွဲသူများ + ဘလော့ခ်ထားသော သုံးစွဲသူ ပေါင်းထည့်ရန် + ဘလော့ခ်ထားသော သုံးစွဲသူများသည် သင့်ထံ ဖုန်းခေါ်ခြင်း သို့မဟုတ် မက်ဆေ့ချ်ပို့ခြင်းများ မလုပ်နိုင်တော့ပါ။ + ဘလော့ခ်ထားသော သုံးစွဲသူ မရှိ + သုံးစွဲသူအား ဘလော့ခ်မည်လား။ \"%1$s\" သည် သင့်ကိုဖုန်းခေါ်ခြင်း သို့မဟုတ် စာပို့ခြင်း မလုပ်နိုင်တော့ပါ။ - ဘလော့မည် + ဘလော့ခ်ရန် @@ -138,8 +139,8 @@ ဆက်လုပ်ရန် - %1$s ကို ဘလော့ပြီး ထွက်မည်လား။ - %1$sကို ဘလော့မည်လား။ + %1$s ကို ဘလော့ခ်ပြီး ထွက်မည်လား။ + %1$s ကို ဘလော့ခ်မည်လား။ သင်သည် ဤအဖွဲ့မှ မက်ဆေ့ခ်ျများ သို့မဟုတ် နောက်ဆုံးသတင်းများကို လက်ခံရရှိတော့မည် မဟုတ်ပါ။ အဖွဲ့ဝင်များက သင့်ကို ဤအဖွဲ့ထဲသို့ ထပ်မံထည့်သွင်း၍ မရနိုင်တော့ပါ။ အဖွဲ့ဝင်များက သင့်ကို ဤအဖွဲ့ထဲသို့ ထပ်မံထည့်သွင်း၍ မရနိုင်တော့ပါ။ အဖွဲ့ဝင်များက သင့်ကို ဤအဖွဲ့ထဲသို့ ထပ်မံထည့်သွင်း၍ မရနိုင်တော့ပါ။ @@ -147,16 +148,16 @@ သင်တို့တစ်ဦးနှင့်တစ်ဦး မက်ဆေ့ချ်ပို့ခြင်းနှင့် ဖုန်းခေါ်ဆိုခြင်း ပြုလုပ်နိုင်ပြီး သင်၏အမည်နှင့် ဓာတ်ပုံကို သူတို့နှင့်အတူ မျှဝေမည်။ သင်တို့အချင်းချင်း မက်ဆေ့ချ်ပို့နိုင်ပါမည်။ - ဘလော့ထားသော လူများသည် သင့်ကို ဖုန်းခေါ်ခြင်း သို့မဟုတ် မက်ဆေ့ချ်ပို့ခြင်းများ မလုပ်နိုင်တော့ပါ။ - ဘလော့လုပ်ခံထားရသူများသည် သင့်ထံ မက်ဆေ့ချ်များ ပို့နိုင်တော့မည် မဟုတ်ပါ။ + ဘလော့ခ်ထားသော လူများသည် သင့်ထံ ဖုန်းခေါ်ခြင်း သို့မဟုတ် မက်ဆေ့ချ်ပို့ခြင်းများ မလုပ်နိုင်တော့ပါ။ + ဘလော့ခ်ထားသော သူများသည် သင့်ထံ မက်ဆေ့ချ်များ ပို့နိုင်တော့မည် မဟုတ်ပါ။ Signal အပ်ဒိတ်များနှင့် သတင်းများ ရယူမှုကို ပိတ်ပါမည်။ Signal အပ်ဒိတ်များနှင့် သတင်းများကို ပြန်ရယူပါမည်။ - %1$s ကို မဘလော့ တော့ဘူးလား။ - ဘလော့မည် - ဘလော့ပြီး ထွက်မည် - စပမ်းကို တိုင်ကြားပြီး ဘလော့မည် + %1$s ကို ဘလော့ခ်ဖြေမည်လား။ + ဘလော့ခ်ရန် + ဘလော့ခ်ပြီး ထွက်ရန် + စပမ်းအဖြစ် ရီပို့(တ်)လုပ်ပြီး ဘလော့ခ်ရန် ယနေ့ @@ -318,7 +319,7 @@ Signal မက်ဆေ့ချ် Molly %1$s ကိုပြောင်းသုံးကြပါစို့ ဆက်သွယ်လိုသည့်သူကို ရွေးချယ်ပါ - ဘလော့ဖြည်ပါ + ဘလော့ခ်ဖြေရန် ပူးတွဲဖိုင်သည် သတ်မှတ်ဖိုင်အရွယ်အစားထက်ကြီးနေသည်။ အသံဖမ်း၍ မရနိုင်ပါ! သင်သည်အဖွဲ့ဝင်မဟုတ်တော့သောကြောင့် ဤအဖွဲ့သို့စာများပို့မရပါ။ @@ -366,7 +367,7 @@ မီဒီယာပို့ခြင်းအမှား - áá­á¯ááºáá¼á¬á¸áá®á¸ ááá±á¬á·ááẠ+ စပမ်းအဖြစ် ရီပို့(တ်)လုပ်ပြီး ဘလော့ခ်ပြီးပါပြီ။ SMS မက်ဆေ့ချ်လုပ်ဆောင်ချက်ကို လက်ရှိတွင် ပိတ်ထားပါသည်။ သင့်မက်ဆေ့ချ်များကို သင့်ဖုန်းရှိ အခြားအက်ပ်ထံသို့ ထုတ်သိမ်းနိုင်ပါသည်။ @@ -441,15 +442,15 @@ %1$s ရှိနေပါသည် - တောင်းဆိုချက်ကို ဘလော့လုပ်မည်လား။ + တောင်းဆိုချက်ကို ဘလော့ခ်မည်လား။ %1$s သည် ဤအဖွဲ့လင့်ခ်မှတစ်ဆင့် ဤအဖွဲ့တွင် ပါဝင်နိုင်ခြင်း သို့မဟုတ် ပါဝင်ရန် တောင်းဆိုနိုင်ခြင်းများ ပြုလုပ်နိုင်တော့မည် မဟုတ်ပါ။ ၎င်းတို့ကို အဖွဲ့အတွင်းသို့ ကိုယ်တိုင် ပေါင်းထည့်နိုင်ပါသေးသည်။ - တောင်းဆိုချက်ကို ဘလော့လုပ်ရန် + တောင်းဆိုချက်ကို ဘလော့ခ်ရန် ပယ်ဖျက်မယ် - ပိတ်ပယ်ထားသည် + ဘလော့ခ်ထားပြီး စစ်ထုတ်မှုကို ရှင်းရန် @@ -470,7 +471,7 @@ ပြောစကား %1$d ကို စာပုံးထဲသို့ရွေ့သည် - ဖတ်သည် + ဖတ်ပြီး မဖတ်ရသေး @@ -482,12 +483,12 @@ ပင်ဖြုတ်ရန် - အသံတိတ်ထားရန် + အသံပိတ်ရန် - အသံပြန်ဖွင့်ရန် + အသံပြန်ဖွင့်ရန် - ရွေးမယ် + ရွေးရန် စုစည်းရန် @@ -497,7 +498,7 @@ ဖျက်ရန် - အားလုံးကို ရွေးမယ် + အားလုံး ရွေးရန် %1$d ရွေးထားပြီး @@ -522,6 +523,15 @@ +%1$d + + သင့်စက်များကို ပြန်ချိတ်ရန် + + သင့်စက်ကို စာရင်းမသွင်းထားပါက သင် ပေါင်းထည့်ထားသော စက်များသည် ချိတ်ဆက်မှုပြုတ်နေပါမည်။ စက်တစ်လုံးလုံးကို ပြန်ချိတ်ရန် ဆက်တင်သို့ သွားပါ။ + + ဆက်တင် ဖွင့်ရန် + + နောက်မှ + မန်ဘာများ ရွေးရန် @@ -897,7 +907,7 @@ မန်းရှင်းများရှိလျှင် ကျွန်ုပ်ကိုအသိပေးပါ - အသံတိတ် စကားပြောဆိုမှုများတွင် သင့်ကိုမန်းရှင်းခေါ်လျှင် အသိပေးချက်များ လက်ခံမည်လား? + အသံပိတ်ထားသော ချက်(တ်)များတွင် သင့်ကို မန်းရှင်းခေါ်လျှင် အသိပေးချက်များ လက်ခံလိုပါသလား။ အမြဲတမ်း ကျွန်ုပ်ကို အသိပေးပါ ကျွန်ုပ်အား အသိပေးမထားပါနဲ့ @@ -915,6 +925,16 @@ သုံးစွဲသူအမည် ဖန်တီးပြီး သုံးစွဲသူအမည် ကူးပြီး + + သုံးစွဲသူအမည်ကို ဖျက်၍ မရနိုင်ပါ။ နောက်မှ ထပ်ကြိုးစားကြည့်ပါ။ + + သုံးစွဲသူအမည် ဖျက်ပစ်ပြီး + + + + သင်၏ သုံးစွဲသူအမည်နှင့်ပတ်သက်၍ တစ်စုံတစ်ခု မှားယွင်းနေပါသည်၊ သင့်အကောင့်အတွက် သတ်မှတ်ပေးတော့မည် မဟုတ်ပါ။ ထပ်မံကြိုးစား၍ သတ်မှတ်နိုင်ပါသည် သို့မဟုတ် အသစ်တစ်ခု ရွေးချယ်နိုင်ပါသည်။ + + ယခု ဖြေရှင်းရန် @@ -1108,8 +1128,8 @@ အဖွဲ့သစ် သူငယ်ချင်းများကို ဖိတ်ပါ SMS ကိုသုံးပါ - ပုံပန်းသဏ္ဌာန် - ဓာတ်ပုံ ပေါင်းထည့်ရန် + ချက်(တ်)အရောင် + ပရိုဖိုင်ပုံ ပေါင်းထည့်ရန် ပြန်စာများ @@ -1410,14 +1430,14 @@ လက်ခံသည် ဆက်လက်၍ ဖျက်ရန် - တားမြစ်မည် - ပြန်ဖွင့်သည် + ဘလော့ခ်ရန် + ဘလော့ခ်ဖြေရန် %1$s မှ သင့်ကို မက်ဆေ့ချ်ပို့ရန် ခွင့်ပြုပြီး သင်၏ အမည်နှင့် ရုပ်ပုံတို့ကို ၎င်းနှင့် မျှဝေမည်လား? ၎င်းသည် သင်မှ ၎င်းတို့၏ မက်ဆေ့ချ်များကို ဖတ်ရှုခဲ့သည်ကို သင်လက်ခံသည်အထိ မတွေ့ရပါ။ - %1$s မှ သင့်ကို မက်ဆေ့ချ်ပေးပို့ရန် ခွင့်ပြုပြီး ၎င်းနှင့် သင်၏ အမည်နှင့် ရုပ်ပုံကို မျှဝေမည်လား? သင်မှ ၎င်းအား ပိတ်ပယ်ထားခြင်းကို ပြန်ဖြည်သည်အထိ မည်သည့်မက်ဆေ့ချ်များကို မရရှိနိုင်ပါ။ + %1$s အား သင့်ထံ မက်ဆေ့ချ်ပို့ခွင့်ပြုပြီး သင်၏ အမည်နှင့် ပုံကို ၎င်းထံ မျှဝေမည်လား။ ၎င်းတို့အား ဘလော့ခ် မဖြေမချင်း မက်ဆေ့ချ်များ ရရှိမည် မဟုတ်ပါ။ - %1$s အား သင့်ထံ မက်ဆေ့ချ် ပို့ခွင့်ပြုမည်လား။ ၎င်းတို့အား ဘလော့ မဖြေမချင်း မက်ဆေ့ချ်များ ရရှိမည် မဟုတ်ပါ။ - %1$s ထံမှ အပ်ဒိတ်များနှင့် သတင်းများ ရယူမည်လား။ ၎င်းတို့အား ဘလော့ မဖြေမချင်း အပ်ဒိတ်များ ရရှိမည် မဟုတ်ပါ။ + %1$s အား သင့်ထံ မက်ဆေ့ချ် ပို့ခွင့်ပြုမည်လား။ ၎င်းတို့အား ဘလော့ခ် မဖြေမချင်း မက်ဆေ့ချ်များ ရရှိမည် မဟုတ်ပါ။ + %1$s ထံမှ အပ်ဒိတ်များနှင့် သတင်းများ ရယူမည်လား။ ၎င်းတို့အား ဘလော့ခ် မဖြေမချင်း အပ်ဒိတ်များ ရရှိမည် မဟုတ်ပါ။ ၎င်းအဖွဲ့နှင့် စကားပြောဆိုမှု ဆက်လက်ပြီး သင်၏ အမည်နှင့် ရုပ်ပုံကို အဖွဲ့ဝင်များနှင့် မျှဝေမည်လား? ၎င်းအဖွဲ့ကို အဆင့်မြှင့်၍ @မန်းရှင်းများ နှင့် စီမံသူ အင်္ဂါရပ်အသစ်များကို သုံးလိုက်ပါ။ ၎င်းအဖွဲ့ထဲတွင် အမည်နှင့် ရုပ်ပုံများ မမျှဝေရသေးသော အဖွဲ့ဝင်များကို ဝင်ရောက်ရန် ဖိတ်ခေါ်ပါလိမ့်မည်။ ဤ အမွေအဖွဲ့သည် အလွန်ကြီး၍ အဖွဲ့သစ်သို့အဆင့်မြှင့်လို့မရပါ။ အများဆုံးရှိနိုင်သော အဖွဲ့ဝင်အရေတွက်သည် %1$d ဦးသာ ဖြစ်ပါသည်။ @@ -1425,7 +1445,7 @@ ၎င်းအဖွဲ့သို့ ဝင်ရောက်ပြီး သင်၏ အမည် နှင့် ရုပ်ပုံတို့ကို အဖွဲ့ဝင်များနှင့် မျှဝေမည်လား? အဖွဲ့ဝင်များသည် သင်မှ ၎င်းတို့၏ မက်ဆေ့ချ်များကို ဖတ်ရှုခဲ့သည်ကို သင်လက်ခံသည်အထိ မတွေ့ရပါ။ ဤအဖွဲ့တွင် ပါဝင်ပြီး ၎င်းမန်ဘာများထံ သင့်အမည်နှင့် ဓာတ်ပုံကို မျှဝေမည်လား။ သင် လက်မခံမချင်း ၎င်းတို့၏ မက်ဆေ့ချ်များကို မြင်နိုင်မည် မဟုတ်ပါ။ ၎င်းအဖွဲ့သို့ ဝင်ရောက်မည်လား? အခြားအဖွဲ့ဝင်များသည် သင်မှ ၎င်းတို့၏ မက်ဆေ့ချ်များကို ဖတ်ရှုခဲ့သည်ကို သင်လက်ခံသည်အထိ မတွေ့ရပါ။ - ၎င်းအဖွဲ့ကို ပိတ်ထားခြင်းမှ ပြန်ဖြည်ပြီး အဖွဲ့ဝင်များနှင့် သင်၏ အမည်နှင့် ရုပ်ပုံကို မျှဝေမည်လား? သင်မှ ပြန်ဖြည်သည်အထိ မည်သည့်မက်ဆေ့ချ်များကို မရရှိနိုင်ပါ။ + ဤအဖွဲ့ကို ဘလော့ခ်ဖြေပြီး ၎င်းအဖွဲ့ဝင်များထံ သင့်အမည်နှင့် ပုံကို မျှဝေမည်လား။ သင် ဘလော့ခ်မဖြေမချင်း မက်ဆေ့ချ်များ ရရှိမည် မဟုတ်ပါ။ ကြည့်မယ် %1$s ၏ အဖွဲ့ဝင် @@ -1520,9 +1540,20 @@ ပင်နံပါတ်အသစ်ဖန်တီးပါ + + SMS ကုဒ် ပို့ရန် + + Signal မှတ်ပုံတင်ခြင်း- Android အတွက် PIN ပြန်လည်မှတ်ပုံတင်ခြင်းနှင့်အတူ အကူအညီ လိုအပ်ချက် + + သင်ဖန်တီးထားသော သင်၏ PIN သည် ဂဏန်း %1$d ခုအထက် ပါသော ကုဒ်ဖြစ်ပြီး နံပါတ် သို့မဟုတ် အက္ခရာနံပါတ် ဖြစ်နိုင်ပါသည်။\n\nသင်၏ PIN ကို မမှတ်မိပါက အသစ်တစ်ခု ဖန်တီးနိုင်ပါသည်။ + + သင်၏ PIN ကို မမှတ်မိပါက အသစ်တစ်ခု ဖန်တီးနိုင်ပါသည်။ + + PIN ခန့်မှန်းထည့်သွင်းမှုများ မရှိတော့ပါ၊ သို့သော် PIN အသစ်တစ်ခု ဖန်တီးခြင်းဖြင့် သင့် Signal အကောင့်ကို ဝင်ရောက်သုံးစွဲနိုင်ပါသေးသည်။ + သတိပေးချက် - ပင်နံပါတ်ကို ပိတ်ထားလျှင် သင်မှ​ ကိုယ်တိုင် အရန်ပြုပြင်ထားခြင်းမရှိခဲ့လျှင် Signal ပေါ်ပြန်စာရင်းသွင်းသောအခါ ဒေတာအားလုံးကို ဆုံးရှူံးပါလိမ့်မည်။ ပင်နံပါတ်ကို ပိတ်ထားစဉ် မှတ်ပုံတင်ခြင်းသော့ကို သင်ဖွင့်၍ မရပါ။ + PIN ကို ပိတ်ထားပါက Signal ကို ပြန်လည်စာရင်းသွင်းသည့်အခါ ကိုယ်တိုင် ဘက်ခ်အပ်လုပ်၍ ပြန်လည်မသိုလှောင်မချင်း ဒေတာအားလုံးပျောက်သွားပါလိမ့်မည်။ PIN ကို ပိတ်ထားစဉ် စာရင်းသွင်းလော့ခ်ကို ဖွင့်၍ မရနိုင်ပါ။ ပင်နံပါတ်ပိတ်ပါ @@ -1552,8 +1583,8 @@ ကျွန်ုပ်၏ စတိုရီ - ဘလော့ လုပ်မည် - ဘလော့ဖြည်ပါ + ဘလော့ခ်ရန် + ဘလော့ခ်ဖြေရန် @@ -1642,9 +1673,9 @@ ကင်မရာ - အသံပြန်ဖွင့်မယ် + အသံပြန်ဖွင့်ရန် - အသံတိတ်ထားမည် + အသံပိတ်ရန် ဖုန်းခေါ်ရန် @@ -1656,12 +1687,12 @@ - %1$sကိုပိတ်ထားသည် + %1$s ကို ဘလော့ခ်ထားသည် ပိုမိုသိရှိစရာများ သင်သည် သူတို့၏ အသံ သို့မဟုတ် ဗီဒီယိုများကို လက်ခံရရှိလိမ့်မည် မဟုတ်ပါ၊ ၎င်းတို့လည်း သင်၏ ဖိုင်များကို မလက်ခံရရှိပါ။ %1$s မှ အသံနှင့် ဗီဒီယို မရရှိနိုင်ပါ %1$s မှ အသံနှင့် ဗီဒီယို မရရှိနိုင်ပါ - သင့် လုံခြုံရေးနံပါတ် ပြောင်းလဲမှုကို ၎င်းတို့မှ အတည်မပြုရသေးသောကြောင့် သို့မဟုတ် ၎င်းတို့စက်တွင် ပျက်ကွက်မှုဖြစ်ခဲ့သောကြောင့် သို့မဟုတ် ၎င်းတို့မှ သင့်ကို ပိတ်ပယ်ထားခြင်းကြောင့် တို့ဖြစ်နိုင်ပါသည်။ + သင့်လုံခြုံရေးနံပါတ် ပြောင်းလဲမှုကို ၎င်းတို့ အတည်မပြုရသေးသောကြောင့် သော်လည်းကောင်း၊ ၎င်းတို့ စက်တွင် ပြဿနာရှိနေသောကြောင့် သော်လည်းကောင်း၊ ၎င်းတို့က သင့်ကို ဘလော့ခ်ထားသောကြောင့် သော်လည်းကောင်း ဖြစ်နိုင်ပါသည်။ စခရင် မျှဝေမှုကို ကြည့်ရန် ပွတ်ဆွဲပါ @@ -1698,11 +1729,18 @@ Signal သည် သင့်အား မိတ်ဆွေများနှင့် ဆက်သွယ်ပေးနိုင်ရန်နှင့် မက်ဆေ့ချ်များ ပေးပို့နိုင်စေရန်အတွက် ကူညီပေးရန် အဆက်အသွယ်နှင့် မီဒီယာ ခွင့်ပြုချက်များ လိုအပ်ပါသည်။ သင့်အဆက်အသွယ်များကို End-to-end ကုဒ်ပြောင်းဝှက်ထားသော Signal ၏ သီးသန့် အဆက်အသွယ် ရှာဖွေရေးကို သုံး၍ အပ်လုဒ်လုပ်ထားသောကြောင့် Signal ဝန်ဆောင်မှုတွင် မည်သည့်အခါမျှ မြင်ရမည် မဟုတ်ပါ။ Signal သည် သင့်အား မိတ်ဆွေများနှင့် ဆက်သွယ်ပေးရန်အတွက် အဆက်အသွယ် ခွင့်ပြုချက် လိုအပ်ပါသည်။ သင့်အဆက်အသွယ်များကို End-to-end ကုဒ်ပြောင်းဝှက်ထားသော Signal ၏ သီးသန့် အဆက်အသွယ် ရှာဖွေရေးကို သုံး၍ အပ်လုဒ်လုပ်ထားသောကြောင့် Signal ဝန်ဆောင်မှုတွင် မည်သည့်အခါမျှ မြင်ရမည် မဟုတ်ပါ။ ဤနံပတ်ဖြင့် မှတ်ပုံတင်ခြင်းကို အကြိမ်ပေါင်းများစွာ ကြိုးစားပြီးပါပြီ။ ကျေးဇူးပြူ၍ နောင်အခါမှ ပြန်လည်ကြိုးစားပါ။ + + ဤနံပါတ်ကို မှတ်ပုံတင်ရန် ကြိုးပမ်းမှုများစွာ ဆောင်ရွက်ခဲ့ပါသည်။ %1$s အကြာတွင် ထပ်ကြိုးစားကြည့်ပါ။ ဝန်ဆောင်မှုနှင့် ဆက်သွယ်၍ မရနိုင်ပါ။ အင်တာနက်ချိတ်ဆက်မှုအား နောက်တစ်ကြိမ် စစ်ဆေး၍ ပြန်လည်ကြိုးစားကြည့်ပါ။ စံပုံစံနှင့် မကိုက်ညီသည့် နံပါတ် သင်ထည့်သွင်းထားသည့် နံပါတ် (%1$s) သည် စံပုံစံနှင့် မကိုက်ညီပုံပေါ်ပါသည်။\n\nသင်ဆိုလိုသည်မှာ %2$s ဟုတ်ပါသလား။ Molly Android - ဖုန်းနံပါတ် ပုံစံ + ကောလ် တောင်းဆိုပြီး + + SMS တောင်းဆိုပြီး + + အတည်ပြုကုဒ် တောင်းဆိုပြီး ယခု သင်သည် ပျက်ကွက်မှုပြင်ခြင်း မှတ်တမ်း တင်ရန်အတွက် အဆင့် %1$d ဆင့်သာလိုတော့သည် @@ -1721,6 +1759,16 @@ ဖုန်းခေါ်ဆိုမှု အတည်ပြုကုဒ် ကုဒ် ပြန်ပို့ရန် + + စာရင်းသွင်းမှုနှင့်စပ်လျဉ်း၍ အခက်အခဲရှိပါသလား။ + + • သင့် SMS သို့မဟုတ် ခေါ်ဆိုမှုကို လက်ခံရန် သင့်ဖုန်းတွင် ဆယ်လူလာ လိုင်းရှိကြောင်း သေချာပါစေ\n • နံပါတ်သို့ ဖုန်းခေါ်ဆိုမှု လက်ခံနိုင်ကြောင်း အတည်ပြုပါ\n • သင့်ဖုန်းနံပါတ်ကို မှန်ကန်စွာ ရိုက်ထည့်ထားကြောင်း စစ်ဆေးပါ။ + + နောက်ထပ်အချက်အလက်အတွက် ဤပြဿနာဖြေရှင်းနည်း အဆင့်များအတိုင်း လုပ်ဆောင်ပါ သို့မဟုတ် ပံ့ပိုးမှုစင်တာသို့ ဆက်သွယ်ပါ + + ဤပြဿနာဖြေရှင်းနည်း အဆင့်များ + + ပံ့ပိုးမှုစင်တာသို့ ဆက်သွယ်ရန် မှတ်ပုံတင်မှု ပိတ်ထားခြင်းကို အသုံးပြုမည်လား? @@ -1887,6 +1935,10 @@ ငွေပေးချေမှု မက်ဆေ့ချ် အစီအစဉ် + + သင့်မက်ဆေ့ချ်မှတ်တမ်းကို ပေါင်းစည်းပြီးပါပြီ + + %1$s ကို %2$s က ပိုင်ဆိုင်ပါသည် Molly ဗားရှင်းအသစ် @@ -1958,7 +2010,7 @@ ရှိမနေတော့သည့် ဆက်ရှင်အတွက် MMS စာများကို encrypt လုပ်ထားပါသည်။ - အသိပေးချက်ကို ပိတ်ပါ + အသိပေးချက် အသံပိတ်ရန် သွင်းယူနေသည် @@ -2015,14 +2067,16 @@ မလုံခြုံသော SMS %1$s %2$s အဆက်အသွယ် - \"%2$s\" သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ - သင့် ဗီဒီယို သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ - သင့် ရုပ်ပုံ သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ - သင့် GIF အား %1$s တုံ့ပြန်ခဲ့သည်။ - သင့် ဖိုင်သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ - သင့် အသံဖိုင် သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ - သင့် တစ်ခါကြည့် မီဒီယာ သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ - သင့် စတစ်ကာ သို့ %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + \"%2$s\" ကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့်ဗီဒီယိုကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့်ရုပ်ပုံကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့် GIF ကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့်ဖိုင်ကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့်အသံဖိုင်ကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့်တစ်ကြိမ်ကြည့် မီဒီယာကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + + သင့်ငွေပေးချေမှုကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ + သင့်စတစ်ကာကို %1$s ဖြင့် တုံ့ပြန်ခဲ့ပါသည်။ ဤမက်ဆေ့အား ဖျက်ပြီးပါပြီ။ အဆက်အသွယ်မှ​ Signal သို့ ဝင်ရောက်သည်ဆိုသည့် အသိပေးချက်များကို ပိတ်မည်လား? Signal > အပြင်အဆင်များ > အသိပေးချက်များ ထဲဝင်၍ ၎င်းအသိပေးချက်များကို ဖွင့်လို့ရပါသည်။ @@ -2222,7 +2276,7 @@ ခေါ်ဆိုမှု သတိပေးချက်များ ဖွင့်မယ် အဆက်အသွယ် အပ်ဒိတ်မယ် - တောင်းဆိုချက်ကို ဘလော့လုပ်ရန် + တောင်းဆိုချက်ကို ဘလော့ခ်ရန် တူညီသော အဖွဲ့များ မရှိပါ။ တောင်းခံမှုများကို သေချာစွာ သုံးသပ်ပါ။ ၎င်းအဖွဲ့ထဲတွင် အဆက်အသွယ်များ မရှိပါ။ တောင်းခံမှုများကို သေချာစွာ သုံးသပ်ပါ။ ကြည့်မယ် @@ -2472,7 +2526,7 @@ ဆိုင်းငံ့နေသည် - သို့ပို့ပြီး + ဖော်ပြပါသို့ ပို့ပြီး မှပေးပို့သည် သို့ပို့ပြီး မှဖတ်ပြီး @@ -2552,10 +2606,10 @@ မူလကို သုံးပါ အထူးပြုကိုသုံးသည် - အသံကို ၁ နာရီ ပိတ်ထားပါ။ - ၈ နာရီ အသံပိတ်ထားပါ - အသံကို ၁ ရက် ပိတ်ထားပါ - အသံကို ၇ ရက် ပိတ်ထားပါ။ + 1 နာရီစာ အသံပိတ်ရန် + 8 နာရီစာ အသံပိတ်ရန် + 1 ရက်စာ အသံပိတ်ရန် + 7 ရက်စာ အသံပိတ်ရန် အမြဲ အပြင်အဆင်များကို မူလသို့ @@ -2591,9 +2645,9 @@ လိပ်စာစာအုပ်ဓာတ်ပုံများကို သုံးပါ ဖြစ်နိုင်လျှင် စနစ်ထဲရှိ အဆက်အသွယ်များ၏ ရုပ်ပုံများကို ပြသပါ - Keep Muted Chats Archived + စုစည်းပြီးသည့် ချက်(တ်)များကို ဆက်လက် အသံပိတ်ရန် - မက်ဆေ့ချ်အသစ် ရောက်လာချိန်တွင် စုစည်းထားသော အသံတိတ်ချက်(တ်)များသည် စုစည်းလျက်သား ကျန်ရှိပါမည်။ + မက်ဆေ့ချ်အသစ် ရောက်လာချိန်တွင် စုစည်းပြီးသော ချက်(တ်)များသည် အသံပိတ်လျက်သား ဆက်ရှိနေပါမည်။ ကြိုတင်ကြည့်ရှုရန် လင့်ခ်များ ထုတ်လုပ်ပါ သင်ပေးပို့သော မက်ဆေ့ချ်များ အတွက် ဝဘ်ဆိုဒ်များမှ ကြိုတင်ကြည့်ရှုနိုင်သည့် လင့်ခ်များကို တိုက်ရိုက်ရယူပါ။ စကားဝှက်ကို ပြောင်းပါ @@ -2887,7 +2941,7 @@ ငွေပေးချေပြီး ငွေပေးချေမှုလက်ခံရယူပြီး ငွေပေးချေမှု အောင်မြင်ပြီး %1$s - နံပါတ်အား ပိတ်ပယ်မယ် + ဖုန်းနံပါတ် ဘလော့ခ်ရန် လွှဲပြောင်းမယ် @@ -2972,7 +3026,7 @@ သို့ မက်ဆေ့ချ်အသစ် … - အသုံးပြုသူအား ပိတ်မယ် + သုံးစွဲသူအား ဘလော့ခ်ရန် အဖွဲ့ထဲသို့ ပေါင်းထည့်မယ် @@ -3043,10 +3097,10 @@ - ပြန်ဖွင့်ပါ + အသံပြန်ဖွင့်ရန် - အသိပေးချက်များကို ပိတ်ပါ + အသိပေးချက် အသံပိတ်ရန် အဖွဲ့ အပြင်အဆင်များ @@ -3207,6 +3261,8 @@ သင်၏ ပင်နံပါတ် ကိုထည့်ပါ သင့်အကောင့်အတွက် ဖန်တီးထားသော ပင်နံပါတ် ထည့်ပါ၊ SMS စာတိုအနေဖြင့်ပေးပို့သည့် အတည်ပြုကုဒ်နှင့် မတူပါ။ + + သင့်အကောင့်အတွက် သင်ဖန်တီးထားသော PIN ကို ရိုက်ထည့်ပါ။ အက္ခရာနံပါတ်ပါ ပင်နံပါတ်ကို ရိုက်ထည့်ပါ နံပါတ်ပါ ပင်နံပါတ်ကို ရိုက်ထည့်ပါ ပင်နံပါတ်မှားနေသည်။ နောက်တစ်ကြိမ် ကြိုးစားပါ။ @@ -3305,7 +3361,10 @@ သင့်ဘက်ခ်အပ်တွင် သိမ်း၍ မရနိုင်သည့် အလွန်ကြီးသော ဖိုင်တစ်ဖိုင် ပါဝင်နေပါသည်။ ၎င်းကို ဖျက်ပြီး ဘက်ခ်အပ်အသစ် ဖန်တီးပါ။ အရန်ကူးခြင်းများကိုစီမံရန် နှိပ်ပါ နံပါတ် မှားနေပါသလား။ + (%1$02d:%2$02d) အကြာတွင် ဖုန်းခေါ်ရန် + + ကုဒ် ပြန်ပို့ရန် (%1$02d:%2$02d) Signal အကူအညီ ရယူမည် Signal မှတ်ပုံတင်ခြင်း Android အတွက် အတည်ပြုကုဒ် ကုဒ် မှားနေပါသည် @@ -3313,6 +3372,18 @@ မသိ ကျွန်ုပ်ဖုန်းနံပါတ်ကိုကြည့်မည် ဖုန်းနံပါတ်အားဖြင့်ကျွန်ုပ်ကိုရှာပါ + + ဖုန်းနံပါတ် + + သင့်ဖုန်းနံပါတ်ကို မြင်နိုင်သူများနှင့် Molly တွင် ထိုနံပါတ်ဖြင့် သင့်ထံသို့ ဆက်သွယ်နိုင်သူများကို ရွေးချယ်ပါ။ + + ကျွန်ုပ်၏ ဖုန်းနံပါတ်ကို မြင်နိုင်သူများ + + Molly ပေါ်တွင် သင့်ဖုန်းနံပါတ်ကို မည်သူမျှ မြင်နိုင်မည် မဟုတ်ပါ + + ဖုန်းနံပါတ်ဖြင့် ကျွန်ုပ်ကို ရှာနိုင်သူများ + + သင် မက်ဆေ့ချ်ပို့သော လူများနှင့် အဖွဲ့များသည် သင့်ဖုန်းနံပါတ်ကို မြင်တွေ့ရပါမည်။ သင့်ဖုန်းနံပါတ်ကို ဖုန်းအဆက်အသွယ်များထဲတွင် ထည့်ထားသူများသည်လည်း သင့်ဖုန်းနံပါတ်ကို Molly ပေါ်တွင် တွေ့ရှိရပါမည်။ လူသားတိုင်း ကျွန်ုပ်၏အဆက်အသွယ်များ ဘယ်သူမှ @@ -3469,8 +3540,8 @@ - ပိတ်ပယ်မယ် - ပြန်ဖွင့်မယ် + ဘလော့ခ်ရန် + ဘလော့ခ်ဖြေရန် အဆက်အသွယ်စာရင်းထဲထည့်မယ် အဆက်အသွယ်များကို ဖွင့်နိုင်သည့် အက်ပ်အား ရှာမတွေ့ပါ။ @@ -3522,9 +3593,9 @@ %1$s/%2$s - \"%1$s\" ကို ပိတ်ပယ်ထားပါသည် - \"%1$s\" ကို ပိတ်ပယ်ခြင်း မအောင်မြင်ပါ - \"%1$s\" ကို ပိတ်ပင်ခြင်းမှ ပြန်ဖြည်လိုက်ပြီ + \"%1$s\" ကို ဘလော့ခ်ထားပါသည် + \"%1$s\" ကို ဘလော့ခ်ရန် မအောင်မြင်ပါ + \"%1$s\" ကို ဘလော့ခ်ဖြေပြီးပါပြီ။ အဖွဲ့ဝင်များကို ပြန်လည်စစ်ဆေးပါ @@ -3549,7 +3620,7 @@ သင်၏အဆက်အသွယ် အဖွဲ့မှ ဖယ်ရှားရန် အဆက်အသွယ် အပ်ဒိတ်မယ် - ပိတ်ပယ်မယ် + ဘလော့ခ်ရန် ဖျက်ရန် မကြာသေးမီက ပရိုဖိုင်းအမည်ကို %1$s မှ %2$s သို့ ပြောင်းထားခဲ့ပါသည်။ @@ -3572,17 +3643,17 @@ Wi-Fi အားနည်းနေပါသည်။ ဆဲလ်လူလာသို့ ပြောင်းပြီး။ - သင်၏အကောင့်ကိုဖျက်ခြင်းသည် - + သင့်အကောင့်ကိုဖျက်ခြင်းဖြင့်- သင့်ဖုန်းနံပါတ်ကိုထည့်ပါ အကောင့်ကို ဖျက်ရန် - သင်၏ အကောင့်အချက်အလက် နှင့် ပရိုဖိုင်းဓာတ်ပုံများကို ဖျက်ရန် - သင်၏ မက်ဆေ့ချ်များအားလုံးကို ဖျက်ရန် + သင့်အကောင့်အချက်အလက်နှင့် ပရိုဖိုင်ဓာတ်ပုံကို ဖျက်မည် + သင်၏ မက်ဆေ့ချ်များအားလုံးကို ဖျက်မည် %1$s ကို သင့်ငွေပေးချေသည့် အကောင့်မှ ဖျက်ရန် နိုင်ငံကုဒ်နံပါတ် သတ်မှတ်ထားခြင်း မရှိပါ နံပါတ်သတ်မှတ်ထားခြင်း မရှိပါ သင်ရိုက်ထည့်ထားသော ဖုန်းနံပါတ်သည် သင့်အကောင့်အတွင်းရှိ နံပါတ်နှင့် မကိုက်ညီပါ။ သင့်အကောင့်ကို ဖျက်သိမ်းလိုသည်မှ သေချာပါသလား။ - ဤလုပ်ဆောင်ချက်သည် သင့် Signal အကောင့်ကို ဖျက်သိမ်းပြီး အက်ပလီကေးရှင်းကို ရီစက် ချပါမည်။ အက်ပ်သည် ဤလုပ်ဆောင်မှုပြီးလျှင် ပိတ်သွားပါမည်။ + ဤသည်က သင့် Signal အကောင့်ကို ဖျက်သိမ်းပြီး အပလီကေးရှင်းကို ပြန်လည်သတ်မှတ်ပါမည်။ အက်ပ်သည် လုပ်ဆောင်မှုပြီးလျှင် ပိတ်သွားပါမည်။ စက်တွင်းရှိဒေတာဖျက်ခြင်း မအောင်မြင်ပါ။ စနစ်ထဲရှိ အက်ပလီကေးရှင်း အပြင်အဆင်များထဲ၌ သင်ကိုယ်တိုင်သွား ရှင်းနိုင်ပါသည်။ ဖုန်းဆက်တင်ကိုဖွင့်မည် @@ -3906,12 +3977,12 @@ ပရိုဖိုင် ဖန်တီးရန် - ပိတ်ပယ်ထားသည် + ဘလော့ခ်ထားပြီး အဆက်အသွယ် %1$d ခု မက်ဆေ့ချ်ပေးပို့ခြင်း ပျောက်ကွယ်မည့် မက်ဆေ့ချ်များ အက်ပ် လုံခြုံရေး - မကြာသေးမီ စာရင်းနှင့် app အတွင်း Screenshot များကို တားမြစ်ပါ + နောက်ဆုံး စာရင်းနှင့် အက်ပ်အတွင်း စခရင်ရှော့(တ်)များကို ပိတ်ရန် Signal မက်ဆေ့ချ်များနှင့် ကောလ်များ၊ အမြဲတမ်း ထပ်ဆင့်ပို့ ကောလ်များနှင့် ချိပ်ပိတ်ပေးပို့သူ ချက်(တ်)အသစ်များအတွက် ပုံသေတိုင်မာ သင် စတင်ခဲ့သည့် ချက်(တ်)အသစ်များ အားလုံးအတွက် ပျောက်သွားမည့် မက်ဆေ့ချ် ပုံသေတိုင်မာကို သတ်မှတ်ပါ။ @@ -4066,9 +4137,9 @@ ခေါ်ဆိုမယ် - အသံတိတ်ထားမည် + အသံပိတ်ရန် - အသံပိတ်ထား + အသံပိတ်ပြီး ရှာဖွေမယ် ပျောက်ကွယ်မည့် မက်ဆေ့ချ်များ @@ -4076,10 +4147,10 @@ အဆက်အသွယ် အသေးစိတ် လုံခြုံရေးနံပါတ်ကိုကြည့်မယ် - ပိတ်ပယ်မယ် - အဖွဲ့ကိုပိတ်ပယ်မယ် - ပြန်ဖွင့်မယ် - အဖွဲ့ကိုပြန်ဖွင့်မယ် + ဘလော့ခ်ရန် + အဖွဲ့ကို ဘလော့ခ်ရန် + ဘလော့ခ်ဖြေရန် + အဖွဲ့ကို ဘလော့ခ်ဖြေရန် အဖွဲ့တစ်ဖွဲ့ထဲသို့ ထည့်မယ် အားလုံးကို ကြည့်မယ် အဖွဲ့ဝင်များ ထည့်ပါ @@ -4087,9 +4158,9 @@ တောင်းဆိုချက်များနှင့် ဖိတ်ခေါ်မှုများ Group link အဆက်အသွယ်အဖြစ် ပေါင်းထည့်ရန် - အသံပြန်ဖွင့်မယ် - %1$s အထိ စကားဝိုင်း အသိပေးသံကို ပိတ်ထားပါသည် - စကားဝိုင်း အသိပေးသံကို အမြဲပိတ်ထားပါသည် + အသံပြန်ဖွင့်ရန် + %1$s အထိ စကားဝိုင်း အသံပိတ်ထားပါသည် + စကားဝိုင်းကို အမြဲ အသံပိတ်ထားပါသည် ဖုန်းနံပါတ်ကို ကလစ်ဘုတ်တွင် ကူးယူပြီး။ ဖုန်းနံပါတ် Signal ကို ပံ့ပိုးခြင်းဖြင် သင့်ပရိုဖိုင်အတွက် ဘဲ့ဂျ်များကို ရယူပါ။ ပိုမိုလေ့လာရန် ဘဲ့ဂျ်တစ်ခုကို နှိပ်ပါ။ @@ -4105,8 +4176,8 @@ မည်သူတို့သည် မက်ဆေ့ချ်များ ပို့နိုင်မည်နည်း။ - အသိပေးချက်များကို ပိတ်ပါ - အသံမပိတ်ပါ + အသိပေးချက် အသံပိတ်ရန် + အသံ မပိတ်ထားပါ မန်းရှင်းများ အမြဲ အသိပေးရန် အသိမပေးရန် @@ -4135,7 +4206,7 @@ ဖယ်ရှားရန် - ဘလော့ လုပ်မည် + ဘလော့ခ်ရန် %1$s ကို ဖယ်ရှားမည်လား။ @@ -4143,7 +4214,7 @@ %1$s ကို ဖယ်ရှားထားပါသည် - %1$s အား ဘလော့ထားပါသည် + %1$s အား ဘလော့ခ်ပြီးပါပြီ %1$s ကို ဖယ်ရှား၍ မရနိုင်ပါ @@ -4405,7 +4476,7 @@ ကွန်ရက်ချို့ယွင်းချက်ကြောင့် သင့်လှူဒါန်းမှုကို မပေးပို့နိုင်ပါ။ ချိတ်ဆက်မှုစစ်ဆေးပြီး ထပ်ကြိုးစားကြည့်ပါ။ - %1$s သို့ လှူဒါန်းမှု + %1$s ကိုယ်စား လှူဒါန်းမှု %1$s သည် သင့်ကိုယ်စား Signal သို့ လှူဒါန်းထားပါသည် @@ -5476,5 +5547,15 @@ သုံးစွဲသူအမည်ကို ဖျက်ရန် + + + h + + m + + သတ်မှတ်ရန် + + စခရင်လော့ခ် မသုံးမီ အနည်းဆုံးကြာချိန်မှာ 1 မိနစ်ဖြစ်ပါသည်။ + diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 97d9e14d26..8d70f1411b 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -14,6 +14,7 @@ + Ja Nei @@ -98,9 +99,9 @@ Blokkerte brukere Legg til blokkert bruker - Blokkerte brukere kan ikke ringe eller sende deg meldinger. + Blokkerte brukere kan ikke ringe til deg eller sende deg meldinger. Ingen blokkerte brukere - Blokker bruker? + Vil du blokkere brukeren? \"%1$s\" vil ikke kunne ringe deg eller sende deg meldinger. Blokker @@ -138,8 +139,8 @@ Fortsett - Blokker og forlat %1$s? - Blokker %1$s? + Vil du blokkere og forlate %1$s? + Vil du blokkere %1$s? Du vil ikke lenger motta meldinger eller oppdateringer fra denne gruppen, og medlemmene vil ikke kunne legge deg til denne gruppen igjen. Gruppens medlemmer vil ikke kunne legge deg til denne gruppen igjen. Gruppemedlemmer kan legge deg til denne gruppen igjen. @@ -147,13 +148,13 @@ Du vil være i stand til å sende meldinger og ringe hverandre og navn og bilde ditt vil bli delt med dem. Dette fører til at dere kan sende meldinger til hverandre. - Blokkerte personer vil ikke kunne ringe deg eller sende deg meldinger. + Blokkerte personer vil ikke kunne ringe til deg eller sende deg meldinger. Blokkerte personer vil ikke kunne sende deg meldinger. Blokker nyheter og oppdateringer fra Signal. Få nyheter og oppdateringer fra Signal. - avblokker %1$s? + Vil du fjerne blokkeringen av %1$s? Blokker Blokker og forlat Rapporter søppelpost og blokkér. @@ -447,7 +448,7 @@ %1$s på - Blokkere forespørselen? + Vil du blokkere forespørselen? %1$s kan ikke bli med i eller be om å bli med i denne gruppen via gruppelenken. De kan fremdeles legges til i gruppen manuelt. @@ -496,12 +497,12 @@ Løsne - Lydløs - Lydløs + Dempet samtale + Dempede samtaler - Slå på lyd - Slå på lyd + Samtale med lyd slått på + Samtaler med lyd slått på Velg @@ -542,6 +543,15 @@ +%1$d + + Koble til enhetene dine på nytt + + Enhetene som du la til, ble koblet fra da du avregistrerte deg. Gå til innstillingene for å koble til enhetene på nytt. + + Åpne innstillingene + + Senere + Velg medlemmer @@ -935,7 +945,7 @@ Varsle meg om omtaler - Motta varsler når du blir nevnt i mutede samtaler? + Vil du motta varsler når du blir nevnt i dempede samtaler? Varsle meg alltid Ikke varsle meg @@ -953,6 +963,16 @@ Brukernavnet er lagret Brukernavnet er kopiert + + Kunne ikke slette brukernavnet. Prøv igjen senere. + + Brukernavnet er slettet + + + + Noe gikk galt med brukernavnet ditt. Det er ikke lenger knyttet til kontoen din. Du kan prøve å angi det på nytt, eller velge et annet brukernavn. + + Fiks nå @@ -1156,8 +1176,8 @@ Ny gruppe Inviter venner Bruk SMS - Utseende - Legg til et bilde + Samtalefarger + Legg til et profilbilde Svar @@ -1469,13 +1489,13 @@ Fortsett Slett Blokker - Skru av blokkering + Fjern blokkering La %1$s sende meldinger til deg, samt dele navn og bilde med dem? De vil ikke vite at du har sett meldingen deres før du godtar. - La %1$s sende meldinger til deg, samt dele navn og bilde med dem? Du vil ikke motta noen meldinger fra dem før du fjerner blokkeringen av dem. + Vil du at %1$s skal kunne sende deg meldinger, og vil du dele navn og bilde med vedkommende? Du mottar ikke noen meldinger før du fjerner blokkeringen av personen. - Vil du at %1$s kan sende deg meldinger? Du mottar ikke noen meldinger før du fjerner blokkeringen. - Vil du at %1$s kan sende deg oppdateringer og nyheter? Du mottar ikke noen oppdateringer før du fjerner blokkeringen. + Vil du at %1$s skal kunne sende deg meldinger? Du mottar ikke noen meldinger før du fjerner blokkeringen av personen. + Ønsker du å motta oppdateringer og nyheter fra %1$s? Du mottar ikke noen oppdateringer før du fjerner blokkeringen av personen. Fortsett denne samtalen med denne gruppen og del ditt navn og bilde med gruppens medlemmer? Oppgrader denne gruppen for å aktivere nye funksjoner som @-omtaler og gruppeadministratorer. Medlemmer som ikke har delt navn eller bilde i gruppen vil bli invitert inn. Denne Legacy gruppen kan ikke lenger benyttes fordi den er for stor. Maksimal gruppestørrelse er %1$d. @@ -1483,7 +1503,7 @@ Bli medlem i denne gruppen samt dele navn og bilde med medlemmene? De vil ikke vite at du har sett meldingene før du godtar. Vil du bli medlem av denne gruppen og dele navnet og bildet ditt med den? Du ser ikke gruppemeldingene før du godtar invitasjonen. Bli med i denne gruppen? De vil ikke se om du har sett meldingene deres før du godtar. - Fjerne blokkering av denne gruppen samt dele navn og bilde med medlemmene? Du vil ikke motta noen meldinger før du fjerner blokkeringen av den. + Vil du fjerne blokkering av denne gruppen samt dele navn og bilde med medlemmene? Du mottar ikke noen meldinger før du fjerner blokkeringen av gruppen. Vis Medlem av %1$s @@ -1584,9 +1604,20 @@ Opprett ny PIN-kode + + Send kode på SMS + + Signal-registrering - Trenger hjelp til å registrere PIN-kode for Android + + PIN-koden din er en kode på minst %1$d tegn som du har valgt selv.\n\nHvis du ikke husker PIN-koden din, kan du opprette en ny. + + Hvis du ikke husker PIN-koden din, kan du opprette en ny. + + Du har gått tom for forsøk på å skrive inn PIN-koden, men du kan få tilgang til Signal-kontoen din ved å lage en ny kode. + Advarsel - Hvis du deaktiverer PIN-koden vil du miste all data når du gjennomfører Signal-registrering på nytt, med mindre du manuelt tar en sikkerhetskopi og gjenoppretter innholdet. Du kan ikke skru på registreringslås mens PIN-koden er deaktivert. + Hvis du deaktiverer PIN-koden mister du alle data når du gjennomfører Signal-registreringen på nytt, med mindre du manuelt tar en sikkerhetskopi og gjenoppretter innholdet. Du kan ikke slå på registreringslåsen mens PIN-koden er deaktivert. Deaktiver PIN-kode @@ -1713,7 +1744,7 @@ Slå lyd på - Demping + Demp Ring @@ -1731,7 +1762,7 @@ Du vil ikke motta deres lyd eller bilde og de vil ikke motta tilsvarende fra deg. Kan ikke motta lyd & video fra %1$s Kan ikke motta lyd og video fra %1$s - Dette kan være fordi de ikke har verifisert ditt endrede sikkerhetsnummer, fordi det er et problem med enheten deres eller de har blokkert deg. + Dette kan være fordi vedkommende ikke har verifisert det endrede sikkerhetsnummeret ditt, det er et problem med personens enhet eller hen har blokkert deg. Sveip for å se skjermdelingen @@ -1768,11 +1799,18 @@ Signal trenger kontaktene og medietillatelsene for å hjelpe deg med å få kontakt med venner og sende meldinger. Kontaktene dine lastes opp ved hjelp av Signal private kontaktoppdagelse, noe som betyr at de er ende-til-ende-krypterte og aldri synlige for Signal-tjenesten. Signal trenger kontakttillatelsen for å hjelpe deg med å få kontakt med venner. Kontaktene dine lastes opp ved hjelp av Signal private kontaktoppdagelse, noe som betyr at de er ende-til-ende-krypterte og aldri synlige for Signal-tjenesten. Du har gjort for mange forsøk på å registrere dette nummeret. Prøv igjen senere. + + Du har gjort for mange forsøk på å registrere dette nummeret. Prøv igjen om %1$s minutter. Klarte ikke å koble til tjenesten. Kontroller at du har en stabil nettilkobling og prøv på nytt. Ikke-standard tallformat Tallet du skrev inn (%1$s), ser ut til å være skrevet i et ikke-standard tallformat.\n\nMente du %2$s? Molly Android – format for telefonnummer + Anrop forespurt + + SMS forespurt + + Bekreftelseskode forespurt Du er nå %1$d steg unna fra å sende inn en feilsøkingslogg. Du er nå %1$d steg unna fra å sende inn en feilsøkingslogg. @@ -1792,6 +1830,16 @@ Ring Bekreftelseskode Send koden på nytt + + Har du problemer med å registrere deg? + + • Pass på at telefonen din har dekning, slik at du kan motta koden på SMS eller via anrop\n • Sørg for at du kan motta anrop til telefonnummeret\n • Dobbeltsjekk at du har skrevet inn riktig telefonnummer. + + For å få mer informasjon kan du følge disse feilsøkingstrinnene eller kontakte brukerstøtten vår + + disse feilsøkingstrinnene + + Kontakt brukerstøtte Aktiver registreringslås? @@ -1958,6 +2006,10 @@ Betaling Planlagt melding + + Meldingsloggene har blitt slått sammen + + %1$s tilhører %2$s Molly oppdatering @@ -2087,14 +2139,16 @@ Usikret SMS %1$s %2$s Kontakt - Reagerte %1$s til \"%2$s\". - Reagerte %1$s til videoen din. - Reagerte %1$s til bildet ditt. + Reagerte med %1$s på «%2$s». + Reagerte med %1$s på videoen din. + Reagerte med %1$s på bildet ditt. Reagerte med %1$s på GIF-en din. - Reagerte %1$s på filen din. - Reagerte %1$s på lyden din. - Reagerte %1$spå engangsmediet ditt. - Reagerte %1$s på klistremerket ditt. + Reagerte med %1$s på filen din. + Reagerte med %1$s på lyden din. + Reagerte med %1$s på vis-én-gang-mediefilen din. + + Reagerte med %1$s på betalingen din. + Reagerte med %1$s på klistremerket ditt. Denne meldingen ble slettet. Skru av varsling når kontakter tar i bruk Signal? Du kan skru de på igjen i Signal> Innstillinger > Varsler. @@ -2487,8 +2541,8 @@ Problemer med levering - En melding, et klistremerke, en reaksjon, en lesebekreftelse eller et medium kunne ikke leveres til deg fra %1$s. Vedkommende kan ha prøvd å sende elementet til deg direkte eller i en gruppe. - En melding, et klistremerke, en reaksjon, en lesebekreftelse eller et medium fra %1$s kunne ikke leveres til deg. + En melding, et klistremerke, en reaksjon eller en lesebekreftelse fra %1$s kunne ikke leveres til deg. Vedkommende kan ha prøvd å sende dette til deg direkte eller i en gruppe. + En melding, et klistremerke, en reaksjon eller en lesebekreftelse fra %1$s kunne ikke leveres til deg. Fornavn (påkrevd) @@ -2675,9 +2729,9 @@ Benytt adressebokbilder Vis adressebokbilder hvis tilgjengelig - Hold lydløse samtaler arkivert + Hold dempede samtaler arkivert - Samtaler som er satt på lydløs og arkiverte vil forbli arkiverte selv når du får en ny melding. + Dempede samtaler som er arkiverte vil forbli arkiverte selv når du får en ny melding. Forhåndsvis nettsteder Hent forhåndsvisninger av lenker direkte fra nettsteder for meldinger du sender. Endre passordfrase @@ -2971,7 +3025,7 @@ Sendt betaling Mottatt betaling Betaling fullført %1$s - Blokker nummeret + Blokkeringsnummer Overfør @@ -3056,7 +3110,7 @@ Ny melding til… - Blokker bruker + Blokker brukeren Legg til gruppen @@ -3128,10 +3182,10 @@ - Slå lyd på + Vis - Skru av varsler + Ikke vis varsler Gruppeinstillinger @@ -3295,6 +3349,8 @@ Skriv inn PIN-koden din Skriv inn PIN-koden du opprettet for kontoen din. Dette er forskjellig fra SMS-bekreftelseskoden. + + Angi PIN-koden som er knyttet til kontoen din. Skriv inn alfanumerisk PIN Skriv inn numerisk PIN Feil PIN-kode. Prøv igjen. @@ -3398,7 +3454,10 @@ Sikkerhetskopien inneholder en veldig stor fil som ikke kunne kopieres. Slett filen og opprett en ny sikkerhetskopi. Trykk for å håndtere sikkerhetskopier. Feil nummer? + Ring meg (%1$02d:%2$02d) + + Send koden på nytt (%1$02d:%2$02d) Kontakt Signal brukerstøtte Signal Registrering - Verifikasjonskode for Android Feil kode @@ -3406,6 +3465,18 @@ Ukjent Se mitt telefonnummer Finn meg via telefonnummer + + Telefonnummer + + Velg hvem som kan se telefonnummeret ditt og bruke det til å komme i kontakt med deg på Molly. + + Hvem kan se telefonnummeret mitt? + + Ingen kan se telefonnummeret ditt på Molly + + Hvem kan finne meg ved å søke opp telefonnummeret mitt? + + Telefonnummeret ditt vil være synlig for personer og grupper du sender meldinger til. Personer som har nummeret ditt i kontaktlisten på telefonen sin, vil også kunne se det på Molly. Alle Mine kontakter Ingen @@ -3563,7 +3634,7 @@ Blokker - Skru av blokkering + Fjern blokkering Legg til i kontakter Finner ingen apper som kontaktene kan åpnes i. @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" har blitt blokkert. - Klarte ikke blokkere \"%1$s\" - \"%1$s\" har blitt avblokkert. + «%1$s» har blitt blokkert. + Kunne ikke blokkere «%1$s» + Blokkeringen av «%1$s» er fjernet. Gå gjennom medlemmer @@ -3670,8 +3741,8 @@ Sletting av kontoen din vil: Skriv inn ditt telefonnummer Slett kontoen - Slett kontoinformasjonen din og profilbildet ditt - Slett alle meldingene dine + slette kontoinformasjonen og profilbildet ditt + slette alle meldingene dine Slett %1$s fra betalingskontoen Ingen landskode angitt Ikke noe nummer angitt @@ -3784,12 +3855,12 @@ Deaktiver lommeboken Din balanse - Vi anbefaler deg å overføre pengene til en annen lommebokadresse før du deaktiverer betalingene. Hvis du velger å ikke overføre pengene dine nå, forblir de i lommeboken som er knyttet til Molly hvis du aktiverer betalinger på nytt. + Vi anbefaler deg å overføre pengene til en annen lommebokadresse før du deaktiverer betalinger. Hvis du velger å ikke overføre pengene nå, forblir de i lommeboken som er knyttet til Molly, hvis du aktiverer betalinger på nytt. Overfør gjenværende saldo Deaktiver uten å overføre Deaktiver Vil du deaktivere uten å overføre? - Kontosaldoen din vil holde seg i lommeboken du har koblet til Molly, hvis du ønsker å reaktivere betalingene. + Saldoen din forblir i lommeboken som er knyttet til Molly, hvis du velger å aktivere betalinger på nytt. Feil ved deaktivering av lommeboken. @@ -4008,7 +4079,7 @@ Meldinger Tidsavgrensede meldinger App-sikkerhet - Blokker skjermbilder i sist brukte-liste og i programmet + Blokker skjermbilder i sist brukte-listen og inne i appen Signal-meldinger og samtaler, omdiriger alle samtaler, og forseglet avsender Standard nedtelling for nye samtaler Sett standard utløpstid for tidsavgrensede meldinger, for nye samtaler startet av deg. @@ -4106,7 +4177,7 @@ Ikke nå - Tilpasse reaksjonene + Tilpass reaksjonene Trykk for å erstatte en emoji Tilbakestill Lagre @@ -4165,9 +4236,9 @@ Ring - Demping + Ikke vis - Dempet + Vis Søk Tidsavgrensede meldinger @@ -4177,7 +4248,7 @@ Vis sikkerhetsnummer Blokker Blokker gruppen - Skru av blokkering + Fjern blokkering Fjern blokkering av gruppen Legg til gruppen Se alle @@ -4187,8 +4258,8 @@ Gruppelenke Legg til som ny kontakt Slå lyd på - Samtalen er satt på lydløs frem til %1$s - Samtalen er satt på lydløs for alltid + Samtalen er dempet frem til %1$s + Samtalen er dempet for alltid Telefonnummeret er kopiert til utklippstavlen. Telefonnummer Få merker på profilen din ved å støtte Signal. Trykk på et merke for å lese mer. @@ -4204,8 +4275,8 @@ Hvem kan sende meldinger? - Skru av varslinger - Ikke i stillemodus + Ikke vis varsler + Vises Omtaler Send alltid varsel Ikke send varsel @@ -4508,7 +4579,7 @@ Pengegaven kunne ikke sendes på grunn av en nettverksfeil. Sjekk internettilkoblingen og prøv igjen. - Pengegave til %1$s + Pengegave på vegne av %1$s %1$s ga penger til Signal på dine vegne @@ -4909,7 +4980,7 @@ Tillat svar og reaksjoner - Gjør det mulig for de som kan se storyen din å reagere og svare + Gjør det mulig for de som kan se storyen din, å reagere og svare Signal-kontakter @@ -5601,5 +5672,15 @@ Slett brukernavnet + + + t + + min + + Angi + + Minimumstiden før skjermen låses er 1 minutt. + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 96d12fe5d6..fb709d6fd6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -14,6 +14,7 @@ + Ja Nee @@ -96,10 +97,10 @@ Aan het nagaan of er nieuwe berichten zijn … - Geblokkeerde personen & groepen - Contactpersoon aan blokkeerlijst toevoegen - Tik hier om handmatig een persoon toe te voegen aan de lijst van geblokkeerde personen. — Geblokkeerde personen kunnen niet zien dat ze geblokkeerd zijn: het lijkt voor hen alsof je telefoon over gaat, en alsof berichten worden afgeleverd. Je huidige profielnaam en -foto blijven voor geblokkeerde personen zichtbaar maar worden niet bijgewerkt als je een nieuwe foto of naam instelt. Geblokkeerde personen kunnen in groepsgesprekken de berichten die jij stuurt niet zien. - Je hebt geen personen of groepen geblokkeerd + Geblokkeerde personen + Geblokkeerde persoon toevoegen + Geblokkeerde personen kunnen je niet bellen en geen berichten sturen. + Je hebt geen personen geblokkeerd Persoon blokkeren? “%1$s” kan jou nog steeds bellen of berichten sturen en weet dus niet dat hij of zijn geblokkeerd is, maar je zult die oproepen en berichten niet zien. Blokkeren @@ -147,10 +148,10 @@ Jullie zullen weer berichten naar elkaar kunnen verzenden, kunnen bellen, en je profielnaam, -foto en -omschrijving zullen voor hem of haar weer worden bijgewerkt. Jullie zullen weer sms-berichten aan elkaar kunnen verzenden via Signal. - Geblokkeerde personen kunnen niet zien dat ze geblokkeerd zijn: het lijkt voor hen alsof je telefoon over gaat en alsof berichten worden afgeleverd. Je huidige profielnaam en -foto blijven voor geblokkeerde personen zichtbaar maar worden ze niet bijgewerkt als je een nieuwe foto of naam instelt. Geblokkeerde personen kunnen in groepsgesprekken de berichten die jij stuurt niet zien. - Geblokkeerde personen kunnen niet zien dat ze geblokkeerd zijn: het lijkt voor hen alsof sms-berichten worden afgeleverd. + Geblokkeerde mensen kunnen je niet bellen of je berichten sturen. + Geblokkeerde personen kunnen je niet bellen en geen berichten sturen. - Nieuwsberichten over Signal blokkeren. + Updates en nieuws over Signal blokkeren. Voortaan weer nieuws over Signal ontvangen. %1$s deblokkeren? @@ -447,11 +448,11 @@ %1$s ingeschakeld - Verzoeken blokkeren? + Verzoek blokkeren? %1$s zal niet langer via de groepslink lid kunnen worden van deze groep en ook niet kunnen verzoeken om lid te worden van de groep. Hij of zij kan dan nog wel lid worden via een uitnodiging. - Verzoeken blokkeren + Verzoek blokkeren Annuleren @@ -488,20 +489,20 @@ Ongelezen - Vast­prikken - Vast­prikken + Vastprikken + Vastprikken Losmaken Losmaken - Meldingen dempen - Meldingen dempen + Dempen + Dempen - Meldingen niet langer dempen - Meldingen niet langer dempen + Niet langer dempen + Niet langer dempen Selecteren @@ -509,12 +510,12 @@ Archiveren - Dearchi­veren - Dearchi­veren + Dearchiveren + Dearchiveren - Wissen - Wissen + Verwijderen + Verwijderen Alles selecteren @@ -542,6 +543,15 @@ +%1$d + + Koppel je apparaten opnieuw + + De apparaten die je hebt toegevoegd, zijn ontkoppeld toen je de registratie van je apparaat ongedaan maakte. Ga naar Instellingen om apparaten opnieuw te koppelen. + + Instellingen openen + + Later + Kies leden @@ -935,7 +945,7 @@ Meldingen bij naamsvermeldingen - Wil je toch meldingen ontvangen wanneer iemand je naam in een gedempt gesprek vermeld? + Wil je toch meldingen ontvangen als iemand je naam in een gedempt gesprek vermeldt? Geef me altijd een melding Geen meldingen als gedempt @@ -953,6 +963,16 @@ Gebruikersnaam gecreëerd. Gebruikersnaam gekopieerd. + + Kan gebruikersnaam niet verwijderen. Probeer het later opnieuw. + + Gebruikersnaam verwijderd + + + + Er is iets misgegaan met je gebruikersnaam, deze is niet meer aan je account toegewezen. Je kunt het opnieuw proberen in te stellen of een nieuwe kiezen. + + Nu regelen @@ -1156,8 +1176,8 @@ Nieuwe-stijl groep Kennissen uitnodigen Sms gebruiken - Uiterlijk - Voeg een foto toe + Gesprekskleur + Voeg een profielfoto toe Reacties @@ -1472,10 +1492,10 @@ Deblokkeren Wil je berichten van %1$s ontvangen, en sta je toe dat hij of zij je profielnaam, -foto en -omschrijving kan zien? Als je leesbevestigingen hebt ingeschakeld kan hij of zij die nog niet zien totdat je de uitnodiging hebt aanvaard. - Wil je berichten van %1$s ontvangen, en sta je toe dat hij of zij je profielnaam, -foto en -omschrijving kan zien? Totdat je hem of haar hebt gedeblokkeerd zul je geen berichten of oproepen ontvangen en kan hij of zij veranderingen van je profiel niet zien. + Wil je berichten van %1$s ontvangen en sta je toe dat diegene je profielnaam en -foto kan zien? Totdat je dit contact hebt gedeblokkeerd, zul je geen berichten ontvangen. - Wil je berichten van %1$s ontvangen? Totdat je hem of haar hebt gedeblokkeerd zul je geen sms-berichten ontvangen. - Wil je nieuwsberichten van %1$s ontvangen? Je zult geen berichten ontvangen totdat je hen hebt gedeblokkeerd. + Wil je berichten van %1$s ontvangen? Totdat je diegene hebt gedeblokkeerd, zul je geen berichten ontvangen. + Wil je updates en nieuws van %1$s ontvangen? Je zult geen berichten ontvangen totdat je diegene hebt gedeblokkeerd. Wil je je profielnaam, -foto en -omschrijving voor deze groep zichtbaar maken om je gesprek met de groep voort te kunnen zetten? Zet deze groep om om nieuwe functionaliteiten te kunnen gebruiken zoals @vermeldingen en beheerders. Groepsleden die hun profielnaam, -foto en -omschrijving niet voor deze groep zichtbaar hebben gemaakt, zullen worden uitgenodigd om tot deze groep toe te treden. Deze verouderde groep kan niet langer gebruikt worden omdat hij teveel leden heeft. Het maximum ledenaantal voor een groep is %1$d. @@ -1483,7 +1503,7 @@ Wil je lid worden van deze groep, en sta je toe dat alle leden van de groep je profielnaam, -foto en -omschrijving kunnen zien? Als je leesbevestigingen hebt ingeschakeld kunnen de leden van deze groep die nog niet zien totdat je de uitnodiging hebt aanvaard. Wil je lid worden van deze groep, en sta je toe dat alle leden van de groep je profielnaam, -foto en -omschrijving kunnen zien? Je kunt hun berichten nog niet zien totdat je de uitnodiging hebt aanvaard. Je bent door iemand met wie je eerder een gesprek hebt gehad of met wie je in dezelfde groep zat toegevoegd aan deze groep. Wil je lid blijven van deze groep? Als je leesbevestigingen hebt ingeschakeld kunnen de leden van deze groep die nog niet zien totdat je de uitnodiging hebt aanvaard. - Wil je weer berichten van deze groep ontvangen, en sta je toe dat alle leden van de groep je profielnaam, -foto en -omschrijving kunnen zien? Totdat je de groep hebt gedeblokkeerd zul je geen berichten of oproepen ontvangen en kunnen leden van deze groep veranderingen van je profiel niet zien. + Wil je weer berichten van deze groep ontvangen en sta je toe dat alle leden je profielnaam en -foto kunnen zien? Totdat je de groep hebt gedeblokkeerd, zul je geen berichten ontvangen. Weergeven Lid van %1$s @@ -1584,9 +1604,20 @@ Nieuwe pincode aanmaken + + Sms-code versturen + + Signal-registratie - Hulp nodig bij het opnieuw registreren van de pincode voor Android + + Je pincode is een %1$d+ cijferige code die numeriek of alfanumeriek kan zijn.\n\nAls je je pincode niet meer weet, kun je een nieuwe aanmaken. + + Als je je pincode niet meer weet, kun je een nieuwe aanmaken. + + Je hebt geen pincodes meer, maar je hebt nog steeds toegang tot je Signal-account als je een nieuwe pincode aanmaakt. + Waarschuwing - Als je de pincode uitschakelt zul je wanneer je Signal opnieuw registreert alle gegevens kwijtraken, tenzij je ze handmatig herstelt vanuit een back-upbestand. Daarnaast kun je zolang de pincode is uitgeschakeld de registratievergrendeling niet inschakelen. + Als je de pincode uitzet, verlies je alle gegevens als je Signal opnieuw registreert, tenzij je handmatig een back-up maakt en herstelt. Je kunt Registratievergrendeling niet aanzetten terwijl de pincode uitstaat. Pincode uitschakelen @@ -1711,7 +1742,7 @@ Camera - Microfoon niet langer dempen + Niet langer dempen Dempen @@ -1731,7 +1762,7 @@ Je zult hen niet kunnen zien of horen en zij zullen jou niet kunnen zien of horen. Kan geen beeld & geluid van %1$s ontvangen Kan geen beeld en geluid van %1$s ontvangen - Dit kan gebeuren als je jullie veiligheidsnummer niet opnieuw hebt geverifieerd nadat die is veranderd, als er een probleem is met je apparaat of als hij of zij jou heeft geblokkeerd. + Dit kan zijn omdat diegene de wijziging van het veiligheidsnummer niet heeft geverifieerd, er een probleem is met hun apparaat, of als ze jou hebben geblokkeerd. Veeg om gedeeld scherm weer te geven @@ -1768,11 +1799,18 @@ Signal zal een aantal toestemmingen vragen: 1. Je contactenlijst lezen om personen weer te geven naar wie je berichten kunt verzenden. Je contactenlijst blijft altijd onleesbaar voor Signals servers. 2. Bestanden lezen van en schrijven naar de externe opslag, om bestanden bij te voegen als bijlagen en om ontvangen bijlagen op te kunnen slaan. 3. De telefoonstatus lezen om te voorkomen dat Signal-oproepen je andere oproepen verstoren. Signal moet je contactenlijst lezen om personen weer te geven naar wie je berichten kunt sturen en met wie je oproepen kunt beginnen. Je contactenlijst blijft altijd onleesbaar voor Signals servers. Je hebt te veel pogingen ondernomen om Signal voor dit telefoonnummer te registreren. Probeer het later opnieuw. + + Je hebt te vaak geprobeerd dit nummer te registreren. Probeer het opnieuw over %1$s. Het lukt niet om te verbinden met Signals servers. Ga na dat je apparaat met het internet is verbonden en probeer het opnieuw. Afwijkend telefoonnummerformaat Het door jou ingevulde telefoonnummer (%1$s) heeft een afwijkend formaat.\n\nBedoelde je %2$s? Molly Android - Telefoonnummerformaat + Belverzoek ingediend + + Sms aangevraagd + + Verificatiecode aangevraagd Je bent nu %1$d stap verwijderd van het indienen van een foutopsporingslog. Je bent nu %1$d stappen verwijderd van het indienen van een foutopsporingslog. @@ -1792,6 +1830,16 @@ Bel me Verificatiecode Stuur code opnieuw + + Problemen met registreren? + + • Zorg ervoor dat je telefoon een mobiel signaal heeft om je sms of oproep te ontvangen\n • Bevestig dat je een oproep naar nummer kunt ontvangen\n • Controleer of je je telefoonnummer correct hebt ingevoerd. + + Volg voor meer informatie deze stappen voor probleemoplossing of neem contact op met ons supportteam + + deze stappen voor probleemoplossing + + Neem contact op met ons supportteam Registratievergrendeling inschakelen? @@ -1953,11 +2001,15 @@ Heeft %1$s op je verhaal gereageerd - Heeft %1$s op hun verhaal gereageerd + Hebt %1$s op hun verhaal gereageerd Overschrijving Gepland bericht + + Je berichtengeschiedenis is samengevoegd + + %1$s is van %2$s Nieuwe versie van Molly @@ -2087,14 +2139,16 @@ Onbeveiligde sms %1$s %2$s Contactpersoon - reageerde met %1$s op “%2$s”. - reageerde met %1$s op je video. - reageerde met %1$s op je afbeelding. + Reageerde met %1$s op “%2$s”. + Reageerde met %1$s op je video. + Reageerde met %1$s op je afbeelding. Reageerde met %1$s op je GIF. - reageerde met %1$s op je bestand. - reageerde met %1$s op je audio. - reageerde met %1$s op je eenmaligeweergave-media. - reageerde met %1$s op je sticker. + Reageerde met %1$s op je bestand. + Reageerde met %1$s op je audio. + Reageerde met %1$s op je eenmaligeweergave-media. + + Reageerde met %1$s op je betaling. + Reageerde met %1$s op je sticker. Dit bericht is gewist. Zet meldingen uit over mensen die bereikbaar zijn via Signal. Je kunt ze opnieuw aanzetten in Signal > Instellingen > Meldingen. @@ -2298,7 +2352,7 @@ Oproepmeldingen inschakelen Contactpersoon aanpassen - Verzoeken van deze persoon blokkeren + Verzoek blokkeren Deze persoon is een onbekende, je zit zelfs niet in een groepsgesprek met deze persoon. Overweeg daarom zorgvuldig of je dit gespreksverzoek wilt aanvaarden. In deze groep zit niemand met wie je eerder een gesprek hebt gevoerd, overweeg daarom zorgvuldig of je wel lid wilt worden. Weergeven @@ -2555,7 +2609,7 @@ In afwachting - Verzonden aan + Verstuurd naar Verzonden door Afgeleverd aan Gelezen door @@ -2677,7 +2731,7 @@ Gedempte gesprekken gearchiveerd houden - Gedempte gesprekken die zijn gearchiveerd blijven gearchiveerd als er een nieuw bericht binnenkomt. + Gedempte gesprekken die zijn gearchiveerd, blijven gearchiveerd als er een nieuw bericht binnenkomt. Voorbeeld van website meesturen Door dit in te schakelen wordt voor elke link naar een website die je verzendt een voorbeeldafbeelding gegenereerd en met je bericht meegestuurd. Een nadeel is wel dat als je dit inschakelt websites zouden kunnen detecteren dat je hen in je bericht vermeldt en ook welke pagina je vermeldt. Wachtwoord wijzigen @@ -2971,7 +3025,7 @@ Overschrijving verzenden Ontvangen overschrijving Overschrijving voltooid %1$s - Bloknummer + Nummer blokkeren Overschrijven @@ -3128,7 +3182,7 @@ - Meldingen niet langer dempen + Niet langer dempen Meldingen dempen @@ -3295,6 +3349,8 @@ Voer je pincode in Voer de pincode in welke je eerder hebt aangemaakt om informatie versleuteld op te slaan op Signals servers. Dit is niet dezelfde code als de telefoonnummerverificatie-code die je per sms of telefoonoproep hebt ontvangen. + + Voer de pincode in die je voor je account hebt gemaakt. Een alfanumerieke pincode invoeren Een numerieke pincode invoeren Foutieve pincode, probeer het opnieuw. @@ -3398,7 +3454,10 @@ Je back-up bevat een erg groot bestand dat niet opgeslagen kan worden. Verwijder het en probeer het opnieuw. Tik hier om het maken van back-upbestanden in te stellen. Verkeerd nummer? + Bel me (%1$02d:%2$02d) + + Code opnieuw versturen (%1$02d:%2$02d) Contact opnemen met Signal-ondersteuning Signal-registratie - Verificatiecode voor Android Onjuiste code @@ -3406,6 +3465,18 @@ Onbekend Mijn telefoonnummer zien Me vinden via mijn telefoonnummer + + Telefoonnummer + + Kies wie je telefoonnummer kan zien en wie ermee contact met je kan opnemen op Molly. + + Wie kan mijn nummer zien + + Niemand zal jouw telefoonnummer zien op Molly + + Wie kan mij vinden via mijn nummer + + Je telefoonnummer zal zichtbaar zijn voor iedereen en elke groep waar je een bericht naar stuurt. Mensen die jouw telefoonnummer hebben in hun contactenlijst zullen je ook zien op Molly. Iedereen Mijn contactpersonen Niemand @@ -3615,9 +3686,9 @@ %1$s/%2$s - “%1$s” is geblokkeerd. - “%1$s” blokkeren is mislukt. - “%1$s” is gedeblokkeerd. + %1$s is geblokkeerd. + Kon %1$s niet blokkeren + %1$s is gedeblokkeerd. Groepsleden vergelijken @@ -3670,14 +3741,14 @@ Als je al je gegevens wist: Voer je telefoonnummer in Account verwijderen - Verwijder je accountinformatie en profielfoto + Verwijder je je accountinformatie en profielfoto Wis al je berichten %1$s wissen van je overschrijvingsaccount Er is geen landcode gespecificeerd Er is geen telefoonnummer ingevuld Het telefoonnummer dat je hebt ingevuld komt niet overeen met het telefoonnummer waarmee je staat geregistreerd bij Signal. Weet je zeker dat je je account wilt verwijderen? - Hiermee verwijder je je Signal-account en reset je de applicatie. De app zal zichzelf sluiten nadat je dit doet. + Hiermee verwijder je je Signal-account en reset je de app. De app wordt vanzelf afgesloten. Het wissen van lokale gegevens is mislukt. Je kunt de lokale gegevens zelf wissen via het \'apps\'-menu in de instellingenapp van je besturingssysteem. App-instellingen openen @@ -3789,7 +3860,7 @@ Uitschakelen zonder overschrijven Uitschakelen Uitschakelen zonder overschrijven? - Je krediet is wanneer je overschrijvingen inschakelt weer beschikbaar in de portemonnee die aan Molly is gekoppeld. + Je krediet blijft beschikbaar in de portemonnee die aan Molly is gekoppeld, zodra je overschrijvingen inschakelt. Fout bij het uitschakelen van je portemonnee @@ -4008,7 +4079,7 @@ Berichten Verlopende berichten App-beveiliging - Verberg Signal in de lijst van recent geopende apps en blokkeer schermafdrukken op je eigen apparaat, om je gesprekken te beschermen tegen stiekeme schermopnames vanuit spionerende apps. + Screenshots in de lijst van recent geopende apps en in de app blokkeren Signal-berichten en -oproepen, alle omroepen omleiden en verzegelde afzender Standaard-tijdspanne voor toekomstige gesprekken Stel in dat berichten in alle toekomstige gesprekken die jij zelf begint een bepaalde tijd nadat ze gezien zijn voor zowel jou als je gesprekspartner vanzelf worden gewist. @@ -4165,9 +4236,9 @@ Bellen - Meldingen dempen + Dempen - Meldingen niet langer dempen + Niet langer dempen Zoeken Verlopende berichten @@ -4176,7 +4247,7 @@ In systeemcontactenlijst weergeven Veiligheidsnummer weergeven Blokkeren - Groep blokkeren en verlaten + Groep blokkeren Deblokkeren Groep deblokkeren Aan een groep toevoegen @@ -4186,9 +4257,9 @@ Verzoeken & uitnodiging Groepslink Als contactpersoon toevoegen - Meldingen niet langer dempen - Je heb dit gesprek in het verleden tot %1$s gedempt - Je hebt dit gesprek in het verleden voor onbepaalde tijd gedempt + Niet langer dempen + Gesprek gedempt tot %1$s + Gesprek gedempt voor onbepaalde tijd Telefoonnummer naar klembord gekopieerd. Telefoonnummer Door Signal te ondersteunen verdien je badges om op je profiel weer te geven. Tik op een badge om er meer over te leren. @@ -4330,7 +4401,7 @@ Een verhaal toevoegen Berichttekst toevoegen Een reactie toevoegen - Verzonden aan + Versturen naar Eenmaligeweergave-bericht Een of meerdere items zijn te groot Een of meerdere items zijn ongeldig @@ -4465,7 +4536,7 @@ Je maandelijkse donaties zijn stopgezet omdat Signal je betaling niet kon verwerken. Je badge is niet langer op je profiel zichtbaar. Je terugkerende maandelijkse donatie werd geannuleerd. %1$s Je %2$s badge is niet langer zichtbaar op je profiel. - Je kunt Signal gewoon blijven gebruiken, maar als je Signal wilt ondersteunen en je badge weer wilt weergeven dan kun je nu je donaties vernieuwen. + Je kunt Signal gewoon blijven gebruiken, maar als je Signal wilt ondersteunen en je badge weer wilt weergeven dan moet je je donaties vernieuwen. Maandelijkse donaties vernieuwen Google Pay openen @@ -4508,7 +4579,7 @@ Je donatie kon niet worden verstuurd vanwege een netwerkfout. Ga na dat je apparaat met het internet is verbonden en probeer het opnieuw. - Donatie aan %1$s + Donatie namens %1$s %1$s heeft namens jou gedoneerd aan Signal @@ -4859,7 +4930,7 @@ Reacties - Op dit verhaal reageren + Reageer op dit verhaal Één-op-één-reactie aan het schrijven naar %1$s @@ -5087,7 +5158,7 @@ Donatie bevestigen - Verzonden aan + Versturen naar De ontvanger wordt in een persoonlijk bericht op de hoogte gebracht van de donatie. Voeg hieronder je eigen bericht toe. @@ -5601,5 +5672,15 @@ Gebruikersnaam verwijderen + + + u + + m + + Instellen + + Je scherm wordt na 1 minuut vergrendeld. + diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index d04e2543a6..0f33362f13 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -14,13 +14,14 @@ + ਹਾਂ ਨਹੀਂ ਮਿਟਾਓ ਉਡੀਕੋ… ਸੰਭਾਲੋ - ਖੁਦ ਲਈ ਨੋਟ ਕਰੋ + ਖੁਦ ਲਈ ਨੋਟ @@ -96,13 +97,13 @@ ਸੁਨੇਹਿਆਂ ਦੀ ਜਾਂਚ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ… - ਪਾਬੰਦੀ ਲਾਏ ਵਰਤੋਂਕਾਰ - ਪਾਬੰਦੀ ਲਾਇਆ ਵਰਤੋਂਕਾਰ ਜੋੜੋ + ਪਾਬੰਦੀਸ਼ੁਦਾ ਵਰਤੋਂਕਾਰ + ਪਾਬੰਦੀਸ਼ੁਦਾ ਵਰਤੋਂਕਾਰ ਸ਼ਾਮਲ ਕਰੋ ਪਾਬੰਦੀਸ਼ੁਦਾ ਵਰਤੋਂਕਾਰ ਤੁਹਾਨੂੰ ਕਾਲ ਨਹੀਂ ਕਰ ਸਕਣਗੇ ਜਾਂ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਣਗੇ। - ਕੋਈ ਪਾਬੰਦੀਸ਼ੁਦਾ ਵਰਤੋਂਕਾਰ ਨਹੀਂ - ਵਰਤੋਂਕਾਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ ਹੈ? + ਕੋਈ ਵੀ ਪਾਬੰਦੀਸ਼ੁਦਾ ਵਰਤੋਂਕਾਰ ਮੌਜੂਦ ਨਹੀਂ ਹੈ + ਕੀ ਵਰਤੋਂਕਾਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਉਣੀ ਹੈ? \"%1$s\" ਤੁਹਾਨੂੰ ਕਾਲ ਨਹੀਂ ਕਰ ਸਕਣਗੇ ਜਾਂ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਣਗੇ। - ਪਾਬੰਦੀ ਲਾਓ + ਪਾਬੰਦੀ ਲਗਾਓ @@ -138,8 +139,8 @@ ਜਾਰੀ ਰੱਖੋ - %1$s ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ ਅਤੇ ਛੱਡਣਾ ਹੈ? - %1$s ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣੀ ਹੈ? + ਕੀ %1$s ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਉਣੀ ਹੈ ਅਤੇ ਉਸਨੂੰ ਛੱਡਣਾ ਹੈ? + ਕੀ %1$s ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਉਣੀ ਹੈ? ਤੁਹਾਨੂੰ ਇਸ ਗਰੁੱਪ ਤੋਂ ਹੁਣ ਕੋਈ ਸੁਨੇਹੇ ਜਾਂ ਅੱਪਡੇਟ ਨਹੀਂ ਆਉਣਗੇ, ਅਤੇ ਗਰੁੱਪ ਮੈਂਬਰ ਤੁਹਾਨੂੰ ਦੁਬਾਰਾ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਨਹੀਂ ਕਰ ਸਕਣਗੇ। ਗਰੁੱਪ ਦੇ ਮੈਂਬਰ ਤੁਹਾਨੂੰ ਦੁਬਾਰਾ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਨਹੀਂ ਕਰ ਸਕਣਗੇ। ਗਰੁੱਪ ਦੇ ਮੈਂਬਰ ਤੁਹਾਨੂੰ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਦੁਬਾਰਾ ਸ਼ਾਮਲ ਕਰਨ ਦੇ ਸਮਰੱਥ ਹੋਣਗੇ। @@ -148,15 +149,15 @@ ਤੁਸੀਂ ਇੱਕ ਦੂਜੇ ਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਸਕੋਗੇ। ਪਾਬੰਦੀਸ਼ੁਦਾ ਲੋਕ ਤੁਹਾਨੂੰ ਕਾਲ ਨਹੀਂ ਕਰ ਸਕਣਗੇ ਜਾਂ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਣਗੇ। - ਪਾਬੰਦੀ ਲਗਾਏ ਲੋਕ ਤੁਹਾਨੂੰ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਣਗੇ। + ਪਾਬੰਦੀਸ਼ੁਦਾ ਲੋਕ ਤੁਹਾਨੂੰ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਣਗੇ। - Signal ਅੱਪਡੇਟ ਤੇ ਖ਼ਬਰਾਂ ਲੈਣ ਤੋਂ ਪਾਬੰਦੀ ਲਾਓ। + Signal ਅੱਪਡੇਟਾਂ ਅਤੇ ਖ਼ਬਰਾਂ ਪ੍ਰਾਪਤ ਕਰਨ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ। Signal ਅੱਪਡੇਟ ਤੇ ਖ਼ਬਰਾਂ ਲੈਣਾ ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ। - %1$s ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਉਣੀ ਹੈ? - ਪਾਬੰਦੀ ਲਾਓ - ਪਾਬੰਦੀ ਲਾਓ ਤੇ ਛੱਡੋ - ਸਪੈਮ ਵਜੋਂ ਰਿਪੋਰਟ ਕਰੋ ਤੇ ਪਾਬੰਦੀ ਲਾਓ + ਕੀ %1$s ਉੱਤੋਂ ਪਾਬੰਦੀ ਹਟਾਉਣੀ ਹੈ? + ਪਾਬੰਦੀ ਲਗਾਓ + ਪਾਬੰਦੀ ਲਗਾਓ ਅਤੇ ਛੱਡੋ + ਸਪੈਮ ਵਜੋਂ ਰਿਪੋਰਟ ਕਰੋ ਅਤੇ ਪਾਬੰਦੀ ਲਗਾਓ ਅੱਜ @@ -366,7 +367,7 @@ ਮੀਡੀਆ ਭੇਜਣ ਦੌਰਾਨ ਤਰੁੱਟੀ - ਸਪੈਮ ਵਜੋਂ ਰਿਪੋਰਟ ਕੀਤਾ ਤੇ ਪਾਬੰਦੀ ਲਾਈ। + ਸਪੈਮ ਵਜੋਂ ਰਿਪੋਰਟ ਕੀਤਾ ਗਿਆ ਅਤੇ ਪਾਬੰਦੀ ਲਗਾਈ ਗਈ। SMS ਮੈਸੇਜਿੰਗ ਫ਼ਿਲਹਾਲ ਅਸਮਰੱਥ ਹੈ। ਤੁਸੀਂ ਆਪਣੇ ਸੁਨੇਹਿਆਂ ਨੂੰ ਆਪਣੇ ਫ਼ੋਨ ਵਿੱਚ ਕਿਸੇ ਹੋਰ ਐਪ \'ਤੇ ਐਕਸਪੋਰਟ ਕਰ ਸਕਦੇ ਹੋ। @@ -451,11 +452,11 @@ %1$s ਗਰੁੱਪ ਲਿੰਕ ਰਾਹੀਂ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਨਹੀਂ ਹੋ ਸਕਣਗੇ ਜਾਂ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦੀ ਬੇਨਤੀ ਨਹੀਂ ਕਰ ਸਕਣਗੇ। ਉਹਨਾਂ ਨੂੰ ਅਜੇ ਵੀ ਖੁਦ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। - ਬੇਨਤੀ ਉੱਤੇ ਪਾਬੰਦੀ + ਬੇਨਤੀ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ ਰੱਦ ਕਰੋ - ਪਾਬੰਦੀ ਲੱਗਾ + ਪਾਬੰਦੀ ਲਗਾਈ ਗਈ ਫਿਲਟਰ ਹਟਾਓ @@ -480,28 +481,28 @@ %1$d ਗੱਲਬਾਤਾਂ ਨੂੰ ਇਨਬਾਕਸ ਵਿੱਚ ਭੇਜਿਆ ਗਿਆ - ਪੜ੍ਹੇ - ਪੜ੍ਹੇ + ਪੜ੍ਹਿਆ ਗਿਆ + ਪੜ੍ਹੇ ਗਏ - ਨਾ-ਪੜ੍ਹੇ - ਨਾ-ਪੜ੍ਹੇ + ਨਹੀਂ-ਪੜ੍ਹਿਆ ਗਿਆ + ਨਹੀਂ-ਪੜ੍ਹੇ ਗਏ - ਟੰਗੋ - ਟੰਗੋ + ਪਿੰਨ ਕਰੋ + ਪਿੰਨ ਕਰੋ - ਲਾਹੋ - ਲਾਹੋ + ਅਣਪਿੰਨ ਕਰੋ + ਅਣਪਿੰਨ ਕਰੋ - ਚੁੱਪ - ਚੁੱਪ + ਮਿਊਟ ਕਰੋ + ਮਿਊਟ ਕਰੋ - ਅਣ-ਮਿਊਟ ਕਰੋ - ਅਣ-ਮਿਊਟ ਕਰੋ + ਅਣਮਿਊਟ ਕਰੋ + ਅਣਮਿਊਟ ਕਰੋ ਚੁਣੋ @@ -516,7 +517,7 @@ ਮਿਟਾਓ ਮਿਟਾਓ - ਸਾਰਿਆ ਨੂੰ ਚੁਣੋ + ਸਾਰਿਆਂ ਨੂੰ ਚੁਣੋ %1$dਚੁਣਿਆ %1$dਚੁਣੇ @@ -542,6 +543,15 @@ +%1$d + + ਆਪਣੇ ਡਿਵਾਈਸਾਂ ਨੂੰ ਦੁਬਾਰਾ ਲਿੰਕ ਕਰੋ + + ਜਦੋਂ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਨੂੰ ਅਨ-ਰਜਿਸਟਰ ਕੀਤਾ ਗਿਆ ਸੀ, ਉਦੋਂ ਤੁਹਾਡੇ ਦੁਆਰਾ ਲਿੰਕ ਕੀਤੇ ਡਿਵਾਈਸਾਂ ਨੂੰ ਅਨਲਿੰਕ ਕਰ ਦਿੱਤਾ ਗਿਆ ਸੀ। ਕਿਸੇ ਵੀ ਡਿਵਾਈਸ ਨੂੰ ਦੁਬਾਰਾ ਲਿੰਕ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ। + + ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ + + ਬਾਅਦ ਵਿੱਚ + ਮੈਂਬਰ ਚੁਣੋ @@ -935,7 +945,7 @@ ਹਵਾਲਿਆਂ ਲਈ ਮੈਨੂੰ ਸੂਚਿਤ ਕਰੋ - ਮਿਊਟ ਕੀਤੀਆਂ ਚੈਟਾਂ ਵਿੱਚ ਤੁਹਾਡਾ ਹਵਾਲਾ ਦਿੱਤੇ ਜਾਣ ਸਮੇਂ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਕਰਨੀਆਂ ਹਨ? + ਜਦੋਂ ਮਿਊਟ ਕੀਤੀਆਂ ਚੈਟਾਂ ਵਿੱਚ ਤੁਹਾਡਾ ਜ਼ਿਕਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਤਾਂ ਕੀ ਉਦੋਂ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਕਰਨੀਆਂ ਹਨ? ਮੈਨੂੰ ਹਮੇਸ਼ਾਂ ਸੂਚਿਤ ਕਰੋ ਮੈਨੂੰ ਸੂਚਿਤ ਨਾ ਕਰੋ @@ -953,6 +963,16 @@ ਵਰਤੋਂਕਾਰ ਨਾਮ ਬਣਾਇਆ ਗਿਆ ਵਰਤੋਂਕਾਰ ਨਾਮ ਕਾਪੀ ਕੀਤਾ ਗਿਆ + + ਵਰਤੋਂਕਾਰ ਨਾਂ ਮਿਟਾ ਨਹੀਂ ਸਕੇ। ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + + ਵਰਤੋਂਕਾਰ ਨਾਂ ਮਿਟਾਇਆ ਗਿਆ + + + + ਤੁਹਾਡੇ ਵਰਤੋਂਕਾਰ ਨਾਂ ਨਾਲ ਕੁਝ ਗਲਤ ਵਾਪਰ ਗਿਆ ਹੈ, ਇਹ ਹੁਣ ਤੁਹਾਵਰਤੋਂਕਾਰ ਨਾਂਡੇ ਖਾਤੇ ਨੂੰ ਅਸਾਈਨ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਤੁਸੀਂ ਇਸਨੂੰ ਦੁਬਾਰਾ ਸੈੱਟ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹੋ ਜਾਂ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਨਾਂ ਚੁਣ ਸਕਦੇ ਹੋ। + + ਹੁਣੇ ਠੀਕ ਕਰੋ @@ -1156,8 +1176,8 @@ ਨਵਾਂ ਗਰੁੱਪ ਦੋਸਤਾਂ ਨੂੰ ਸੱਦਾ ਦਿਓ SMS ਵਰਤੋ - ਦਿੱਖ - ਫ਼ੋਟੋ ਜੋੜੋ + ਚੈਟ ਦਾ ਰੰਗ + ਪ੍ਰੋਫ਼ਾਈਲ ਫ਼ੋਟੋ ਸ਼ਾਮਲ ਕਰੋ ਜਵਾਬ @@ -1468,14 +1488,14 @@ ਮਨਜ਼ੂਰ ਕਰੋ ਜਾਰੀ ਰੱਖੋ ਮਿਟਾਓ - ਪਾਬੰਦੀ ਲਾਓ + ਪਾਬੰਦੀ ਲਗਾਓ ਪਾਬੰਦੀ ਹਟਾਓ %1$s ਨੂੰ ਤੁਹਾਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣ ਅਤੇ ਉਹਨਾਂ ਨਾਲ ਤੁਹਾਡਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਮਨਜ਼ੂਰ ਨਹੀਂ ਕਰ ਲੈਂਦੇ ਉਦੋਂ ਤੱਕ ਉਹਨਾਂ ਨੂੰ ਇਹ ਪਤਾ ਨਹੀਂ ਲੱਗੇਗਾ ਕਿ ਤੁਸੀਂ ਉਹਨਾਂ ਦਾ ਸੁਨੇਹਾ ਦੇਖ ਲਿਆ ਹੈ। - %1$s ਨੂੰ ਤੁਹਾਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣ ਅਤੇ ਤੁਹਾਡਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾ ਨਹੀਂ ਦਿੰਦੇ ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਵੀ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਣਗੇ। + ਕੀ ਤੁਸੀਂ %1$s ਨੂੰ ਤੁਹਾਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣਾ ਚਾਹੁੰਦੇ ਹੋ ਅਤੇ ਉਹਨਾਂ ਨਾਲ ਆਪਣਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨੀ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਉੱਤੋਂ ਪਾਬੰਦੀ ਨਹੀਂ ਹਟਾਉਂਦੇ, ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਵੀ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਣਗੇ। - ਕੀ ਤੁਸੀਂ ਚਾਹੁੰਦੇ ਹੋ ਕਿ %1$s ਤੁਹਾਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਸਕਣ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਤੋਂ ਪਾਬੰਦੀ ਨਹੀਂ ਹਟਾਉਂਦੇ, ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਸੁਨੇਹਾ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਵੇਗਾ। - ਕੀ ਤੁਸੀਂ %1$s ਤੋਂ ਅੱਪਡੇਟਾਂ ਅਤੇ ਖ਼ਬਰਾਂ ਪ੍ਰਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਤੋਂ ਪਾਬੰਦੀ ਨਹੀਂ ਹਟਾਉਂਦੇ, ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਸੁਨੇਹਾ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਵੇਗਾ। + ਕੀ ਤੁਸੀਂ %1$s ਨੂੰ ਤੁਹਾਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣਾ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਉੱਤੋਂ ਪਾਬੰਦੀ ਨਹੀਂ ਹਟਾਉਂਦੇ, ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਵੀ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਣਗੇ। + ਕੀ ਤੁਸੀਂ %1$s ਤੋਂ ਅੱਪਡੇਟਾਂ ਅਤੇ ਖ਼ਬਰਾਂ ਪ੍ਰਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਉੱਤੋਂ ਪਾਬੰਦੀ ਨਹੀਂ ਹਟਾਉਂਦੇ, ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਵੀ ਅੱਪਡੇਟ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਵੇਗੀ। ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਆਪਣੀ ਗੱਲਬਾਤ ਅਤੇ ਇਸਦੇ ਮੈਂਬਰਾਂ ਨਾਲ ਆਪਣਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨੀ ਜਾਰੀ ਰੱਖਣੀ ਹੈ? \@mentions ਅਤੇ ਐਡਮਿਨ ਵਰਗੀਆਂ ਨਵੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਕਿਰਿਆਸ਼ੀਲ ਕਰਨ ਲਈ ਇਸ ਗਰੁੱਪ ਨੂੰ ਅੱਪਗ੍ਰੇਡ ਕਰੋ। ਜਿਹਨਾਂ ਮੈਂਬਰਾਂ ਨੇ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਆਪਣਾ ਨਾਂ ਜਾਂ ਫ਼ੋਟੋ ਸਾਂਝੀ ਨਹੀਂ ਕੀਤੀ ਹੈ, ਉਹਨਾਂ ਨੂੰ ਸ਼ਾਮਲ ਹੋਣ ਲਈ ਸੱਦਾ ਭੇਜਿਆ ਜਾਵੇਗਾ। ਇਸ ਲੈਗਸੀ ਗਰੁੱਪ ਨੂੰ ਹੁਣ ਹੋਰ ਨਹੀਂ ਵਰਤਿਆ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਇਹ ਬਹੁਤ ਵੱਡਾ ਹੈ। ਗਰੁੱਪ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਆਕਾਰ %1$d ਹੈ। @@ -1483,7 +1503,7 @@ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣਾ ਅਤੇ ਇਸਦੇ ਮੈਂਬਰਾਂ ਨਾਲ ਆਪਣਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨੀ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਮਨਜ਼ੂਰ ਨਹੀਂ ਕਰ ਲੈਂਦੇ ਉਦੋਂ ਤੱਕ ਉਹਨਾਂ ਨੂੰ ਇਹ ਪਤਾ ਨਹੀਂ ਲੱਗੇਗਾ ਕਿ ਤੁਸੀਂ ਉਹਨਾਂ ਦੇ ਸੁਨੇਹੇ ਦੇਖ ਲਏ ਹਨ। ਕੀ ਤੁਸੀਂ ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣਾ ਅਤੇ ਇਸ ਦੇ ਮੈਂਬਰਾਂ ਨਾਲ ਆਪਣਾ ਨਾਮ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਸਵੀਕਾਰ ਨਹੀਂ ਕਰਦੇ, ਤੁਸੀਂ ਉਹਨਾਂ ਦੇ ਸੁਨੇਹੇ ਨਹੀਂ ਦੇਖ ਸਕੋਗੇ। ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣਾ ਹੈ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਮਨਜ਼ੂਰ ਨਹੀਂ ਕਰ ਲੈਂਦੇ ਉਦੋਂ ਤੱਕ ਉਹਨਾਂ ਨੂੰ ਇਹ ਪਤਾ ਨਹੀਂ ਲੱਗੇਗਾ ਕਿ ਤੁਸੀਂ ਉਹਨਾਂ ਦੇ ਸੁਨੇਹੇ ਦੇਖ ਲਏ ਹਨ। - ਇਸ ਗਰੁੱਪ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਉਣੀ ਅਤੇ ਇਸਦੇ ਮੈਂਬਰਾਂ ਨਾਲ ਆਪਣਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨੀ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾ ਨਹੀਂ ਦਿੰਦੇ ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਵੀ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਣਗੇ। + ਕੀ ਤੁਸੀਂ ਇਸ ਗਰੁੱਪ ਉੱਤੋਂ ਪਾਬੰਦੀ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ ਅਤੇ ਇਸ ਗਰੁੱਪ ਦੇ ਮੈਂਬਰਾਂ ਨਾਲ ਆਪਣਾ ਨਾਂ ਅਤੇ ਫ਼ੋਟੋ ਸਾਂਝੀ ਕਰਨੀ ਚਾਹੁੰਦੇ ਹੋ? ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਉੱਤੋਂ ਪਾਬੰਦੀ ਨਹੀਂ ਹਟਾਉਂਦੇ, ਉਦੋਂ ਤੱਕ ਤੁਹਾਨੂੰ ਕੋਈ ਵੀ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਣਗੇ। ਵੇਖੋ %1$s ਦੇ ਮੈਂਬਰ @@ -1584,9 +1604,20 @@ ਨਵਾਂ PIN ਬਣਾਓ + + SMS ਕੋਡ ਭੇਜੋ + + Signal ਰਜਿਸਟ੍ਰੇਸ਼ਨ - Android ਦੇ ਲਈ PIN ਨੂੰ ਮੁੜ-ਰਜਿਸਟਰ ਕਰਨ ਲਈ ਮਦਦ ਚਾਹੀਦੀ ਹੈ + + ਤੁਹਾਡਾ PIN ਤੁਹਾਡੇ ਵੱਲੋਂ ਬਣਾਇਆ ਗਿਆ %1$d+ ਅੰਕਾਂ ਦਾ ਕੋਡ ਹੁੰਦਾ ਹੈ, ਜਿਸ ਵਿੱਚ ਸਿਰਫ਼ ਅੰਕ ਜਾਂ ਅੱਖਰ ਅਤੇ ਅੰਕ ਦੋਵੇਂ ਹੋ ਸਕਦੇ ਹਨ।ਜੇਕਰ ਤੁਹਾਨੂੰ ਆਪਣਾ PIN ਯਾਦ ਨਹੀਂ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਨਵਾਂ PIN ਬਣਾ ਸਕਦੇ ਹੋ। + + ਜੇਕਰ ਤੁਹਾਨੂੰ ਆਪਣਾ PIN ਯਾਦ ਨਹੀਂ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਨਵਾਂ PIN ਬਣਾ ਸਕਦੇ ਹੋ। + + PIN ਦਾ ਅੰਦਾਜ਼ਾ ਲਗਾਉਣ ਲਈ ਤੁਹਾਡੇ ਕੋਲ ਉਪਲਬਧ ਮੌਕੇ ਖਤਮ ਹੋ ਗਏ ਹਨ, ਪਰ ਤੁਸੀਂ ਅਜੇ ਵੀ ਇੱਕ ਨਵਾਂ PIN ਬਣਾ ਕੇ ਆਪਣੇ Signal ਖਾਤੇ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੇ ਹੋ। + ਚੇਤਾਵਨੀ - ਜੇ ਤੁਸੀਂ PIN ਅਸਮਰੱਥ ਕੀਤਾ ਤਾਂ ਤੁਸੀਂ Signal ਨੂੰ ਮੁੜ-ਰਜਿਸਟਰ ਕਰਨ ਵੇਲੇ ਸਾਰਾ ਡੇਟਾ ਗੁਆ ਬੈਠੋਗੇ, ਜਦੋਂ ਤੱਕ ਕਿ ਤੁਸੀਂ ਖੁਦ ਬੈਕਅੱਪ ਨਹੀਂ ਲੈਂਦੇ ਤੇ ਬਹਾਲ ਕਰਦੇ। PIN ਅਸਮਰੱਥ ਕੀਤੇ ਹੋਣ ਨਾਲ ਤੁਸੀਂ ਰਜਿਸਟ੍ਰੇਸ਼ਨ ਲੌਕ ਨੂੰ ਨਹੀਂ ਖੋਲ੍ਹ ਸਕਦੇ ਹੋ। + ਜੇ ਤੁਸੀਂ PIN ਨੂੰ ਅਸਮਰੱਥ ਕੀਤਾ ਹੋਇਆ ਤਾਂ ਤੁਸੀਂ Signal ਨੂੰ ਮੁੜ-ਰਜਿਸਟਰ ਕਰਨ ਵੇਲੇ ਸਾਰਾ ਡੇਟਾ ਗੁਆ ਬੈਠੋਗੇ, ਜਦੋਂ ਤੱਕ ਕਿ ਤੁਸੀਂ ਖੁਦ ਬੈਕਅੱਪ ਨਹੀਂ ਲੈਂਦੇ ਅਤੇ ਰੀਸਟੋਰ ਨਹੀਂ ਕਰਦੇ। PIN ਦੇ ਅਸਮਰੱਥ ਹੋਣ ਦੌਰਾਨ ਤੁਸੀਂ ਰਜਿਸਟ੍ਰੇਸ਼ਨ ਲਾਕ ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਦੇ ਹੋ। PIN ਅਸਮਰੱਥ ਕਰੋ @@ -1616,7 +1647,7 @@ ਮੇਰੀ ਸਟੋਰੀ - ਪਾਬੰਦੀ ਲਾਓ + ਪਾਬੰਦੀ ਲਗਾਓ ਪਾਬੰਦੀ ਹਟਾਓ @@ -1711,9 +1742,9 @@ ਕੈਮਰਾ - ਅਣ-ਮਿਊਟ ਕਰੋ + ਅਨਮਿਊਟ ਕਰੋ - ਚੁੱਪ + ਮਿਊਟ ਕਰੋ ਰਿੰਗ @@ -1726,12 +1757,12 @@ - %1$s ਉੱਤੇ ਪਾਬੰਦੀ ਹੈ + %1$s ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਈ ਗਈ ਹੈ ਹੋਰ ਜਾਣਕਾਰੀ ਤੁਹਾਨੂੰ ਇੱਕ-ਦੂਸਰੇ ਦੀ ਆਡੀਓ ਜਾਂ ਵੀਡੀਓ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਵੇਗੀ। %1$s ਤੋਂ ਆਡੀਓ & ਵੀਡੀਓ ਨਹੀਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ %1$s ਤੋਂ ਆਡੀਓ ਤੇ ਵੀਡੀਓ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋ ਸਕਦੀ ਹੈ - ਅਜਿਹਾ ਸ਼ਾਇਦ ਇਸ ਲਈ ਹੋ ਸਕਦਾ ਹੈ ਕਿਉਂਕਿ ਉਹਨਾਂ ਨੇ ਤੁਹਾਡੇ ਸੁਰੱਖਿਆ ਨੰਬਰ ਵਿੱਚ ਤਬਦੀਲੀ ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਨਹੀਂ ਕੀਤਾ ਹੈ, ਉਹਨਾਂ ਦੀ ਡਿਵਾਈਸ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਹੋ ਸਕਦੀ ਹੈ, ਜਾਂ ਉਹਨਾਂ ਨੇ ਤੁਹਾਡੇ ’ਤੇ ਪਾਬੰਦੀ ਲਗਾ ਦਿੱਤੀ ਹੈ। + ਅਜਿਹਾ ਇਸ ਕਾਰਨ ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਸ਼ਾਇਦ ਉਹਨਾਂ ਨੇ ਤੁਹਾਡਾ ਸੁਰੱਖਿਆ ਨੰਬਰ ਬਦਲਣ ਦੀ ਤਸਦੀਕ ਨਹੀਂ ਕੀਤੀ ਹੈ, ਉਹਨਾਂ ਦੇ ਡਿਵਾਈਸ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਹੈ, ਜਾਂ ਉਹਨਾਂ ਨੇ ਤੁਹਾਡੇ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਈ ਹੋਈ ਹੈ। ਸਾਂਝੀ ਕੀਤੀ ਸਕਰੀਨ ਨੂੰ ਵੇਖਣ ਲਈ ਸਵਾਈਪ ਕਰੋ @@ -1768,11 +1799,18 @@ ਤੁਹਾਨੂੰ ਦੋਸਤਾਂ ਨਾਲ ਕਨੈਕਟ ਕਰਨ ਤੇ ਸੁਨੇਹੇ ਭੇਜਣ ਵਾਸਤੇ Signal ਨੂੰ ਸੰਪਰਕ ਤੇ ਮੀਡੀਏ ਵਾਸਤੇ ਇਜਾਜ਼ਤਾਂ ਦੀ ਲੋੜ ਹੈ। ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਨੂੰ Signal ਦੇ ਪ੍ਰਾਈਵੇਟ ਸੰਪਰਕ ਡਿਸਕਵਰੀ ਵਰਤ ਕੇ ਅੱਪਲੋਡ ਕੀਤੇ ਜਾਂਦੇ ਹਨ, ਜਿਸ ਦਾ ਅਰਥ ਹੈ ਕਿ ਉਹ ਸਿਰੇ-ਤੋਂ-ਸਿਰੇ ਤੱਕ ਇੰਕ੍ਰਿਪਟ ਹਨ ਅਤੇ Signal ਸੇਵਾ ਨੂੰ ਕਦੇ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ ਹਨ। ਤੁਹਾਨੂੰ ਦੋਸਤਾਂ ਨਾਲ ਕਨੈਕਟ ਕਰਨ ਵਾਸਤੇ Signal ਨੂੰ ਸੰਪਰਕ ਵਾਸਤੇ ਇਜਾਜ਼ਤਾਂ ਦੀ ਲੋੜ ਹੈ। ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਨੂੰ Signal ਦੇ ਪ੍ਰਾਈਵੇਟ ਸੰਪਰਕ ਡਿਸਕਵਰੀ ਵਰਤ ਕੇ ਅੱਪਲੋਡ ਕੀਤੇ ਜਾਂਦੇ ਹਨ, ਜਿਸ ਦਾ ਅਰਥ ਹੈ ਕਿ ਉਹ ਸਿਰੇ-ਤੋਂ-ਸਿਰੇ ਤੱਕ ਇੰਕ੍ਰਿਪਟ ਹਨ ਅਤੇ Signal ਸੇਵਾ ਨੂੰ ਕਦੇ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ ਹਨ। ਤੁਸੀਂ ਇਸ ਨੰਬਰ ਨੂੰ ਰਜਿਸਟਰ ਕਰਨ ਦੀਆਂ ਬਹੁਤ ਸਾਰੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ ਕਰ ਲਈਆਂ ਹਨ। ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। + + ਤੁਸੀਂ ਇਸ ਨੰਬਰ ਨੂੰ ਰਜਿਸਟਰ ਕਰਨ ਲਈ ਬਹੁਤ ਸਾਰੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ ਕਰ ਚੁੱਕੇ ਹੋ। ਕਿਰਪਾ ਕਰਕੇ %1$s ਬਾਅਦ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। ਸੇਵਾ ਨਾਲ ਜੁੜਨ ਵਿੱਚ ਅਸਮਰੱਥ। ਕਿਰਪਾ ਕਰਕੇ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। ਗ਼ੈਰ-ਮਿਆਰੀ ਅੰਕ ਰੂਪ ਤੁਹਾਡੇ ਵਲੋਂ ਦਿੱਤਾ ਨੰਬਰ (%1$s) ਗੈਰ-ਮਿਆਰੀ ਜਾਪਦਾ ਹੈ।\n\nਕੀ ਤੁਹਾਡਾ ਮਤਲਬ %2$s ਹੈ? Molly Android - ਫ਼ੋਨ ਨੰਬਰ ਫਾਰਮਿਟ + ਕਾਲ ਲਈ ਬੇਨਤੀ ਕੀਤੀ ਗਈ + + SMS ਲਈ ਬੇਨਤੀ ਕੀਤੀ ਗਈ + + ਤਸਦੀਕ ਕੋਡ ਲਈ ਬੇਨਤੀ ਕੀਤੀ ਗਈ ਹੁਣ ਤੁਸੀਂ ਡੀਬੱਗ ਲਾਗ ਨੂੰ ਸਬਮਿਟ ਕਰਨ ਤੋਂ %1$d ਕਦਮ ਦੂਰ ਹੋ. ਹੁਣ ਤੁਸੀਂ ਡੀਬੱਗ ਲੌਗ ਨੂੰ ਦਰਜ ਕਰਨ ਤੋਂ %1$d ਕਦਮ ਦੂਰ ਹੋ। @@ -1792,6 +1830,16 @@ ਕਾਲ ਤਸਦੀਕ ਕੋਡ ਕੋਡ ਦੁਬਾਰਾ ਭੇਜੋ + + ਰਜਿਸਟਰ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ ਪੇਸ਼ ਆ ਰਹੀ ਹੈ? + + • SMS ਜਾਂ ਕਾਲ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡੇ ਫ਼ੋਨ ਵਿੱਚ ਸੈਲੂਲਰ ਸਿਗਨਲ ਆ ਰਿਹਾ ਹੋਵੇ\n • ਪੁਸ਼ਟੀ ਕਰੋ ਕਿ ਤੁਸੀਂ ਨੰਬਰ \'ਤੇ ਫ਼ੋਨ ਕਾਲ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ\n • ਜਾਂਚ ਕਰੋ ਕਿ ਤੁਸੀਂ ਆਪਣਾ ਫ਼ੋਨ ਨੰਬਰ ਸਹੀ ਢੰਗ ਨਾਲ ਦਰਜ ਕੀਤਾ ਹੈ। + + ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਕਿਰਪਾ ਕਰਕੇ ਸਮੱਸਿਆ ਦਾ ਨਿਪਟਾਰਾ ਕਰਨ ਵਾਲੇ ਇਹਨਾਂ ਕਦਮਾਂ ਦੀ ਪਾਲਣਾ ਕਰੋ ਜਾਂ ਸਹਾਇਤਾ ਟੀਮ ਨਾਲ ਸੰਪਰਕ ਕਰੋ + + ਸਮੱਸਿਆ ਦਾ ਨਿਪਟਾਰਾ ਕਰਨ ਵਾਲੇ ਇਹਨਾਂ ਕਦਮਾਂ + + ਸਹਾਇਤਾ ਟੀਮ ਨਾਲ ਸੰਪਰਕ ਕਰੋ ਰਜਿਸਟਰੇਸ਼ਨ ਲਾਕ ਚਾਲੂ ਕਰਨਾ ਹੈ? @@ -1958,6 +2006,10 @@ ਭੁਗਤਾਨ ਸ਼ੈਡਿਊਲ ਕੀਤੇ ਸੁਨੇਹੇ + + ਤੁਹਾਡੀ ਪੁਰਾਣੀ ਚੈਟ ਨੂੰ ਆਪਸ ਵਿੱਚ ਮਿਲਾ ਦਿੱਤਾ ਗਿਆ ਹੈ + + %1$s ਨੰਬਰ %2$s ਦਾ ਹੈ Molly ਅੱਪਡੇਟ @@ -2030,7 +2082,7 @@ ਗੈਰ-ਮੌਜੂਦ ਸੈਸ਼ਨ ਲਈ ਇਨਕ੍ਰਿਪਟ ਕੀਤਾ MMS ਸੁਨੇਹਾ - ਨੋਟੀਫਿਕੇਸ਼ਨ ਮਿਊਟ ਕਰੋ + ਸੂਚਨਾਵਾਂ ਨੂੰ ਮਿਊਟ ਕਰੋ ਇੰਪੋਰਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ @@ -2087,14 +2139,16 @@ ਨਾ-ਸੁਰੱਖਿਅਤ SMS %1$s %2$s ਸੰਪਰਕ - \"%2$s\": \'ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ - ਤੁਹਾਡੇ ਵੀਡੀਓ \'ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ - ਤੁਹਾਡੇ ਚਿੱਤਰ ’ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ। + \"%2$s\": ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ + ਤੁਹਾਡੇ ਵੀਡੀਓ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ + ਤੁਹਾਡੇ ਚਿੱਤਰ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ। ਤੁਹਾਡੀ GIF ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ। - ਤੁਹਾਡੀ ਫਾਈਲ \'ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ। - ਤੁਹਾਡੇ ਆਡੀਓ \'ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ। - ਤੁਹਾਡੇ ਇੱਕ ਵਾਰ ਦੇਖਣਯੋਗ ਮੀਡੀਆ \'ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ। - ਤੁਹਾਡੇ ਸਟਿੱਕਰ \'ਤੇ %1$s ਪ੍ਰਤਿਕਿਰਿਆ ਦਿੱਤੀ। + ਤੁਹਾਡੀ ਫ਼ਾਈਲ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ। + ਤੁਹਾਡੇ ਆਡੀਓ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ। + ਤੁਹਾਡੇ ਇੱਕ ਵਾਰ ਦੇਖੇ ਜਾ ਸਕਣ ਵਾਲੇ ਮੀਡੀਆ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ। + + ਤੁਹਾਡੇ ਭੁਗਤਾਨ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ + ਤੁਹਾਡੇ ਸਟਿੱਕਰ ਉੱਤੇ %1$s ਰਿਐਕਸ਼ਨ ਦਿੱਤਾ। ਇਹ ਸੁਨੇਹਾ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਸੀ। ਸੰਪਰਕ ਦੁਆਰਾ Signal ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਬਾਰੇ ਸੂਚਨਾਵਾਂ ਨੂੰ ਬੰਦ ਕਰਨਾ ਹੈ? ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ Signal > ਸੈਟਿੰਗਾਂ > ਸੂਚਨਾਵਾਂ ਵਿੱਚ ਜਾ ਕੇ ਵਿੱਚ ਦੁਬਾਰਾ ਸਮਰੱਥ ਕਰ ਸਕਦੇ ਹੋ। @@ -2298,7 +2352,7 @@ ਕਾਲ ਸੂਚਨਾਵਾਂ ਸਮਰੱਥ ਕਰੋ ਸੰਪਰਕ ਅੱਪਡੇਟ ਕਰੋ - ਬੇਨਤੀ ਉੱਤੇ ਪਾਬੰਦੀ + ਬੇਨਤੀ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ ਕੋਈ ਸਾਂਝੇ ਗਰੁੱਪ ਨਹੀਂ। ਬੇਨਤੀਆਂ ਦੀ ਧਿਆਨ ਨਾਲ ਸਮੀਖਿਆ ਕਰੋ। ਇਸ ਗਰੁੱਪ ਵਿੱਚ ਕੋਈ ਸੰਪਰਕ ਨਹੀਂ। ਬੇਨਤੀਆਂ ਦੀ ਧਿਆਨ ਨਾਲ ਸਮੀਖਿਆ ਕਰੋ। ਵੇਖੋ @@ -2487,8 +2541,8 @@ ਡਿਲਿਵਰੀ ਮਸਲਾ - %1$s ਤੋਂ ਤੁਹਾਨੂੰ ਕੋਈ ਸੁਨੇਹਾ, ਸਟਿੱਕਰ, ਪ੍ਰਤਿਕਿਰਿਆ, ਪੜ੍ਹਨ ਦੀ ਰਸੀਦ ਭੇਜੇ ਨਹੀਂ ਜਾ ਸਕੇ। ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਉਹਨਾਂ ਨੇ ਤੁਹਾਨੂੰ ਸਿੱਧੇ ਤੌਰ ’ਤੇ, ਜਾਂ ਕਿਸੇ ਗਰੁੱਪ ਵਿੱਚ ਇਸਨੂੰ ਭੇਜਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਹੋਵੇ। - %1$s ਤੋਂ ਤੁਹਾਨੂੰ ਕੋਈ ਸੁਨੇਹਾ, ਸਟਿੱਕਰ, ਪ੍ਰਤਿਕਿਰਿਆ ਜਾਂ ਪੜ੍ਹਨ ਦੀ ਰਸੀਦ ਭੇਜੇ ਨਹੀਂ ਜਾ ਸਕੇ। + %1$s ਵੱਲੋਂ ਆਇਆ ਕੋਈ ਸੁਨੇਹਾ, ਸਟਿੱਕਰ, ਰਿਐਕਸ਼ਨ, ਪੜ੍ਹਨ ਦੀ ਸੂਚਨਾ ਤੁਹਾਨੂੰ ਡਿਲੀਵਰ ਨਹੀਂ ਕਰ ਜਾ ਸਕੇ। ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਉਹਨਾਂ ਨੇ ਤੁਹਾਨੂੰ ਸਿੱਧਾ, ਜਾਂ ਕਿਸੇ ਗਰੁੱਪ ਵਿੱਚ ਇਸਨੂੰ ਭੇਜਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਹੋਵੇ। + %1$s ਵੱਲੋਂ ਆਇਆ ਕੋਈ ਸੁਨੇਹਾ, ਸਟਿੱਕਰ, ਰਿਐਕਸ਼ਨ, ਪੜ੍ਹਨ ਦੀ ਸੂਚਨਾ ਤੁਹਾਨੂੰ ਡਿਲੀਵਰ ਨਹੀਂ ਕਰ ਜਾ ਸਕੇ। ਨਾਂ ਦਾ ਪਹਿਲਾਂ ਹਿੱਸਾ (ਲਾਜ਼ਮੀ) @@ -2555,7 +2609,7 @@ ਬਕਾਇਆ - ਇਸ ਨੂੰ ਭੇਜੇ + ਇਹਨਾਂ ਨੂੰ ਭੇਜਿਆ ਗਿਆ ਇਸ ਵਲੋਂ ਭੇਜੇ ਇਸ ਲਈ ਪਹੁੰਚਾਏ ਪੜ੍ਹੇ @@ -2635,10 +2689,10 @@ ਡਿਫੌਲਟ ਵਰਤੋ ਕਸਟਮ ਦੀ ਵਰਤੋਂ ਕਰੋ - 1 ਘੰਟੇ ਲਈ ਮੂਕ ਕਰੋ - 8 ਘੰਟਿਆਂ ਲਈ ਮੌਨ - 1 ਦਿਨ ਲਈ ਮੂਕ ਕਰੋ - 7 ਦਿਨ ਲਈ ਮੂਕ ਕਰੋ + 1 ਘੰਟੇ ਲਈ ਮਿਊਟ ਕਰੋ + 8 ਘੰਟਿਆਂ ਲਈ ਮਿਊਟ ਕਰੋ + 1 ਦਿਨ ਲਈ ਮਿਊਟ ਕਰੋ + 7 ਦਿਨਾਂ ਲਈ ਮਿਊਟ ਕਰੋ ਹਮੇਸ਼ਾਂ ਡਿਫੌਲਟ ਸੈਟਿੰਗਾਂ @@ -2675,9 +2729,9 @@ ਐਡਰੈਸ ਬੁੱਕ ਫੋਟੋਆਂ ਵਰਤੋਂ ਤੁਹਾਡੀ ਐਡਰੈਸ ਬੁੱਕ ਤੋਂ ਸੰਪਰਕ ਤਸਵੀਰ ਵੇਖਾਓ, ਜੇ ਉਪਲਬਧ ਹੋਣ - ਮੌਨ ਕੀਤੀਆਂ ਗਈਆਂ ਚੈਟਾਂ ਨੂੰ ਆਰਕਾਈਵ ਵਿੱਚ ਹੀ ਰੱਖੋ + ਮਿਊਟ ਕੀਤੀਆਂ ਗਈਆਂ ਚੈਟਾਂ ਨੂੰ ਆਰਕਾਈਵ ਵਿੱਚ ਹੀ ਰੱਖੋ - ਨਵਾਂ ਸੁਨੇਹਾ ਆਉਣ \'ਤੇ ਆਰਕਾਈਵ ਵਿੱਚ ਮੌਜੂਦ ਮੌਨ ਕੀਤੀਆਂ ਗਈਆਂ ਚੈਟਾਂ ਆਰਕਾਈਵ ਵਿੱਚ ਹੀ ਰਹਿਣਗੀਆਂ। + ਨਵਾਂ ਸੁਨੇਹਾ ਆਉਣ \'ਤੇ ਆਰਕਾਈਵ ਵਿੱਚ ਮੌਜੂਦ ਮਿਊਟ ਕੀਤੀਆਂ ਗਈਆਂ ਚੈਟਾਂ, ਆਰਕਾਈਵ ਵਿੱਚ ਹੀ ਰਹਿਣਗੀਆਂ। ਲਿੰਕ ਝਲਕ ਤਿਆਰ ਕਰੋ ਹੁਣ ਤੁਹਾਡੇ ਦੁਆਰਾ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਲਈ ਸਿੱਧੇ ਕਿਸੇ ਵੀ ਵੈੱਬਸਾਈਟ ਤੋਂ ਲਿੰਕ ਝਲਕ ਪ੍ਰਾਪਤ ਕਰੋ। ਪਾਸਫ਼੍ਰੇਜ਼ ਬਦਲੋ @@ -2971,7 +3025,7 @@ ਭੁਗਤਾਨ ਭੇਜਿਆ ਭੁਗਤਾਨ ਮਿਲਿਆ ਭੁਗਤਾਨ ਪੂਰਾ %1$s - ਨੰਬਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ + ਨੰਬਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ ਟਰਾਂਸਫਰ @@ -3056,7 +3110,7 @@ ਨਵਾਂ ਸੁਨੇਹਾ … - ਵਰਤੋਂਕਾਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ + ਵਰਤੋਂਕਾਰ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ ਕਿਸੇ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ @@ -3295,6 +3349,8 @@ ਆਪਣਾ PIN ਦਰਜ ਕਰੋ ਆਪਣੇ ਖਾਤੇ ਲਈ ਤੁਹਾਡੇ ਵੱਲੋਂ ਬਣਾਇਆ PIN ਦਿਓ। ਇਹ ਤੁਹਾਡੇ SMS ਤਸਦੀਕ ਕੋਡ ਤੋਂ ਵੱਖਰਾ ਹੁੰਦਾ ਹੈ. + + ਉਹ PIN ਦਰਜ ਕਰੋ ਜੋ ਤੁਸੀਂ ਆਪਣੇ ਖਾਤੇ ਲਈ ਬਣਾਇਆ ਹੈ। ਅੱਖਰਾਂ ਅਤੇ ਸੰਖਿਆਵਾਂ ਤੋਂ ਬਣਿਆ PIN ਦਰਜ ਕਰੋ ਅੰਕਾਂ ਵਾਲਾ ਪਿੰਨ ਦਿਓ ਗਲਤ PIN। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। @@ -3398,7 +3454,10 @@ ਤੁਹਾਡੇ ਬੈਕਅੱਪ ਵਿੱਚ ਇੱਕ ਬਹੁਤ ਵੱਡੀ ਫ਼ਾਈਲ ਮੌਜੂਦ ਹੈ ਜਿਸਦਾ ਬੈਕਅੱਪ ਨਹੀਂ ਲਿਆ ਜਾ ਸਕਦਾ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਇਸਨੂੰ ਮਿਟਾਓ ਅਤੇ ਇੱਕ ਨਵਾਂ ਬੈਕਅੱਪ ਬਣਾਓ। ਬੈਕਅੱਪਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਨ ਲਈ ਛੂਹੋ। ਕੀ ਇਹ ਨੰਬਰ ਗਲਤ ਹੈ? + ਮੈਨੂੰ ਕਾਲ ਕਰੋ (%1$02d:%2$02d) + + ਕੋਡ ਦੁਬਾਰਾ ਭੇਜੋ (%1$02d:%2$02d) ਸੰਪਰਕ Signal ਸਹਾਇਤਾ Signal ਰਜਿਸਟ੍ਰੇਸ਼ਨ - ਐਂਡਰਾਇਡ ਲਈ ਪੁਸ਼ਟੀਕਰਣ ਕੋਡ ਗਲਤ ਕੋਡ @@ -3406,6 +3465,18 @@ ਅਣਜਾਣ ਮੇਰੇ ਫੋਨ ਨੰਬਰ ਵੇਖਾਓ ਮੈਨੂੰ ਫ਼ੋਨ ਨੰਬਰ ਨਾਲ ਲੱਭੋ + + ਫ਼ੋਨ ਨੰਬਰ + + ਚੁਣੋ ਕਿ ਤੁਹਾਡਾ ਫ਼ੋਨ ਨੰਬਰ ਕੌਣ ਦੇਖ ਸਕਦਾ ਹੈ ਅਤੇ ਕੌਣ ਤੁਹਾਡੇ ਨਾਲ Molly \'ਤੇ ਸੰਪਰਕ ਕਰ ਸਕਦਾ ਹੈ। + + ਮੇਰਾ ਨੰਬਰ ਕੌਣ ਦੇਖ ਸਕਦਾ ਹੈ + + ਕੋਈ ਵੀ Molly ਉੱਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੰਬਰ ਨਹੀਂ ਦੇਖ ਸਕੇਗਾ। + + ਮੈਨੂੰ ਮੇਰੇ ਨੰਬਰ ਨਾਲ ਕੌਣ ਲੱਭ ਸਕਦਾ ਹੈ + + ਤੁਹਾਡਾ ਫ਼ੋਨ ਨੰਬਰ ਉਹਨਾਂ ਲੋਕਾਂ ਅਤੇ ਗਰੁੱਪਾਂ ਨੂੰ ਦਿਖਾਈ ਦੇਵੇਗਾ ਜਿਹਨਾਂ ਨੂੰ ਤੁਸੀਂ ਸੁਨੇਹਾ ਭੇਜਦੇ ਹੋ। ਜਿਹਨਾਂ ਲੋਕਾਂ ਦੇ ਫ਼ੋਨ ਵਿੱਚ ਤੁਹਾਡਾ ਫ਼ੋਨ ਨੰਬਰ ਸੇਵ ਕੀਤਾ ਹੋਇਆ ਹੈ, ਉਹ ਵੀ ਤੁਹਾਨੂੰ Molly ਉੱਤੇ ਦੇਖ ਸਕਣਗੇ। ਹਰ ਕੋਈ ਮੇਰੇ ਸੰਪਰਕ ਕੋਈ ਵੀ ਨਹੀਂ @@ -3562,7 +3633,7 @@ - ਬਲਾਕ + ਪਾਬੰਦੀ ਲਗਾਓ ਪਾਬੰਦੀ ਹਟਾਓ ਸੰਪਰਕਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਈ ਹੈ। - \"%1$s\" ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਣ ਲਈ ਅਸਫ਼ਲ ਹੈ - \"%1$s\" ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਈ ਜਾ ਚੁੱਕੀ ਹੈ। + \"%1$s\" ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਈ ਗਈ ਹੈ। + \"%1$s\" ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਉਣ ਵਿੱਚ ਅਸਫਲ ਰਹੇ + \"%1$s\" ਉੱਤੋਂ ਪਾਬੰਦੀ ਹਟਾ ਦਿੱਤੀ ਗਈ ਹੈ। ਮੈਂਬਰ ਦੀ ਪੜਤਾਲ @@ -3644,7 +3715,7 @@ ਤੁਹਾਡਾ ਸੰਪਰਕ ਗਰੁੱਪ ਵਿੱਚੋਂ ਹਟਾਓ ਸੰਪਰਕ ਅੱਪਡੇਟ ਕਰੋ - ਬਲੌਕ ਕਰੋ + ਪਾਬੰਦੀ ਲਗਾਓ ਮਿਟਾਓ ਹਾਲ ਹੀ ਵਿੱਚ ਆਪਣਾ ਪ੍ਰੋਫ਼ਾਈਲ ਨਾਂ %1$s ਤੋਂ ਬਦਲ ਕੇ %2$s ਰੱਖਿਆ। @@ -3667,10 +3738,10 @@ Wi-Fi ਸਿਗਨਲ ਕਮਜ਼ੋਰ ਹੈ। ਸੈਲੂਲਰ \'ਤੇ ਬਦਲਿਆ ਗਿਆ। - ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਹਟਾਉਣ ਨਾਲ: + ਜੇ ਤੁਸੀਂ ਆਪਣੇ ਖਾਤੇ ਨੂੰ ਮਿਟਾ ਦਿੰਦੇ ਹੋ ਤਾਂ: ਆਪਣਾ ਫੋਨ ਨੰਬਰ ਦਰਜ ਕਰੋ ਖਾਤਾ ਮਿਟਾਓ - ਆਪਣੇ ਖਾਤੇ ਦੀ ਜਾਣਕਾਰੀ ਅਤੇ ਪ੍ਰੋਫ਼ਾਈਲ ਫ਼ੋਟੋ ਨੂੰ ਮਿਟਾਓ + ਤੁਹਾਡੇ ਖਾਤੇ ਦੀ ਜਾਣਕਾਰੀ ਅਤੇ ਪ੍ਰੋਫਾਈਲ ਫ਼ੋਟੋ ਨੂੰ ਵੀ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ ਆਪਣੇ ਸਾਰੇ ਸੁਨੇਹੇ ਮਿਟਾਓ ਤੁਹਾਡੇ ਭੁਗਤਾਨ ਖਾਤੇ ਵਿੱਚ %1$s ਨੂੰ ਮਿਟਾਓ ਕੋਈ ਦੇਸ਼ ਦਾ ਕੋਡ ਨਹੀਂ ਦਿੱਤਾ @@ -3784,12 +3855,12 @@ ਵਾਲਟ ਡਿ-ਐਕਟੀਵੇਟ ਕਰੋ ਤੁਹਾਡਾ ਬੈਲਨਸ - ਇਹ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਕਿ ਭੁਗਤਾਨਾਂ ਨੂੰ ਅਕਿਰਿਆਸ਼ੀਲ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਤੁਸੀਂ ਆਪਣੇ ਫੰਡ ਕਿਸੇ ਹੋਰ ਵਾਲੇਟ ਪਤੇ ’ਤੇ ਟ੍ਰਾਂਸਫ਼ਰ ਕਰ ਲਓ। ਜੇ ਤੁਸੀਂ ਹੁਣੇ ਆਪਣੇ ਫੰਡ ਟ੍ਰਾਂਸਫ਼ਰ ਨਾ ਕਰਨ ਦੀ ਚੋਣ ਕਰਦੇ ਹੋ, ਤਾਂ ਜੇ ਤੁਸੀਂ ਭੁਗਤਾਨਾਂ ਨੂੰ ਮੁੜ ਕਿਰਿਆਸ਼ੀਲ ਕਰੋਗੇ ਤਾਂ ਉਹ ਤੁਹਾਡੇ ਵਾਲੇਟ ਵਿੱਚ Molly ਨਾਲ ਜੁੜੇ ਰਹਿਣਗੇ। + ਤੁਹਾਨੂੰ ਇਹ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ ਕਿ ਭੁਗਤਾਨ ਫੀਚਰ ਅਕਿਰਿਆਸ਼ੀਲ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਤੁਸੀਂ ਆਪਣੇ ਫੰਡ ਕਿਸੇ ਹੋਰ ਵਾਲੇਟ ਉੱਤੇ ਟ੍ਰਾਂਸਫਰ ਕਰ ਲਓ। ਜੇ ਤੁਸੀਂ ਹੁਣੇ ਆਪਣੇ ਫੰਡ ਟ੍ਰਾਂਸਫਰ ਨਹੀਂ ਕਰਦੇ ਹੋ, ਤਾਂ ਜਦੋਂ ਤੁਸੀਂ ਭੁਗਤਾਨ ਫੀਚਰ ਨੂੰ ਮੁੜ ਕਿਰਿਆਸ਼ੀਲ ਕਰੋਗੇ ਤਾਂ ਉਹ ਫੰਡ ਤੁਹਾਡੇ Molly ਨਾਲ ਲਿੰਕ ਕੀਤੇ ਹੋਏ ਵਾਲੇਟ ਵਿੱਚ ਹੀ ਰਹਿਣਗੇ। ਬਾਕੀ ਬਕਾਇਆ ਟ੍ਰਾਂਸਫ਼ਰ ਕਰੋ ਟ੍ਰਾਂਸਫ਼ਰ ਕੀਤੇ ਬਿਨਾਂ ਅਕਿਰਿਆਸ਼ੀਲ ਕਰੋ ਅਕਿਰਿਆਸ਼ੀਲ ਕਰੋ ਟ੍ਰਾਂਸਫਰ ਕੀਤੇ ਬਿਨਾ ਅਕਿਰਿਆਸ਼ੀਲ ਕਰਨਾ ਹੈ? - ਜੇ ਤੁਸੀਂ ਭੁਗਤਾਨਾਂ ਨੂੰ ਮੁੜ-ਕਿਰਿਆਸ਼ੀਲ ਕਰਨ ਦੀ ਚੋਣ ਕਰਦੇ ਹੋ ਤਾਂ ਤੁਹਾਡਾ ਬਕਾਇਆ Molly ਨਾਲ ਜੁੜੇ ਤੁਹਾਡੇ ਵਾਲੇਟ ਵਿੱਚ ਰਹੇਗਾ। + ਜੇ ਤੁਸੀਂ ਭੁਗਤਾਨ ਫੀਚਰ ਨੂੰ ਮੁੜ-ਕਿਰਿਆਸ਼ੀਲ ਕਰਦੇ ਹੋ ਤਾਂ ਤੁਹਾਡਾ ਬੈਲੈਂਸ Molly ਨਾਲ ਲਿੰਕ ਕੀਤੇ ਵਾਲੇਟ ਵਿੱਚ ਹੀ ਰਹੇਗਾ। ਵਾਲੇਟ ਅਕਿਰਿਆਸ਼ੀਲ ਕਰਨ ਵਿੱਚ ਤਰੁੱਟੀ। @@ -4003,12 +4074,12 @@ ਪ੍ਰੋਫ਼ਾਈਲ ਬਣਾਓ - ਪਾਬੰਦੀ ਲੱਗਾ + ਪਾਬੰਦੀ ਲਗਾਈ ਗਈ %1$d ਸੰਪਰਕ ਸੁਨੇਹੇ ਲੈਣ-ਦੇਣ ਅਲੋਪ ਹੋਣ ਵਾਲੇ ਸੁਨੇਹੇ ਐਪ ਦੀ ਸੁਰੱਖਿਆ - Recents ਸੂਚੀ ਵਿੱਚ ਅਤੇ ਐਪ ਦੇ ਅੰਦਰ ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਬਲੌਕ ਕਰੋ + ਹਾਲੀਆ ਸੂਚੀ ਵਿੱਚ ਅਤੇ ਐਪ ਦੇ ਵਿੱਚ ਸਕ੍ਰੀਨਸ਼ਾਟਾਂ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓਨੂੰ ਪਾਬੰਦੀ ਲਗਾਓ Signal ਸੁਨੇਹੇ ਅਤੇ ਕਾਲਾਂ, ਹਮੇਸ਼ਾਂ ਕਾਲਾਂ ਰਿਲੇਅ ਕਰੋ, ਅਤੇ ਸੀਲਬੰਦ ਭੇਜਣ ਵਾਲਾ ਨਵੀਆਂ ਚੈਟਾਂ ਲਈ ਡਿਫੌਲਟ ਟਾਈਮਰ ਤੁਹਾਡੇ ਦੁਆਰਾ ਸ਼ੁਰੂ ਕੀਤੀਆਂ ਸਾਰੀਆਂ ਨਵੀਆਂ ਚੈਟਾਂ ਲਈ ਅਲੋਪ ਹੋਣ ਵਾਲੇ ਸੁਨੇਹੇ ਦਾ ਡਿਫੌਲਟ ਟਾਈਮਰ ਤੈਅ ਕਰੋ। @@ -4106,7 +4177,7 @@ ਹੁਣੇ ਨਹੀਂ - ਪ੍ਰਤਿਕਿਰਿਆਵਾਂ ਨੂੰ ਪਸੰਦ ਅਨੁਸਾਰ ਬਣਾਓ + ਰਿਐਕਸ਼ਨ ਕਸਟਮਾਈਜ਼ ਕਰੋ ਕਿਸੇ ਇਮੋਜੀ ਨੂੰ ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ ਰੀਸੈੱਟ ਕਰੋ ਸੰਭਾਲੋ @@ -4165,9 +4236,9 @@ ਕਾਲ - ਚੁੱਪ + ਮਿਊਟ ਕਰੋ - ਚੁੱਪ ਕਰਾਏ + ਮਿਊਟ ਕੀਤਾ ਗਿਆ ਖੋਜੋ ਅਲੋਪ ਹੋਣ ਵਾਲੇ ਸੁਨੇਹੇ @@ -4175,10 +4246,10 @@ ਸੰਪਰਕ ਵੇਰਵੇ ਸੁਰੱਖਿਆ ਨੰਬਰ ਵੇਖੋ - ਪਾਬੰਦੀ ਲਾਓ - ਗਰੁੱਪ ’ਤੇ ਪਾਬੰਦੀ ਲਾਓ + ਪਾਬੰਦੀ ਲਗਾਓ + ਗਰੁੱਪ ਉੱਤੇ ਪਾਬੰਦੀ ਲਗਾਓ ਪਾਬੰਦੀ ਹਟਾਓ - ਗਰੁੱਪ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ + ਗਰੁੱਪ ਉੱਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ ਕਿਸੇ ਗਰੁੱਪ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ ਸਾਰੇ ਵੇਖੋ ਮੈਂਬਰਾਂ ਨੂੰ ਸ਼ਾਮਲ ਕਰੋ @@ -4187,8 +4258,8 @@ ਗਰੁੱਪ ਲਿੰਕ ਸੰਪਰਕ ਵਜੋਂ ਸ਼ਾਮਲ ਕਰੋ ਅਨਮਿਊਟ ਕਰੋ - %1$s ਤਕ ਗੱਲਬਾਤ ਮਿਉਟ ਕੀਤੀ - ਗੱਲਬਾਤ ਹਮੇਸ਼ਾਂ ਲਈ ਮਿਊਟ ਕੀਤੀ + ਚੈਟ %1$s ਤੱਕ ਮਿਊਟ ਕੀਤੀ ਗਈ + ਚੈਟ ਹਮੇਸ਼ਾਂ ਲਈ ਮਿਊਟ ਕੀਤੀ ਗਈ ਫ਼ੋਨ ਨੰਬਰ ਨੂੰ ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕੀਤਾ। ਫੋਨ ਨੰਬਰ Signal ਨੂੰ ਆਪਣਾ ਸਹਿਯੋਗ ਦੇ ਕੇ ਆਪਣੀ ਪ੍ਰੋਫ਼ਾਈਲ ਲਈ ਬੈਜ ਪ੍ਰਾਪਤ ਕਰੋ। ਹੋਰ ਜਾਣਨ ਲਈ ਬੈਜ ਉੱਤੇ ਟੈਪ ਕਰੋ। @@ -4205,7 +4276,7 @@ ਸੂਚਨਾਵਾਂ ਨੂੰ ਮਿਊਟ ਕਰੋ - ਚੁੱਪ ਨਹੀਂ + ਮਿਊਟ ਨਹੀਂ ਹੈ ਹਵਾਲੇ ਹਮੇਸ਼ਾਂ ਸੂਚਿਤ ਕਰੋ ਸੂਚਿਤ ਨਾ ਕਰੋ @@ -4234,7 +4305,7 @@ ਹਟਾਓ - ਪਾਬੰਦੀ ਲਾਓ + ਪਾਬੰਦੀ ਲਗਾਓ ਕੀ %1$s ਨੂੰ ਹਟਾਉਣਾ ਹੈ? @@ -4330,7 +4401,7 @@ ਸਟੋਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ ਸੁਨੇਹਾ ਜੋੜੋ ਜਵਾਬ ਜੋੜੋ - ਇਸ ਨੂੰ ਭੇਜੋ + ਇਹਨਾਂ ਨੂੰ ਭੇਜੋ ਸੁਨੇਹੇ ਨੂੰ ਇੱਕ ਵਾਰ ਵੇਖੋ ਇੱਕ ਜਾਂ ਵੱਧ ਚੀਜ਼ਾਂ ਬਹੁਤ ਵੱਡੀਆਂ ਸਨ ਇੱਕ ਜਾਂ ਵੱਧ ਆਈਟਮਾਂ ਵਾਜਬ ਨਹੀਂ ਹਨ @@ -4508,7 +4579,7 @@ ਨੈੱਟਵਰਕ ਵਿੱਚ ਗੜਬੜੀ ਆਉਣ ਕਾਰਨ ਤੁਹਾਡਾ ਦਾਨ ਭੇਜ ਨਹੀਂ ਸਕੇ। ਆਪਣੇ ਕਨੈਕਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। - %1$s ਨੂੰ ਦਾਨ + %1$s ਦੀ ਤਰਫ਼ ਤੋਂ ਦਾਨ %1$s ਨੇ ਤੁਹਾਡੀ ਤਰਫ਼ ਤੋਂ Signal ਨੂੰ ਦਾਨ ਦਿੱਤਾ ਹੈ @@ -4907,7 +4978,7 @@ ਜਵਾਬ ਅਤੇ ਰਿਐਕਸ਼ਨ - ਜਵਾਬ ਅਤੇ ਰਿਐਕਸ਼ਨ ਭੇਜਣ ਦੀ ਮਨਜ਼ੂਰੀ ਦਿਓ + ਜਵਾਬ ਅਤੇ ਰਿਐਕਸ਼ਨ ਭੇਜਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ ਜਿਹੜੇ ਲੋਕ ਤੁਹਾਡੀ ਸਟੋਰੀ ਦੇਖ ਸਕਦੇ ਹਨ, ਉਹਨਾਂ ਨੂੰ ਜਵਾਬ ਅਤੇ ਰਿਐਕਸ਼ਨ ਭੇਜਣ ਦੀ ਸਹੂਲਤ ਦਿਓ @@ -5087,7 +5158,7 @@ ਦਾਨ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ - ਇਸ ਨੂੰ ਭੇਜੋ + ਇਹਨਾਂ ਨੂੰ ਭੇਜੋ ਪ੍ਰਾਪਤਕਰਤਾ ਨੂੰ ਸਿੱਧਾ ਚੈਟ ਦੇ ਵਿੱਚ ਦਾਨ ਦੇਣ ਬਾਰੇ ਸੂਚਿਤ ਕੀਤਾ ਜਾਵੇਗਾ। ਹੇਠਾਂ ਆਪਣਾ ਸੁਨੇਹਾ ਸ਼ਾਮਲ ਕਰੋ। @@ -5601,5 +5672,15 @@ ਵਰਤੋਂਕਾਰ ਨਾਂ ਮਿਟਾਓ + + + ਘੰ + + ਮਿੰ + + ਸੈੱਟ ਕਰੋ + + ਸਕ੍ਰੀਨ ਲਾਕ ਲੱਗਣ ਤੋਂ ਪਹਿਲਾਂ ਘੱਟੋ-ਘੱਟ ਸਮਾਂ 1 ਮਿੰਟ ਹੈ। + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cf59331060..903b67ce72 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -14,6 +14,7 @@ + Tak Nie @@ -153,7 +154,7 @@ Zablokuj otrzymywanie informacji o zmianach i nowościach w Signal. Wznów otrzymywanie informacji i nowości Signal. - Odblokować %1$s? + Odblokować: %1$s? Zablokuj Zablokuj i opuść Zgłoś spam i zablokuj @@ -366,7 +367,7 @@ Błąd przy wysyłaniu multimediów - Zgłoszono, jako spam i zablokowano. + Zgłoszono jako spam i zablokowano. Na tę chwilę obsługa SMS-ów jest wyłączona. Możesz wyeksportować swoje wiadomości do innej aplikacji w telefonie. @@ -467,7 +468,7 @@ Anuluj - Zablokowani + Zablokowano Usuń filtr @@ -502,14 +503,14 @@ Przeczytana Przeczytane - Przeczytane - Przeczytane + Przeczytanych + Przeczytanych Nieprzeczytana Nieprzeczytane - Nieprzeczytane - Nieprzeczytane + Nieprzeczytanych + Nieprzeczytanych Przypnij @@ -530,10 +531,10 @@ Wycisz - Cofnij wyciszenie - Cofnij wyciszenie - Cofnij wyciszenie - Cofnij wyciszenie + Anuluj wyciszenie + Anuluj wyciszenie + Anuluj wyciszenie + Anuluj wyciszenie Zaznacz @@ -582,6 +583,15 @@ +%1$d + + Połącz ponownie urządzenia + + Dodane przez Ciebie urządzenia zostały rozłączone z powodu wyrejestrowanego urządzenia. Przejdź do ustawień, aby ponownie połączyć urządzenia. + + Otwórz ustawienia + + Później + Wybierz członków @@ -1011,7 +1021,7 @@ Powiadom mnie o wzmiankach - Powiadamiać, gdy zostaniesz wspomniany(a) w wyciszonych rozmowach? + Powiadamiać, gdy zostaniesz wspomniany(-a) w wyciszonych rozmowach? Zawsze powiadamiaj Nie powiadamiaj @@ -1029,6 +1039,16 @@ Utworzono nazwę użytkownika Skopiowano nazwę użytkownika + + Nie udało się skasować nazwy użytkownika. Spróbuj ponownie później. + + Nazwa użytkownika została skasowana + + + + Wystąpił błąd z Twoją nazwą użytkownika — nie jest już przypisana do Twojego konta. Możesz spróbować ustawić ją ponownie lub wybrać nową nazwę. + + Napraw błąd teraz @@ -1252,8 +1272,8 @@ Nowa grupa Zaproś znajomych Użyj SMS - Wygląd - Dodaj zdjęcie + Kolory czatu + Dodaj zdjęcie profilowe Odpowiedzi @@ -1588,7 +1608,7 @@ Odblokuj Pozwolić %1$s wysyłać do Ciebie wiadomości i udostępnić Twoje imię i zdjęcie temu kontaktowi? Nie będzie on wiedzieć, że przeczytałeś(aś) tę wiadomość, dopóki nie zaakceptujesz. - Pozwolić %1$s wysyłać do Ciebie wiadomości i udostępnić Twoje imię i zdjęcie temu kontaktowi? Nie otrzymasz żadnych wiadomości, dopóki nie odblokujesz. + Pozwolić %1$s wysyłać do Ciebie wiadomości i udostępnić Twoje imię i zdjęcie? Nie otrzymasz żadnych wiadomości, dopóki nie odblokujesz tego kontaktu. Pozwolić %1$s wysyłać Ci wiadomości? Nie otrzymasz wiadomości od tego użytkownika, dopóki go nie odblokujesz. Czy chcesz otrzymywać informacje i nowości od %1$s? Nie otrzymasz żadnych informacji, dopóki nie odblokujesz tego kontaktu. @@ -1599,7 +1619,7 @@ Czy chcesz dołączyć do tej grupy i udostępnić swoje imię i zdjęcie jej członkom? Członkowie grupy nie będą wiedzieć, że przeczytałeś(aś) ich wiadomość, dopóki nie zaakceptujesz. Czy chcesz dołączyć do tej grupy i udostępnić swoje imię i zdjęcie jej członkom? Nie zobaczysz ich wiadomości, dopóki nie zaakceptujesz. Czy chcesz dołączyć do tej grupy? Członkowie grupy nie będą wiedzieć, że przeczytałeś(aś) ich wiadomość, dopóki nie zaakceptujesz. - Czy chcesz odblokować tę grupę i udostępnić swoje imię i zdjęcie jej członkom? Nie otrzymasz żadnych wiadomości, dopóki nie odblokujesz. + Czy chcesz odblokować tę grupę i udostępnić swoje imię i zdjęcie jej członkom? Nie otrzymasz żadnych wiadomości, dopóki nie odblokujesz tej grupy. Zobacz Członek %1$s @@ -1712,6 +1732,17 @@ Utwórz nowy PIN + + Wyślij kod SMS + + Rejestracja Signal - Potrzebna pomoc z ponowną rejestracją za pomocą kodu PIN dla systemu Android + + Twój PIN to składający się z %1$d lub więcej znaków, utworzony przez Ciebie kod, który może być liczbowy lub alfanumeryczny.\n\nJeśli nie pamiętasz swojego kodu PIN, możesz utworzyć nowy. + + Jeśli nie pamiętasz swojego kodu PIN, możesz utworzyć nowy. + + Wyczerpano dozwoloną liczbę prób odgadnięcia kodu PIN, ale nadal można uzyskać dostęp do konta Signal, tworząc nowy kod PIN. + Uwaga Jeśli wyłączysz PIN, przy ponownej rejestracji Signal stracisz wszystkie dane, chyba że ręcznie wykonasz i przywrócisz kopię zapasową. Gdy PIN jest wyłączony, nie możesz włączyć blokady rejestracji. @@ -1866,12 +1897,12 @@ - %1$s jest zablokowany(a) + %1$s jest zablokowany(-a) Więcej informacji Nie odbierzesz audio i wideo od tej osoby, a ona nie odbierze Twojego. Nie można odebrać audio i wideo od %1$s Nie można odebrać audio i wideo od %1$s - To może być spowodowane tym, że ta osoba nie zweryfikowała Twojego numeru bezpieczeństwa, problemu z urządzeniem, albo Cię zablokowała. + To może być spowodowane tym, że ta osoba nie zweryfikowała Twojego numeru bezpieczeństwa, ma problem z urządzeniem albo Cię zablokowała. Przesuń, aby zobaczyć udostępniony ekran @@ -1908,11 +1939,18 @@ Signal potrzebuje dostępu do Twoich kontaktów i plików multimedialnych, aby ułatwić Ci połączenia ze znajomymi i wymianę wiadomości. Twoje kontakty są przesyłane z użyciem prywatnego wyszukiwania kontaktów Signal, co oznacza, że są zaszyfrowane metodą end-to-end i niedostępne dla usługi Signal. Signal potrzebuje dostępu do Twoich kontaktów, aby ułatwić Ci połączenia ze znajomymi. Twoje kontakty są przesyłane z użyciem prywatnego wyszukiwania kontaktów Signal, co oznacza, że są zaszyfrowane metodą end-to-end i niedostępne dla usługi Signal. Zbyt dużo nieudanych prób rejestracji tego numeru. Spróbuj ponownie później. + + Zbyt dużo nieudanych prób rejestracji tego numeru. Spróbuj ponownie za %1$s. Nie można połączyć się z serwisem. Proszę sprawdzić połączenie internetowe i spróbować ponownie. Niestandardowy format numeru Podany przez Ciebie numer (%1$s) wydaje się mieć niestandardowy format.\n\nCzy chodziło Ci o %2$s? Molly Android - Format numeru telefonu + Poproszono o połączenie + + Zażądano SMS + + Zażądano kodu weryfikacyjnego Jesteś o %1$d krok od wysłania dziennika debugowania. Jesteś o %1$d kroki od wysłania dziennika debugowania. @@ -1934,6 +1972,16 @@ Zadzwoń Kod weryfikacyjny Wyślij ponownie kod + + Masz problem z rejestracją? + + • Upewnij się, że Twój telefon ma połączenie z siecią komórkową potrzebną do odbioru wiadomości SMS lub połączenia\n • Potwierdź, że możesz odebrać połączenie telefoniczne na ten numer\n • Sprawdź, czy wprowadzono poprawny numer telefonu. + + Wciąż potrzebujesz pomocy? Wykonaj podane kroki naprawcze lub skontaktuj się z naszym działem technicznym + + podane kroki naprawcze + + Kontakt z działem technicznym Włączyć blokadę rejestracji? @@ -2093,13 +2141,17 @@ Odebrałeś(-aś) odznakę - Zareagował(a) tak: %1$s na Twoją relację + Zareagował(a) tak %1$s na Twoją relację - Zareagował(a) tak: %1$s na relację + Zareagował(a) tak %1$s na relację Płatność Zaplanowana wiadomość + + Historia wiadomości została połączona + + %1$s należy do: %2$s Aktualizacja Molly @@ -2238,6 +2290,8 @@ Zareagował(a) tak %1$s na Twój plik. Zareagował(a) tak %1$s na Twoje audio. Zareagował(a) tak %1$s na Twoje multimedia jednorazowe. + + Zareagował(a) tak %1$s na Twoją płatność. Zareagował(a) tak %1$s na Twoją naklejkę. Ta wiadomość została usunięta. @@ -2653,7 +2707,7 @@ Problem z dostarczeniem wiadomości - Nie udało się dostarczyć Ci wiadomości, naklejki, reakcji lub potwierdzenia przeczytania od %1$s. Ten kontakt mógł próbować przesłać Ci tę wiadomość, bezpośrednio lub w rozmowie grupowej. + Nie udało się dostarczyć Ci wiadomości, naklejki, reakcji lub potwierdzenia przeczytania od %1$s. Ten kontakt mógł próbować przesłać Ci tę wiadomość bezpośrednio lub w rozmowie grupowej. Nie udało się dostarczyć Ci wiadomości, naklejki, reakcji lub potwierdzenia przeczytania od %1$s. @@ -3471,6 +3525,8 @@ Wpisz swój PIN Wpisz kod PIN, który utworzyłeś(aś) dla swojego konta. Ten kod nie jest Twoim kodem weryfikacyjnym SMS. + + Wprowadź kod PIN utworzony dla tego konta. Wpisz alfanumeryczny PIN Wpisz liczbowy PIN Nieprawidłowy PIN. Spróbuj ponownie. @@ -3584,7 +3640,10 @@ Twoja kopia zapasowa zawiera bardzo duży plik, który nie może zostać zarchiwizowany. Usuń go i utwórz nową kopię zapasową. Stuknij, aby zarządzać kopiami zapasowymi. Błędny numer? + Zadzwoń do mnie (%1$02d:%2$02d) + + Wyślij kod ponownie (%1$02d:%2$02d) Skontaktuj się ze wsparciem technicznym Signal Rejestracja Signal - Kod weryfikacyjny dla systemu Android Niepoprawny kod @@ -3592,6 +3651,18 @@ Nieznane Zobaczyć mój numer telefonu Znaleźć mnie po numerze telefonu + + Numer telefonu + + Zdecyduj, kto może zobaczyć Twój numer telefonu i kto może go użyć do skontaktowania się z Tobą w Molly. + + Kto może zobaczyć mój numer + + Nikt nie zobaczy Twojego numeru telefonu w Molly. + + Kto może mnie znaleźć po numerze telefonu + + Twój numer będzie widoczny dla osób i grup, z którymi wymieniasz wiadomości. Osoby, które mają Twój numer w kontaktach, będą również widzieć go w Molly. Wszyscy Moje kontakty Nikt @@ -3801,9 +3872,9 @@ %1$s/%2$s - Zablokowano \"%1$s\". - Nie udało się zablokować \"%1$s\" - Odblokowano \"%1$s\". + Zablokowano „%1$s”. + Nie udało się zablokować „%1$s” + Odblokowano „%1$s”. Przejrzyj członków @@ -3860,14 +3931,14 @@ Usunięcie Twojego konta spowoduje: Podaj swój numer telefonu Usuń konto - Usuń informacje o swoim koncie i zdjęcie profilowe - Usuń wszystkie swoje wiadomości + Usunięcie informacji o Twoim koncie i zdjęcia profilowego + Usunięcie wszystkich Twoich wiadomości Usuń %1$s na Twoim koncie płatności Nie podano kodu kraju Nie podano numeru Podany numer telefonu nie należy do Twojego konta. Czy na pewno chcesz usunąć swoje konto? - To działanie usunie Twoje konto Signal i zresetuje aplikację. Po zakończeniu tego procesu aplikacja zostanie zamknięta. + To działanie spowoduje usunięcie Twojego konta Signal i reset aplikacji. Po zakończeniu tego procesu aplikacja zostanie zamknięta. Nie udało się usunąć lokalnych danych. Możesz usunąć je ręcznie w systemowych ustawieniach aplikacji. Otwórz Ustawienia aplikacji @@ -3976,7 +4047,7 @@ Dezaktywuj portfel Twoje środki - Przed dezaktywacją płatności, zalecamy przeniesienie środków do innego portfela. Jeśli postanowisz nie przenosić teraz swoich środków, pozostaną one w Twoim portfelu, powiązanym z Molly, jeśli reaktywujesz płatności. + Przed dezaktywacją płatności zalecamy przeniesienie środków do innego portfela. Jeśli postanowisz nie przenosić teraz swoich środków, pozostaną one w Twoim portfelu powiązanym z Molly, jeśli reaktywujesz płatności. Przenieś pozostałe środki Dezaktywuj bez przenoszenia Dezaktywuj @@ -4197,12 +4268,12 @@ Utwórz profil - Zablokowani + Zablokowano %1$d kontakty(ów) Wiadomości Znikające wiadomości Bezpieczeństwo aplikacji - Blokuj rzuty ekranu na liście aktywnych oraz w aplikacji + Blokuj zrzuty ekranu na liście aktywnych oraz w aplikacji Wiadomości i połączenia Signal, zawsze przekazuj połączenia i ukryty nadawca Domyślny czas dla nowych rozmów Ustaw domyślny czas znikania wiadomości dla wszystkich, nowych, rozpoczętych przez Ciebie rozmów. @@ -4714,7 +4785,7 @@ Nie udało się wysłać darowizny z powodu błędu sieci. Sprawdź jakość łącza i spróbuj ponownie. - Darowizna w imieniu %1$s + Darowizna w imieniu: %1$s %1$s wpłacił(a) w Twoim imieniu darowiznę na rzecz Signal @@ -5127,7 +5198,7 @@ Pozwalaj na odpowiedzi i reakcje - Pozwól ludziom, oglądającym Twoją relację, zareagować i odpowiedzieć + Pozwól osobom oglądającym Twoją relację zareagować i odpowiedzieć Kontakty Signal @@ -5277,7 +5348,7 @@ - Zareagowałeś(aś) na relację od %1$s + Zareagowałeś(-aś) na relację od %1$s Zareagowano na Twoją relację @@ -5851,5 +5922,15 @@ Usuń nazwę użytkownika + + + g. + + m. + + Ustaw + + Najkrótszy czas do zablokowania ekranu to 1 minuta. + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index bd25793d1c..7f34b70852 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -14,6 +14,7 @@ + Sim Não @@ -138,8 +139,8 @@ Continuar - Bloquear e sair %1$s? - Bloquear %1$s ? + Bloquear e sair de %1$s? + Bloquear %1$s? Você deixará de receber as mensagens e atualizações do grupo, e nenhum membro poderá adicionar você ao grupo novamente. Os membros do grupo não poderão adicionar você novamente. Os membros poderão adicionar você a este grupo novamente @@ -153,7 +154,7 @@ Bloqueie o recebimento de atualizações e notícias do Signal. Voltar a receber atualizações e notícias do Signal. - Desbloquear %1$s ? + Desbloquear %1$s? Bloquear Bloquear e sair Denunciar spam e bloquear @@ -455,7 +456,7 @@ Cancelar - Bloqueada + Bloqueado Limpar filtro @@ -542,6 +543,15 @@ + %1$d + + Reconecte seus dispositivos + + Os dispositivos que você adicionou foram desconectados quando o registro do seu dispositivo foi cancelado. Abra as configurações para conectar os dispositivos novamente. + + Abrir configurações + + Mais tarde + Selecionar membros @@ -935,7 +945,7 @@ Notifique-me ao ser mencionado - Receber notificações quando você for mencionado em conversas silenciadas? + Receber notificações quando mencionarem você em conversas silenciadas? Notifique-me sempre Não me notifique @@ -953,6 +963,16 @@ Nome de usuário criado Nome de usuário copiado + + Não foi possível apagar o nome de usuário. Tente novamente mais tarde. + + Nome de usuário excluído + + + + Algo deu errado com seu nome de usuário, pois ele não está mais atribuído à sua conta. Você pode tentar defini-lo de novo ou escolher outro. + + Corrigir agora @@ -1156,8 +1176,8 @@ Novo grupo Convidar amigos(as) Usar SMS - Aparência - Adicionar foto + Cores do chat + Adicionar uma foto de perfil Respostas @@ -1216,9 +1236,9 @@ Chamada de vídeo não atendida - Recebendo chamada de voz + Chamada de voz recebida - Recebendo chamada de vídeo + Chamada de vídeo recebida Chamada de voz perdida @@ -1472,10 +1492,10 @@ Desbloquear Você aceita que %1$s lhe envie uma mensagem, saiba seu nome e veja sua foto? Essa pessoa não saberá que você viu a mensagem dela ao menos que você aceite. - Permitir que %1$s envie mensagens para você e enxergue o seu nome e foto? Você não receberá mensagens dessa pessoa até que a desbloqueie. + Permitir que %1$s envie mensagens para você e veja o seu nome e foto? Você não receberá mensagens dessa pessoa até que a desbloqueie. - Quer permitir que %1$s envie mensagens para você? Você não receberá nenhuma mensagem até que desbloqueie essa pessoa. - Receba atualizações e notícias de %1$s? Você não receberá nenhuma atualização até que as desbloqueie. + Permitir que %1$s envie mensagens para você? Você não receberá mensagens dessa pessoa até que a desbloqueie. + Receber atualizações e novidades de %1$s? Você não receberá nenhuma atualização dessa pessoa até que a desbloqueie. Continuar sua conversa com este grupo e exibir seu nome e foto para os membros? Atualize este grupo para ativar novos recursos como @menções e administradores. Os membros que não compartilharam seus nomes ou fotos neste grupo serão convidados a participar. Este Grupo Legado não pode mais ser usado porque é muito grande. O tamanho máximo de grupos é %1$d. @@ -1483,7 +1503,7 @@ Você quer entrar nesse grupo e exibir seu nome e foto para os membros dele? Eles não saberão que você visualizou as mensagens deles até que você aceite. Participe nesse grupo e compartilhe seu nome e foto com os membros dele? Não poderá ver as mensagens deles até que você aceite. Entrar nesse grupo? Eles não saberão que você leu as mensagens deles até que você aceite. - Você quer desbloquear esse grupo e compartilhar seu nome e foto com os membros dele? Você não receberá nenhuma mensagem deles até que desbloqueie o grupo. + Você quer desbloquear esse grupo e exibir seu nome e foto para os seus membros? Você não receberá nenhuma mensagem até que desbloqueie o grupo. Exibir Membro de %1$s @@ -1584,9 +1604,20 @@ Criar novo PIN + + Enviar código SMS + + Registro no Signal - Preciso de ajuda para cadastrar novamente o PIN para Android + + Seu PIN é um código com no mínimo %1$d dígitos e pode ser numérico ou alfanumérico.\n\nSe não conseguir lembrar seu PIN, crie um novo. + + Se não conseguir lembrar seu PIN, crie um novo. + + Você esgotou suas tentativas para acertar o PIN, mas ainda pode acessar sua conta do Signal. Para isso, crie um PIN novo. + Atenção - Se você desativar o PIN, você perderá todos os dados ao se registrar de novo com o Signal, a menos que você faça backup manualmente e restaure. Você não pode ativar o Bloqueio de Registro enquanto o PIN estiver desativado. + Se desativar o PIN, você perderá todos os dados ao se registrar de novo com o Signal, a menos que faça backup e restaure manualmente. Você não pode ativar o Bloqueio de Registro enquanto o PIN estiver desativado. Desabilitar PIN @@ -1711,7 +1742,7 @@ Câmera - Ativar microfone + Reativar notificações Silenciar @@ -1726,12 +1757,12 @@ - %1$s está bloqueado + Você bloqueou %1$s Detalhes Você não receberá o áudio ou o vídeo dessa pessoa, e ela não receberá os seus. Não é possível receber áudio e vídeo de %1$s Não é possível receber áudio e vídeo de %1$s - Isso pode acontecer porque: ou essa pessoa não verificou a alteração do seu número de segurança, ou há um problema com o dispositivo dela, ou ela bloqueou você. + Isso pode acontecer porque: essa pessoa não verificou a alteração do seu número de segurança; há um problema com o dispositivo dela; ela bloqueou você. Deslize para ver o compartilhamento de tela @@ -1768,11 +1799,18 @@ O Signal precisa de acesso aos seus contatos e arquivos de mídia para você poder se conectar com outras pessoas e enviar mensagens. Você encontrará os seus contatos por meio da criptografia de ponta a ponta, de modo que os responsáveis pelo Signal jamais armazenarão ou sequer terão acesso à sua lista de contatos. O Signal precisa acessar sua lista de contatos para você se conectar com outras pessoas. Você encontrará os seus contatos por meio da criptografia de ponta a ponta, de modo que os responsáveis pelo Signal jamais armazenarão ou sequer terão acesso à sua lista de contatos. Você fez muitas tentativas de registrar este número. Por favor, tente novamente mais tarde. + + Você tentou cadastrar esse número muitas vezes. Tente novamente em %1$s. Não é possível conectar ao serviço. Favor verificar a conexão à rede e tentar novamente. Formato de número de telefone não reconhecido O número que você inseriu (%1$s) não parece estar no formato padrão de números de telefone.\n\nSerá que você quis dizer %2$s? Molly Android - Formato de números de telefone + Ligação solicitada + + SMS solicitada + + Código de verificação solicitado Agora você está a %1$d passo de enviar um registro de depuração. Agora você está a %1$d passos de enviar um registro de depuração. @@ -1792,6 +1830,16 @@ Ligar Código de verificação Reenviar código + + Está enfrentando problemas para se cadastrar? + + • Confira se seu telefone tem sinal para receber um SMS ou chamada • Confirme que pode receber uma ligação para este número\n • Confira se inseriu o número de telefone corretamente. + + Para mais informações, siga os passos de resolução de problemas abaixo ou entre em contato com a equipe de suporte + + passos de resolução de problemas + + Entre em contato com a equipe de suporte do Signal Habilitar Desbloqueio de registro? @@ -1958,6 +2006,10 @@ Pagamento Mensagem programada + + Seu histórico de mensagens foi mesclado + + %1$s pertence a %2$s Atualização do Molly @@ -2093,7 +2145,9 @@ Reagiu com %1$s ao seu GIF. Reagiu com %1$s ao seu arquivo. Reagiu com %1$s ao seu áudio. - Reagiu com %1$s à sua mídia efêmera. + Reagiu com %1$s à sua mídia temporária. + + Reagiu com %1$s ao seu pagamento. Reagiu com %1$s à sua figurinha. Esta mensagem foi apagada. @@ -2487,7 +2541,7 @@ Problema na entrega - Não foi possível entregar para você uma mensagem (texto, figurinha, reação ou confirmação de leitura) de %1$s. Essa pessoa tentou enviar para você diretamente ou em um grupo. + Não foi possível entregar para você uma mensagem (texto, figurinha, reação ou confirmação de leitura) de %1$s. Essa pessoa tentou entrar em contato com você diretamente ou em um grupo. Não foi possível entregar para você uma mensagem (texto, figurinha, reação ou confirmação de leitura) de %1$s. @@ -2555,7 +2609,7 @@ Pendente - Enviada para + Enviado para Enviada de Entregue a Lida por @@ -2757,7 +2811,7 @@ Configurações avançadas do PIN Chamadas e mensagens privadas gratuitas para usuários do Signal Enviar registro de depuração - Excluir conta + Excluir a conta Modo de compatibilidade \'WiFi Calling\' Habilite se o seu dispositivo usa entrega de SMS/MMS via WiFi (somente habilite quando \'WiFi Calling\' estiver habilitado no seu dispositivo) Teclado incógnito @@ -3128,7 +3182,7 @@ - Reativar som + Reativar notificações Silenciar notificações @@ -3295,6 +3349,8 @@ Digite seu PIN Digite o PIN que você criou para sua conta. Ele é diferente do seu código de verificação por SMS. + + Insira o PIN que criou para sua conta. Digite o PIN alfanumérico Digite o PIN numérico PIN incorreto. Tente novamente. @@ -3398,7 +3454,10 @@ Seu backup contém um arquivo muito grande não compatível com backup. Apague e crie um backup novo. Toque para gerenciar os backups. Número errado? + Ligue pra mim (%1$02d:%2$02d) + + Reenviar código (%1$02d:%2$02d) Entre em contato com o Suporte do Signal Registro Signal - Código de verificação para Android Código incorreto @@ -3406,6 +3465,18 @@ Desconhecida Ver meu número de telefone Podem me encontrar por número de telefone + + Número de telefone + + Decida quem pode encontrar seu número e quem pode entrar em contato com você pelo Molly usando seu número. + + Quem pode ver meu número? + + Ninguém verá seu número de telefone no Molly + + Quem pode encontrar meu número + + Seu número de telefone estará visível para as pessoas e grupos para os quais você envia mensagens. As pessoas que têm o seu número nos contatos também vão ver seu perfil no Molly. Todo mundo Meus contatos Ninguém @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" foi bloqueado. + Você bloqueou \"%1$s\". Falha ao bloquear \"%1$s\" - \"%1$s\" foi desbloqueado. + Você desbloqueou \"%1$s\". Revisar os membros @@ -3667,17 +3738,17 @@ Sinal de Wi-Fi fraco - mudando para rede de celular. - Excluir sua conta vai: + Ao excluir sua conta, você vai: Digite o seu número de telefone - Excluir conta + Excluir a conta Excluir suas informações da conta e foto de perfil - Apagar todas as suas mensagens + Excluir todas as suas mensagens Apagar %1$s da sua conta de pagamentos Nenhum código do país informado Nenhum número informado O número de telefone que você inseriu não corresponde ao da sua conta. Tem certeza que deseja excluir a sua conta? - Essa ação excluirá a sua conta do Signal e apagará os dados do aplicativo. O aplicativo será fechado após a conclusão do processo. + Essa ação excluirá a sua conta do Signal e redefinirá os dados do app. O app será fechado após a conclusão do processo. Falha ao excluir os dados locais. Você pode limpá-los manualmente nas configurações do aplicativo no sistema. Acessar as configurações do aplicativo @@ -3784,12 +3855,12 @@ Desativar carteira Seu saldo - É recomendável que você transfira o seu saldo para outro endereço de carteira antes de desativar o módulo de pagamentos. Se você optar por não transferir o seu saldo agora, ele permanecerá em sua carteira vinculada à sua conta do Molly, mas estará suspenso para utilização até o momento em que você decida reativar o módulo de pagamentos. + É recomendável que você transfira o seu saldo para outro endereço de carteira antes de desativar o módulo de pagamentos. Se você optar por não transferir o seu saldo agora, ele permanecerá em sua carteira vinculada à conta do Molly, mas estará suspenso para utilização até que você decida reativar o módulo de pagamentos. Transferir saldo remanescente Desativar sem transferir Desativar Desativar sem transferir? - Seu saldo permanecerá em sua carteira vinculada à sua conta do Molly, mas estará suspenso para utilização até o momento em que você decida reativar o módulo de pagamentos. + Seu saldo permanecerá na carteira vinculada à sua conta do Molly, mas estará suspenso para utilização até que você decida reativar o módulo de pagamentos. Erro ao desativar a carteira. @@ -4003,7 +4074,7 @@ Criar perfil - Bloqueada + Bloqueados %1$d contatos Mensagens Mensagens efêmeras @@ -4167,7 +4238,7 @@ Silenciar - Silenciado/a + Silenciado(a) Pesquisar Mensagens efêmeras @@ -4242,7 +4313,7 @@ %1$s foi removido - %1$s foi bloqueado + Você bloqueou %1$s Não foi possível remover %1$s @@ -4465,7 +4536,7 @@ Sua doação mensal recorrente foi cancelada porque não foi possível processar seu pagamento. Seu selo não é mais visível no seu perfil. Sua doação mensal recorrente foi cancelada. %1$s O %2$s selo não está mais visível no seu perfil. - Você ainda pode usar o Signal, mas para apoiar o aplicativo e ter esse selo novamente, faça uma doação mensal. + Você ainda pode usar o Signal, mas que tal renovar agora para apoiar o app e ganhar esse selo novamente? Fazer doação mensal Vá ao Google Pay @@ -4508,7 +4579,7 @@ Não foi possível enviar sua doação devido a um erro de rede. Verifique a sua conexão e tente novamente. - Doação para %1$s + Doação em nome de %1$s %1$s fez uma doação para o Signal no seu nome @@ -4905,9 +4976,9 @@ Escolha quem pode ver seu story. As alterações não afetarão os stories que você já enviou. - Respostas & reações + Respostas e reações - Permitir respostas & reações + Permitir respostas e reações Permitir que as pessoas que podem ver o seu story possam reagir e responder @@ -5601,5 +5672,15 @@ Excluir nome de usuário + + + h + + min + + Definir + + O tempo mínimo antes da aplicação do bloqueio de tela é de 1 minuto. + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index c11a9a12a7..8444549380 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -14,6 +14,7 @@ + Sim Não @@ -155,7 +156,7 @@ Retomar a obtenção de atualizações e novidades do Signal. Desbloquear %1$s? Bloquear - Bloquear e abandonar + Bloquear e sair Reportar spam e bloquear @@ -500,8 +501,8 @@ Silenciar - Remover do silêncio - Remover do silêncio + Anular silêncio + Anular silêncio Selecionar @@ -542,6 +543,15 @@ +%1$d + + Revincule os seus dispositivos + + Os dispositivos que adicionou foram desvinculados quando o dispositivo não estava registado. Aceda às Definições para revincular dispositivos. + + Abrir definições + + Mais tarde + Selecionar membros @@ -953,6 +963,16 @@ Nome de utilizador criado Nome de utilizador copiado + + Não foi possível eliminar o nome de utilizador. Tente novamente mais tarde. + + Nome de utilizador eliminado + + + + Ocorreu algo errado com o seu nome de utilizador, já não está atribuído à sua conta. Pode voltar a configurá-lo ou pode escolher um novo. + + Resolver agora @@ -1156,8 +1176,8 @@ Novo grupo Convidar amigos Utilizar SMS - Aspeto - Adicionar fotografia + Cor dos chats + Adicionar uma fotografia de perfil Respostas @@ -1472,10 +1492,10 @@ Desbloquear Permite que %1$s lhe envie mensagens e que seja partilhado o seu nome e fotografia com ele(a)? Ele(a) não saberá que viu a mensagem dele(a) até que aceite. - Permite que %1$s lhe envie mensagens e que seja partilhado o seu nome e fotografia com ele(a)? Ele(a) não irá receber nenhuma mensagem até que o(a) desbloqueie. + Quer partilhar o seu nome e foto com o utilizador %1$s e permitir que este lhe envie mensagens? Não receberá nenhuma mensagem deste utilizador até o desbloquear. - Permite que %1$s lhe envie mensagens? Não irá receber nenhuma mensagem até que o(a) desbloqueie. - Obter atualizações e novidades a partir de %1$s? Não receberá nenhuma atualização até que as desbloqueie. + Quer deixar que %1$s lhe envie mensagens? Não receberá nenhuma mensagem deste utilizador até o desbloquear. + Obter atualizações e novidades de %1$s? Não receberá nenhuma atualização desse utilizador até o desbloquear. Continuar a sua conversa com este grupo e partilhar o seu nome e fotografia com os seus membros? Faça o upgrade deste grupo para ativar os recursos novos como @menções e os administradores. Os membros que não partilharam os seus nomes ou fotografias neste grupo serão convidados a juntarem-se. Este \'Grupo legado\' deixou de poder ser utilizado porque é demasiadamente grande. O tamanho máximo do grupo é de %1$d. @@ -1483,7 +1503,7 @@ Deseja juntar-se a este grupo e partilhar o seu nome e fotografia com os seus membros? Eles não sabem que viu as mensagens deles até que aceite. Juntar-se a este grupo e partilhar o seu nome e fotografia com estes membros? Você não irá ver as mensagens deles até que você aceite. Juntar-se a este grupo? Eles não saberão que viu as mensagens deles até que aceite o convite. - Deseja desbloquear este grupo e partilhar o seu nome e fotografia com os seus membros? Você não receberá nenhuma mensagem até que o desbloqueie. + Deseja desbloquear este grupo e partilhar o seu nome e fotografia com os seus membros? Não receberá nenhuma mensagem até o desbloquear. Ver Membro de %1$s @@ -1584,9 +1604,20 @@ Criar PIN novo + + Enviar código SMS + + Registo do Signal - Necessita de ajuda para voltar a registar o PIN para Android + + O seu PIN é um código de %1$d ou mais dígitos por si criado e pode ser numérico ou alfanumérico.\n\nSe não se lembrar do seu PIN, pode criar um novo. + + Se não se lembrar do seu PIN, pode criar um novo. + + Já esgotou as tentativas de PIN, mas ainda pode aceder à sua conta Signal criando um PIN novo. + Aviso - Se você desativar o PIN, todos os dados serão perdidos ao registar-se novamente no Sinal, a menos que faça uma cópia de segurança e o restaure manualmente. Você não pode ativar o \'Bloqueio de registo\' enquanto o PIN estiver desativado. + Se desativar o PIN, todos os dados serão perdidos ao registar-se novamente no Signal, a menos que faça uma cópia de segurança e o restaure manualmente. Não pode ativar o Bloqueio de registo enquanto o PIN estiver desativado. Desativar PIN @@ -1711,7 +1742,7 @@ Câmara - Alertar + Desativar o silêncio Silenciar @@ -1726,12 +1757,12 @@ - %1$s está bloqueado(a) + O utilizador %1$s está bloqueado Mais informação Não irá receber os áudios e vídeos deles e eles não receberão os seus. Não pode receber áudio e vídeo de %1$s Não pode receber áudio e vídeo de %1$s - Isto poderá dever-se a ele(s) ainda não ter(em) verificado a alteração do seu número de segurança, a existir um problema com os dispositivos dele(s), ou porque ele(s) o bloquearam. + Isto pode dever-se a este utilizador não ter verificado a sua alteração do seu número de segurança, haver um problema com o seu dispositivo, ou este ter bloqueado o seu perfil. Deslize para ver o ecrã de partilha @@ -1768,11 +1799,18 @@ O Signal necessita das permissões de acesso aos contatos e à multimédia para o ajudar a ligar-se a amigos e a enviar mensagens. Os seus contactos são carregados utilizando a descoberta de contacto privado do Signal, o que significa que eles são encriptados de ponta a ponta e nunca visíveis para o serviço do Signal. O Signal necessita das permissões de acesso aos contatos para o ajudar a ligar-se a amigos. Os seus contactos são carregados utilizando a descoberta de contacto privado do Signal, o que significa que eles são encriptados de ponta a ponta e nunca visíveis para o serviço do Signal. Fez demasiadas tentativas para registar este número. Por favor, tente mais tarde. + + Fez demasiadas tentativas para registar este número. Tente mais tarde dentro de %1$s. Não foi possível ligar ao serviço. Por favor, verifique a sua ligação à rede e tente novamente. Formato de número não padronizado O número que introduziu (%1$s) não parece estar num formato padronizado.\n\nSerá que queria dizer %2$s? Molly Android - Formato de números de telefone + Chamada pedida + + SMS pedida + + Código de verificação pedido Está agora a %1$d passo de submeter um registo de depuração. Está agora a %1$d passos de submeter um registo de depuração. @@ -1792,6 +1830,16 @@ Telefonar Código de verificação Reenviar código + + Está com problemas a registar-se? + + • Certifique-se de que o seu telefone tem sinal de rede móvel para receber o seu SMS ou chamada\n • Confirme que pode receber uma chamada telefónica para o número\n • Verifique se introduziu o seu número de telefone corretamente. + + Para mais informações, siga estas etapas de resolução de problemas ou contacte o Suporte. + + estas etapas de resolução de problemas + + Contactar o Suporte Ativar o \'Bloqueio de registo\'? @@ -1958,6 +2006,10 @@ Pagamento Agendar mensagem + + O seu histórico de mensagens foi juntado + + %1$s pertence a %2$s Atualização do Molly @@ -2094,6 +2146,8 @@ Reagiu %1$s ao seu ficheiro. Reagiu %1$s ao seu áudio. Reagiu %1$s ao seu ficheiro multimédia de visualização única + + Reacted %1$s to your payment. Reagiu %1$s ao seu autocolante. Esta mensagem foi eliminada. @@ -2487,7 +2541,7 @@ Problema na entrega - Uma mensagem, autocolante, reação ou recibo de leitura enviado(a) por %1$s não pode ser entregue a si. Essa mensagem poderá ter sido tentada enviada para si diretamente ou através de um grupo. + Uma mensagem, autocolante, reação ou confirmação de leitura enviado por %1$s não pôde ser entregue a si. Poderá ter sido enviado diretamente a si, ou num grupo. Não lhe foi possível entregar uma mensagem, autocolante, reação ou recibo de leitura enviado por %1$s. @@ -2555,7 +2609,7 @@ Pendente - Enviar para + Enviado para Enviada de Entregue a Lida por @@ -2636,7 +2690,7 @@ Usar a opção personalizada Silenciar por 1 hora - Silenciar durante 8 horas + Silenciar por 8 horas Silenciar por 1 dia Silenciar por 7 dias Sempre @@ -3128,7 +3182,7 @@ - Não silenciar + Anular silêncio Silenciar notificações @@ -3295,6 +3349,8 @@ Introduza o seu PIN Introduza o PIN que criou para a sua conta. Ele é diferente do seu código de verificação SMS. + + Insira o PIN que criou para a sua conta. Introduza um PIN alfanumérico Introduza um PIN numérico PIN incorreto. Tente novamente. @@ -3398,7 +3454,10 @@ A sua cópia de segurança contém um ficheiro muito grande que não pode ser copiado. Elimine esse ficheiro e crie uma nova cópia de segurança. Toque para gerir as cópias de segurança. Número errado? + Liguem-me (%1$02d:%2$02d) + + Reenviar código (%1$02d:%2$02d) Contactar o Suporte do Signal Registo Signal - Código de verificação para Android Código Incorrecto @@ -3406,6 +3465,18 @@ Desconhecido Ver o meu número de telefone Encontrar-me através do número de telefone + + Número de telefone + + Escolha quem pode ver o seu número de telemóvel e quem o pode usar para o contactar no Molly. + + Quem pode ver o seu número + + Ninguém verá o seu número de telemóvel no Molly + + Quem me pode encontrar através do número + + O seu número de telefone será visível para as pessoas e grupos com quem troca mensagens. As pessoas que tenham o seu número de telefone nos seus contactos, também o verão no Molly. Todos Os meus contactos Ninguém @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" foi bloqueado(a). + O utilizador \"%1$s\" foi bloqueado. Ocorreu um erro ao tentar bloquear \"%1$s\" - \"%1$s\" foi desbloqueado(a). + O utilizador \"%1$s\" foi desbloqueado. Rever membros @@ -3670,14 +3741,14 @@ Eliminar a sua conta irá: Introduza o sue número de telefone Eliminar conta - Eliminar a sua informação de conta e fotografia de perfil + Eliminar as suas informações de conta e fotografia de perfil Eliminar todas as suas mensagens Eliminar %1$s na sua conta de pagamentos Sem código de país especificado Sem número especificado O número de telefone que introduziu não coincide com as suas contas. Deseja realmente eliminar a sua conta? - Isto irá eliminar a sua conta do Signal e repor a aplicação. A aplicação irá encerrar depois do processo estar completo. + Isto irá eliminar a sua conta do Signal e repor a aplicação. A app irá encerrar após a conclusão do processo. Falha ao eliminar os dados locais. Pode limpá-los manualmente nas definições das aplicações no sistema. Iniciar as Definições das aplicações @@ -4003,7 +4074,7 @@ Criar perfil - Utilizadores bloqueados + Bloqueados %1$d contactos Mensagens Destruição de mensagens @@ -4186,7 +4257,7 @@ Pedidos e convites Link do grupo Adicionar como contacto - Remover do silêncio + Anular silêncio Conversa silenciada até %1$s Conversa silenciada para sempre Copiado número de telefone para a área de transferência. @@ -4205,7 +4276,7 @@ Silenciar notificações - Não silenciado(a) + Não silenciado Menções Notificar sempre Não notificar @@ -4242,7 +4313,7 @@ %1$s foi removido(a) - %1$s foi bloqueado(a). + O utilizador %1$s foi bloqueado Não foi possível remover %1$s @@ -4508,7 +4579,7 @@ A sua doação não pôde ser enviada devido a um erro de rede. Verifique a sua ligação e tente novamente. - Doação para %1$s + Doação em nome de %1$s %1$s doou ao Signal em seu nome @@ -5057,7 +5128,7 @@ - Você reagiu à história de %1$s + Reagiu à história de %1$s Reagiu à sua história @@ -5601,5 +5672,15 @@ Eliminar nome de utilizador + + + h + + m + + Definir + + O tempo mínimo antes do bloqueio de ecrã é de 1 minuto. + diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 2c479e47d4..7b9ca55ca8 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -14,6 +14,7 @@ + Da Nu @@ -562,6 +563,15 @@ +%1$d + + Re-asociază-ți dispozitivele + + Dispozitivele pe care le-ai adăugat au fost deconectate când dispozitivului tău i-a fost anulată înregistrarea. Mergi la Setări ca să re-asociezi dispozitive. + + Deschide setările + + Mai târziu + Selectează membrii @@ -991,6 +1001,16 @@ Numele de utilizator a fost creat Numele de utilizator a fost copiat + + Nu s-a putut șterge numele de utilizator. Încearcă mai târziu. + + Numele de utilizator a fost șters + + + + Ceva nu a funcționat corect cu numele tău de utilizator, nu mai este atribuit contului tău. Poți încerca să îl stabilești din nou sau să alegi unul nou. + + Repară acum @@ -1204,8 +1224,8 @@ Grup nou Invită prieteni Utilizează SMS - Aspect - Adăugare poză + Culoare conversație + Adaugă o poză de profil Răspunsuri @@ -1648,6 +1668,17 @@ Creează un PIN nou + + Trimite codul SMS + + Registrare Signal - Am nevoie de ajutor cu reînregistrarea PIN-ului pentru Android + + PIN-ul tău este un cod de %1$d+ pe care l-ai creat care poate fi numeric sau alfanumeric.\n\nDacă nu poți reține PIN-ul, poți crea unul nou. + + Dacă nu poți să-ți aduci aminte PIN-ul, poți să creezi unul nou. + + Ai rămas fără încercări de PIN, dar poți încă să accesezi contul Signal prin crearea unui nou PIN. + Avertizare Dacă dezactivezi codul PIN, vei pierde toate datele când te înregistrezi din nou la Signal, cu excepția cazului când faci backup și restaurezi manual. Nu poți activa Blocarea înregistrării în timp ce codul PIN este dezactivat. @@ -1838,11 +1869,18 @@ Signal are nevoie de permisiunea pentru contacte și media să te ajute să iei legătura cu prietenii și să trimiți mesaje. Contactele tale sunt încărcate utilizând descoperirea privată a contactelor de la Signal, ceea ce înseamnă că sunt criptate integral și nu sunt niciodată vizibile pentru serviciul Signal. Signal are nevoie de permisiunea pentru contacte să te ajute să iei legătura cu prietenii. Contactele tale sunt încărcate utilizând descoperirea privată a contactelor de la Signal, ceea ce înseamnă că sunt criptate integral și nu sunt niciodată vizibile pentru serviciul Signal. Ai făcut prea multe încercări de înregistrare a acestui număr. Te rugăm să încerci din nou mai târziu. + + Ai făcut prea multe încercări pentru înregistrarea acestui număr. Încearcă din nou în %1$s. Nu s-a putut realiza conexiunea la serviciu. Te rugăm verifică conexiunea la rețea și încearcă din nou. Număr cu format non-standard Numărul pe care l-ai introdus (%1$s) pare a fi în format non-standard.\n\nAi vrut să scrii %2$s? Molly Android - Formatul Numărului de Telefon + Apel solicitat + + SMS cerut + + Cod de verificare cerut Ești acum la %1$d pas de a trimite un jurnal de depanare. Ești acum la %1$d pași de a trimite un jurnal de depanare. @@ -1863,6 +1901,16 @@ Apelează Cod de Verificare Trimite din nou codul + + Ai probleme la înregistrare? + + • Asigură-te că telefonul tău are semnal celular ca să primești SMS-ul sau apelul\n • Confirmă că poți primi un apel telefonic pe numărul tău\n • Verifică că ai introdus corect numărul de telefon. + + Pentru mai multe informații, urmează acești pași de depanare sau contactează Asistența + + acești pași de depanare + + Contactează Asistența Activează Blocarea Înregistrării? @@ -2029,6 +2077,10 @@ Plată Mesaje programate + + Istoricul mesajelor tale a fost cumulat + + %1$s aparține persoanei %2$s Actualizare Molly @@ -2166,6 +2218,8 @@ A reacționat cu %1$s la fișierul tău. A reacționat cu %1$s la înregistrarea ta audio. A reacționat cu %1$s la fișierul media vizibil o singură dată. + + A reacționat cu %1$s la plata ta. A reacționat cu %1$s la autocolantul tău. Acest mesaj a fost șters. @@ -2638,7 +2692,7 @@ În curs - Trimis la + Trimis către Trimis de Livrat la Citit de @@ -2841,7 +2895,7 @@ Setări PIN avansate Mesaje și apeluri, private și gratuite cu utilizatorii Signal Trimite jurnalul de depanare - Șterge cont + Șterge-ți contul Mod compatibilitate \"Apelare WiFi\" Activează dacă dispozitivul tău trimite SMS/MMS prin WiFi (activează atunci când \"Apelare WiFi\" este activat pe dispozitivul tău) Tastatură incognito @@ -3383,6 +3437,8 @@ Introdu PIN-ul tău Introdu codul PIN pe care l-ai creat pentru contul tău. Acesta este diferit față de codul de verificare prin SMS. + + Introdu PIN-ul pe care l-ai creat pentru contul tău. Introdu un PIN alfanumeric Introdu un PIN numeric PIN incorect. Încearcă din nou. @@ -3491,7 +3547,10 @@ Backup-ul tău conține un fișier foarte mare care nu poate fi adăugat la backup. Șterge-l și creează un backup nou. Atinge pentru a gestiona backup-uri. Număr greșit? + Sună-mă (%1$02d:%2$02d) + + Retrimite codul (%1$02d:%2$02d) Contactează Asistența Signal Înregistrare Signal - Cod de Verificare pentru Android Cod incorect @@ -3499,6 +3558,18 @@ Necunoscut Să-mi vadă numărul de telefon Să mă găsească după numărul de telefon + + Număr de telefon + + Alege cine îți poate vedea numărul de telefon și cine te poate contacta pe Molly cu el. + + Cine îmi poate vedea numărul + + Nimeni nu va vedea numărul tău de telefon pe Molly + + Cine mă poate găsi după număr + + Numărul tău de telefon va fi vizibil pentru persoanele și grupurile către care trimiți mesaje. Persoanele care au numărul tău de telefon în lista lor de contacte îl vor vedea și pe Molly. Toată lumea Contactele mele Nimeni @@ -3764,7 +3835,7 @@ Ștergerea contului tău implică: Introdu numărul tău de telefon - Ștergere cont + Șterge contul Ștergerea informațiilor despre contul tău și poza de profil Ștergerea tuturor mesajelor tale Ștergerea a %1$s din contul tău de plăți @@ -4341,7 +4412,7 @@ %1$s a fost eliminat - %1$s a fost blocat + %1$s a fost blocat/ă Nu s-a putut elimina %1$s @@ -4611,7 +4682,7 @@ Donația ta nu a putut fi trimisă din cauza unei erori de rețea. Verifică-ți conexiunea și încearcă din nou. - Donație către %1$s + Donație în numele persoanei %1$s %1$s donați către Signal în numele tău @@ -5171,7 +5242,7 @@ A reacționat la povestea ta - Ai reacționat la poveste + A reacționat la o poveste @@ -5726,5 +5797,15 @@ Șterge numele de utilizator + + + oră/ore + + min + + Aplicare + + Timpul minim înainte să se aplice blocarea ecranului este de 1 minut. + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1aba61145f..3d68117155 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -14,6 +14,7 @@ + Да Нет @@ -366,7 +367,7 @@ Ошибка при отправке медиафайла - Было сообщено о спаме и пользователь был заблокирован. + Было сообщено о спаме, и пользователь был заблокирован. Обмен SMS-сообщениями в настоящее время отключён. Вы можете экспортировать сообщения в другое приложение на вашем телефоне. @@ -582,6 +583,15 @@ +%1$d + + Привяжите свои устройства заново + + Добавленные вами устройства были отвязаны, когда ваше устройство было снято с регистрации. Перейдите в «Настройки», чтобы заново привязать все устройства. + + Открыть настройки + + Позже + Выберите участников @@ -1029,6 +1039,16 @@ Имя пользователя создано Имя пользователя скопировано + + Не удалось удалить имя пользователя. Попробуйте ещё раз позже. + + Имя пользователя удалено + + + + Что-то пошло не так с вашим именем пользователя, оно больше не закреплено за вашей учётной записью. Вы можете попробовать установить его снова или выбрать новое. + + Исправить сейчас @@ -1252,8 +1272,8 @@ Новая группа Пригласить друзей Использовать SMS - Внешний вид - Добавить фото + Цвета чата + Добавьте фото профиля Ответы @@ -1591,7 +1611,7 @@ Разрешить %1$s отправлять вам сообщения и открыть ваше имя и фото этому человеку? Вы не получите никаких сообщений, пока не разблокируете его. Разрешить %1$s отправлять вам сообщения? Вы не получите никаких сообщений, пока не разблокируете этого человека. - Получать обновления и новости от %1$s? Вы не будете получать никаких сообщений, пока не разблокируете. + Получать обновления и новости от %1$s? Вы не будете получать никаких сообщений, пока не разблокируете этого человека. Продолжить ваш разговор с этой группой и открыть ваше имя и фото её участникам? Обновите эту группу, чтобы активировать такие новые функции, как @упоминания и администраторы групп. Участники, которые ещё не открыли свои имена или фото этой группе получат приглашение присоединиться. Эта Старая группа больше не может быть использована, потому что она слишком большая. Максимальный размер группы — %1$d. @@ -1599,7 +1619,7 @@ Присоединиться к этой группе и открыть ваше имя и фото её участникам? Они не узнают, что вы видели их сообщения, пока вы не примете запрос. Присоединиться к этой группе и открыть ваше имя и фото её участникам? Вы не увидите их сообщения, пока не примете запрос. Присоединиться к этой группе? Её участники не узнают, что вы видели их сообщения, пока вы не примете запрос. - Разблокировать эту группу и открыть ваше имя и фото её участникам? Вы не будете получать сообщений, пока не разблокируете эту группу. + Разблокировать эту группу и открыть ваше имя и фото её участникам? Вы не будете получать сообщения, пока не разблокируете эту группу. Просмотреть Состоит в %1$s @@ -1712,9 +1732,20 @@ Создать новый PIN-код + + Отправить SMS-код + + Регистрация в Signal - Нужна помощь с Пин-кодом повторной регистрации для Android + + Ваш Пин-код — это код из %1$d+ символов, созданный вами, который может быть цифровым или буквенно-цифровым.\n\nЕсли вы забыли свой Пин-код, вы можете создать новый. + + Если вы не помните свой Пин-код, то можете создать новый. + + Вы использовали все попытки ввести Пин-код, но всё ещё можете получить доступ к своей учётной записи Signal, создав новый Пин-код. + Предупреждение - Если вы отключите PIN-код, то при переустановке Signal потеряете все данные, если заранее вручную не создадите резервную копию. При отключённом PIN-коде нельзя будет включить блокировку регистрации. + Если вы отключите Пин-код, то при переустановке Signal потеряете все данные, если заранее вручную не создадите резервную копию. При отключённом Пин-коде нельзя будет включить блокировку регистрации. Отключить PIN-код @@ -1849,9 +1880,9 @@ Камера - Вкл. микрофон + Вкл. звук - Откл. микрофон + Откл. звук Звонить @@ -1871,7 +1902,7 @@ Вы не будете получать аудио и видео этого участника, и он не будет получать ваши. Невозможно получать аудио и видео от %1$s Невозможно получать аудио и видео от %1$s - Возможно, это из-за того, что этот участник не подтвердил изменение вашего кода безопасности, что есть какая-то проблема с его устройством или что он вас заблокировал. + Возможно, это из-за того, что этот участник не подтвердил изменение вашего кода безопасности, или есть какая-то проблема с его устройством, или он вас заблокировал. Проведите, чтобы просмотреть показ экрана @@ -1908,11 +1939,18 @@ Signal необходим доступ к вашим контактам и медиафайлам, чтобы помочь вам связаться с друзьями и отправлять сообщения. Ваши контакты загружаются с помощью технологии конфиденциального обнаружения контактов Signal, которая использует сквозное шифрование, поэтому ваши контакты никогда не видны сервису Signal. Signal необходим доступ к вашим контактам, чтобы помочь вам связаться с друзьями. Ваши контакты загружаются с помощью технологии конфиденциального обнаружения контактов Signal, которая использует сквозное шифрование, поэтому ваши контакты никогда не видны сервису Signal. Вы сделали слишком много попыток зарегистрировать этот номер. Пожалуйста, попробуйте ещё раз позже. + + Вы сделали слишком много попыток зарегистрировать этот номер. Пожалуйста, попробуйте ещё раз через %1$s. Не удается подключиться к сервису. Пожалуйста, проверьте подключение к сети и повторите попытку. Нестандартный номер телефона Номер, который вы ввели (%1$s), похоже, не в стандартном формате.\n\nВы имели в виду %2$s? Molly Android - Формат номера телефона + Звонок запрошен + + SMS запрошено + + Код подтверждения запрошен Вы в %1$d шаге от отправки журнала отладки. Вы в %1$d шагах от отправки журнала отладки. @@ -1934,6 +1972,16 @@ Позвонить Код подтверждения Отправить код ещё раз + + Возникли проблемы с регистрацией? + + • Убедитесь, что на вашем телефоне есть сигнал сотовой связи для приема SMS или звонка\n • Подтвердите, что можете принять телефонный звонок на номер\n • Проверьте правильность введённого номера телефона. + + Для получения дополнительной информации выполните следующие шаги для устранения неполадок или свяжитесь с поддержкой + + следующие шаги для устранения неполадок + + Связаться с поддержкой Включить блокировку регистрации? @@ -2100,6 +2148,10 @@ Платёж Запланированное сообщение + + История ваших сообщений была объединена + + %1$s принадлежит %2$s Обновление Molly @@ -2237,7 +2289,9 @@ Отреагировал(-а) %1$s на вашу GIF-анимацию. Отреагировал(-а) %1$s на ваш файл. Отреагировал(-а) %1$s на ваше аудио. - Отреагировал(-а) %1$s на ваше одноразовое медиа. + Отреагировал(-а) %1$s на ваш одноразовый медиафайл. + + Отреагировал(-а) %1$s на ваш платёж. Отреагировал(-а) %1$s на ваш стикер. Это сообщение было удалено. @@ -3139,7 +3193,7 @@ Отправленный платёж Полученный платёж Платёж завершён %1$s - Номер блока + Заблокировать номер Перевести @@ -3471,6 +3525,8 @@ Введите свой PIN-код Введите PIN-код, который вы создали для своей учётной записи. Это не то же самое. что проверочный код из SMS. + + Введите Пин-код, созданный для вашей учётной записи. Ввести буквенно-цифровой PIN-код Ввести цифровой PIN-код Неверный PIN-код. Попробуйте ещё раз. @@ -3584,7 +3640,10 @@ Ваша резервная копия содержит очень большой файл, резервное копирование которого невозможно. Пожалуйста, удалите его и создайте новую резервную копию. Нажмите, чтобы управлять резервным копированием. Неправильный номер? + Позвоните мне (%1$02d:%2$02d) + + Отправить код ещё раз (%1$02d:%2$02d) Связаться с поддержкой Signal Регистрация в Signal - код подтверждения для Android Неверный код @@ -3592,6 +3651,18 @@ Неизвестно Видеть мой номер телефона Найти меня по номеру телефона + + Номер телефона + + Выберите, кто может видеть ваш номер телефона и связываться с вами в Molly с его помощью. + + Кто может видеть мой номер + + Никто не будет видеть ваш номер телефона в Molly + + Кто может найти меня по номеру? + + Ваш номер телефона будет виден людям и группам, которым вы отправляете сообщения. Люди, у которых есть ваш номер в телефонных контактах, также увидят его в Molly. Все Мои контакты Никто @@ -3860,8 +3931,8 @@ Удаление вашей учётной записи приведёт к: Введите свой номер телефона Удалить учётную запись - Удалить информации вашей учётной записи и фотографии профиля - Удалить всех ваших сообщений + Удалению информации вашей учётной записи и фотографии профиля + Удалению всех ваших сообщений Удалить %1$s в вашей платёжной учётной записи Не указан код страны Не указан номер @@ -4384,9 +4455,9 @@ Запросы и приглашения Ссылка на группу Добавить в контакты - Включить звуки - Звуки разговора отключены до %1$s - Звуки разговора отключены навсегда + Включить звук + Звук разговора отключён до %1$s + Звук разговора отключён навсегда Номер телефона был скопирован в буфер обмена. Номер телефона Получайте значки для своего профиля, поддерживая Signal. Нажмите на значок, чтобы узнать больше. @@ -4714,7 +4785,7 @@ Ваше пожертвование не удалось отправить из-за ошибки сети. Проверьте ваше подключение к интернету и попробуйте ещё раз. - Пожертвование %1$s + Пожертвование от имени %1$s %1$s пожертвовал(-а) Signal от вашего имени @@ -5851,5 +5922,15 @@ Удалить имя пользователя + + + ч + + м + + Установить + + Минимальное время перед включением блокировки экрана – 1 минута. + diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 2f99792987..b1a20946d9 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -14,6 +14,7 @@ + Áno Nie @@ -98,7 +99,7 @@ Blokovaní používatelia Pridať blokovaného používateľa - Blokovaní používatelia Vám nebudú môcť zavolať alebo posielať správy. + Blokovaní používatelia vám nebudú môcť volať ani posielať správy. Žiadni blokovaní používatelia Zablokovať používateľa? \"%1$s\" vám nebude môcť zavolať alebo posielať správy. @@ -138,8 +139,8 @@ Pokračovať - Zablokovať a opustiť %1$s? - Zablokovať %1$s? + Blokovať a opustiť %1$s? + Blokovať %1$s? Od tejto skupiny už nebudete dostávať správy ani aktualizácie a členovia vás nebudú môcť znovu pridať. Členovia skupiny vás nebudú môcť znovu do tejto skupiny pridať. Členovia skupiny vás budú môcť do tejto skupiny znovu pridať. @@ -147,15 +148,15 @@ Budete si môcť navzájom písať a volať a budú vidieť vaše meno a fotku. Budete si môcť navzájom posielať správy. - Zablokované osoby vám nebudú môcť volať ani posielať správy. - Zablokovaní ľudia vám nebudú môcť posielať správy. + Blokované osoby vám nebudú môcť volať ani posielať správy. + Blokovaní ľudia vám nebudú môcť posielať správy. Blokovať prijímanie aktualizácií a noviniek Signal. Pokračovať v získavaní aktualizácií a noviniek Signal. Odblokovať %1$s? Blokovať - Zablokovať a odísť + Blokovať a opustiť Nahlásiť spam a blokovať @@ -467,7 +468,7 @@ Zrušiť - Blokovaní + Blokované Zrušiť filter @@ -530,10 +531,10 @@ Stlmiť - Zapnúť zvuk - Zapnúť zvuky - Zapnúť zvuky - Zapnúť zvuky + Zrušiť stlmenie + Zrušiť stlmenie + Zrušiť stlmenie + Zrušiť stlmenie Vybrať @@ -582,6 +583,15 @@ +%1$d + + Znova pripojte svoje zariadenia + + Zariadenia, ktoré ste pridali, boli odpojené, keď vaše zariadenie nebolo registrované. Prejdite do časti Nastavenia a znova pripojte všetky zariadenia. + + Otvoriť nastavenia + + Neskôr + Vyberte členov @@ -1011,7 +1021,7 @@ Upozorni ma na Spomenutia - Prijímať oznámenia, keď vás spomenú v stlmených konverzáciách? + Chcete dostávať upozornenia, keď vás spomenú v stlmených rozhovoroch? Vždy ma upozorniť Neupozorniť ma @@ -1029,6 +1039,16 @@ Používateľské meno bolo vytvorené Používateľské meno bolo skopírované + + Používateľské meno sa nepodarilo odstrániť. Skúste to znova neskôr. + + Používateľské meno bolo odstránené + + + + Vyskytol sa problém s vaším používateľským menom, už nie je priradené k vášmu účtu. Môžete si ho skúsiť nastaviť znova alebo si vybrať nové. + + Opraviť teraz @@ -1252,8 +1272,8 @@ Nová skupina Pozvať priateľov Použiť SMS - Vzhľad - Pridať fotku + Farba četu + Pridať profilovú fotku Odpovede @@ -1590,8 +1610,8 @@ Povoliť používateľovi %1$s, aby vám posielal správy a zdieľať s ním vaše meno a fotku? Kým ho neodblokujete, nepríjmete od neho žiadne správy. - Chcete povoliť používateľovi %1$s, aby vám poslal správu? Kým ich neodblokujete, nebudete dostávať žiadne správy. - Chcete dostávať aktualizácie a novinky od %1$s? Nebudete dostávať žiadne aktualizácie, kým ich neodblokujete. + Chcete povoliť používateľovi %1$s, aby vám poslal správu? Kým ho neodblokujete, nepríjmete od neho žiadne správy. + Chcete dostávať aktualizácie a novinky od používateľa %1$s? Kým ich neodblokujete, nebudete dostávať žiadne aktualizácie. Pokračovať v chate v tejto skupine a zdieľať vaše meno a fotku s jej členmi? Modernizovaním tejto skupiny aktivujete nové funkcie ako @spomenutia a administrátori. Členovia, ktorí nezdieľali svoje meno alebo fotku, budú do tejto skupiny pozvaní. Táto Zastaraná skupina sa nemôže ďalej používať, pretože je príliš veľká. Maximálna veľkosť skupiny je %1$d. @@ -1599,7 +1619,7 @@ Pridať sa do tejto skupiny a zdieľať vaše meno a fotku s jej členmi? Kým s tým nebudete súhlasiť, členovia skupiny nebudú vedieť, že ste ich správy videli. Pripojiť sa k tejto skupine a zdieľať svoje meno a fotografiu s jej členmi? Ich správy neuvidíte, kým ju neprijmete. Pridať sa do tejto skupiny? Kým pozvanie neprijmete, jej členovia sa nedozvú, že ste ich správy videli. - Odblokovať túto skupinu a zdieľať vaše meno a foto s jej členmi? Kým ju neodblokujete, nepríjmete z nej žiadne správy. + Odblokovať túto skupinu a zdieľať vaše meno a fotku s jej členmi? Kým ju neodblokujete, nepríjmete od nej žiadne správy. Zobraziť Člen %1$s @@ -1712,9 +1732,20 @@ Vytvoriť nový PIN + + Odoslať SMS kód + + Signal registrácia – potrebujem pomoc s opätovnou registráciou PIN kódu pre Android + + Váš PIN kód je %1$d alebo viacmiestny kód, ktorý ste si vytvorili a môže pozostávať z číselných alebo alfanumerických znakov.\n\nAk si nepamätáte svoj PIN kód, môžete si vytvoriť nový. + + Ak si nepamätáte svoj PIN kód, môžete si vytvoriť nový. + + Dosiahli ste maximálny počet neúspešných pokusov o zadanie PIN kódu, no ku svojmu Signal účtu budete mať prístup aj naďalej, ak si vytvoríte nový PIN kód. + Varovanie - Ak vypnete PIN, opätovnou registráciou Signalu prídete o všetky vaše dáta, ak ich manuálne nezálohujete a neobnovíte. Kým bude PIN vypnutý, nebudete môcť zapnúť registračný zámok. + Ak vypnete PIN, opätovnou registráciou Signalu prídete o všetky svoje údaje, pokiaľ ich manuálne nezálohujete a neobnovíte. Kým bude PIN vypnutý, nebudete môcť zapnúť registračný zámok. Vypnúť PIN @@ -1849,7 +1880,7 @@ Fotoaparát - Zapnúť zvuky + Zrušiť stlmenie Stlmiť @@ -1871,7 +1902,7 @@ Od tohto používateľa nebudete prijímať zvuk a video a ani on neprijme od vás. Nie je možné prijať audio a video od používateľa %1$s Nie je možné prijať audio a video od používateľa %1$s - Môže to byť pre to, že dotyčný používateľ nepotvrdil zmenu vášho bezpečnostného čísla, že sa vyskytol problém s jeho zariadením, alebo že vás zablokoval. + Môže to byť preto, že daný používateľ nepotvrdil zmenu vášho bezpečnostného čísla, vyskytol sa problém s jeho zariadením, alebo vás zablokoval. Potiahnite prstom pre zobrazenie zdieľania obrazovky @@ -1908,11 +1939,18 @@ Signal potrebuje povolenia na kontakty a médiá, aby vám pomohol spojiť sa s priateľmi a odosielať správy. Vaše kontakty sa nahrávajú pomocou zisťovania súkromných kontaktov Signal, čo znamená, že sú šifrované end-to-end a nikdy ich služba Signal nevidí. Signal potrebuje povolenie kontaktov, aby vám pomohol spojiť sa s priateľmi. Vaše kontakty sa nahrávajú pomocou zisťovania súkromných kontaktov Signal, čo znamená, že sú šifrované end-to-end a nikdy ich služba Signal nevidí. Urobili ste príliš veľa pokusov o zaregistrovanie tohto čísla. Prosím, skúste to znovu neskôr. + + Urobili ste príliš veľa pokusov o zaregistrovanie tohto čísla. Skúste to znova o %1$s. Nepodarilo sa pripojiť k službe. Prosím, overte Vaše sieťové pripojenie a skúste to znovu. Neštandardný formát čísla Zdá sa, že zadané číslo (%1$s) má neštandardný formát.\n\n Mali ste na mysli %2$s? Molly Android - Formát telefónneho čísla + Hovor požadovaný + + SMS vyžiadaná + + Overovací kód vyžiadaný Ste %1$d krok od odoslania ladiaceho záznamu. Ste %1$d kroky od odoslania ladiaceho záznamu. @@ -1934,6 +1972,16 @@ Zavolať Overovací kód Znovu odoslať kód + + Máte problém s registráciou? + + • Uistite sa, že váš telefón má stabilný signál a môže prijímať SMS alebo hovory\n • Potvrďte, že môžete prijať hovor na číslo\n • Skontrolujte, či ste správne zadali svoje telefónne číslo. + + Ak chcete získať ďalšie informácie, postupujte podľa týchto krokov na riešenie problémov alebo kontaktujte podporu + + týchto krokov na riešenie problémov + + Kontaktovať podporu Zapnúť registračný zámok? @@ -2093,13 +2141,17 @@ Odomkli ste odznak - Poslal/a %1$s ako reakciu na tvoj príbeh + Reagoval/a %1$s na váš príbeh - Poslal/a si %1$s ako reakciu na ich príbeh + Reagovali ste %1$s na ich príbeh Platba Naplánovaná správa + + História správ bola zlúčená + + %1$s patrí používateľovi %2$s Aktualizácia Mollyu @@ -2231,14 +2283,16 @@ Nezabezpečená SMS %1$s %2$s Kontakt - reagoval/a na „%2$s“: %1$s. - reagoval/a na vaše video: %1$s. - reagoval/a na váš obrázok: %1$s. - %1$s reagoval na váš GIF. - reagoval/a na váš súbor: %1$s. - reagoval/a na váš zvuk: %1$s. - Reagoval(a) %1$s na vaše médium na jedno zobrazenie - reagoval/a na vašu nálepku: %1$s. + Reagoval/a %1$s na: „%2$s“. + Reagoval/a %1$s na vaše video. + Reagoval/a %1$s na váš obrázok. + Reagoval/a %1$s na vaše GIF. + Reagoval/a %1$s na váš súbor. + Reagoval/a %1$s na vaše audio. + Reagoval/a %1$s na vaše jednorazové médium. + + Reacted %1$s to your payment. + Reagoval/a %1$s na vašu nálepku. Táto správa bola vymazaná. Vypnúť upozornenia na to, že kontakt začal používať Signal? Môžete ich znova zapnúť v Signal > Nastavenia > Upozornenia. @@ -2653,8 +2707,8 @@ Problém s doručením - Správu, nálepku, reakciu, potvrdenie o prečítaní, alebo média od %1$s vám nebolo možné doručiť. Možno sa vám pokúsili poslať ho priamo, alebo v skupine. - Od %1$s vám nebolo možné doručiť správu, nálepku, reakciu alebo potvrdenie o prečítaní. + Správu, nálepku, reakciu alebo potvrdenie o prečítaní od používateľa %1$s vám nebolo možné doručiť. Možno sa vám to pokúsil poslať priamo alebo v skupine. + Správu, nálepku, reakciu alebo potvrdenie o prečítaní od používateľa %1$s vám nebolo možné doručiť. Meno (povinné) @@ -2721,7 +2775,7 @@ Čaká na spracovanie - Odoslané komu + Odoslané používateľom Odoslané kým Doručené komu Prečítané kým @@ -2843,9 +2897,9 @@ Použiť fotky zo zoznamu kontaktov Zobraziť fotky zo zoznamu kontaktov, ak sú dostupné - Ponechať stlmené chaty archivované + Ponechať stlmené čety archivované - Stlmené chaty, ktoré sú archivované, zostanú archivované, keď príde nová správa. + Stlmené čety, ktoré sú archivované, zostanú archivované, keď príde nová správa. Generovať náhľady stránok Získať ukážky stránok priamo z webových prepojení pre správy, ktoré odošlete. Zmeniť heslo @@ -3224,7 +3278,7 @@ Nová správa pre… - Zablokovať používateľa + Blokovať používateľa Pridať do skupiny @@ -3298,7 +3352,7 @@ - Nahlas + Zrušiť stlmenie Stlmiť upozornenia @@ -3471,6 +3525,8 @@ Zadajte svoj PIN Zadajte PIN, ktorý ste si pre váš účet vytvorili. Nie je to to isté ako SMS overovací kód. + + Zadajte PIN kód, ktorý ste vytvorili pre svoj účet. Zadajte alfanumerický PIN kód Zadajte číselný PIN kód Nesprávny PIN kód. Skúste to znova. @@ -3584,7 +3640,10 @@ Vaša záloha obsahuje veľmi veľký súbor, ktorý nie je možné zálohovať. Odstráňte ho a vytvorte novú zálohu. Ťukni pre správu záloh. Zlé číslo? + Zavolajte mi (%1$02d:%2$02d) + + Znova odoslať kód (%1$02d:%2$02d) Kontaktovať podporu Signal Registrácia v Signali – Overovací kód pre Android Nesprávny kód @@ -3592,6 +3651,18 @@ Neznáma Ukázať moje telefónne číslo Nájsť ma podľa telefónneho čísla + + Telefónne číslo + + Vyberte si, kto môže vidieť vaše telefónne číslo a kto vás môže pomocou neho kontaktovať v službe Molly. + + Kto môže vidieť moje telefónne číslo + + Nikto neuvidí vaše telefónne číslo v službe Molly + + Kto ma môže vyhľadať pomocou telefónneho čísla + + Vaše telefónne číslo sa zobrazí ľuďom a skupinám, ktorým pošlete správu. Ľudia, ktorí majú vaše telefónne číslo vo svojich kontaktoch, ho v službe Molly budú vidieť tiež. Každý Moje kontakty Nikto @@ -3801,9 +3872,9 @@ %1$s/%2$s - \"%1$s\" bol/a zablokovaný/á. - Zlyhalo zablokovanie \"%1$s\" - \"%1$s\" bol/a odblokovaný/á. + Používateľ „%1$s“ bol zablokovaný. + Blokovanie používateľa „%1$s%1$s“ zlyhalo + Používateľ „%1$s“ bol odblokovaný. Overiť členov @@ -3860,14 +3931,14 @@ Vymazanie účtu spôsobí: Zadajte svoje telefónne číslo Vymazať účet - Vymažú sa informácie o vašom účte a profilová fotka - Vymažú sa všetky vaše správy + Vymazanie informácií o vašom účte a profilovej fotky + Vymazanie všetkých vašich správ Z vášho platobného účtu sa odstráni %1$s Chýba kód krajiny Chýba telefónne číslo Zadané telefónne číslo sa nezhoduje s telefónnym číslom vášho účtu. Naozaj chcete vymazať svoj účet? - Tým sa odstráni váš účet Signal a obnoví sa aplikácia. Aplikácia sa po dokončení procesu zavrie. + Tým sa odstráni váš Signal účet a obnoví sa aplikácia. Aplikácia sa po dokončení procesu zavrie. Nepodarilo sa odstrániť lokálne údaje. Môžete ich manuálne vymazať v systémových nastaveniach aplikácie. Spustiť nastavenía aplikácie @@ -3976,7 +4047,7 @@ Deaktivovať peňaženku Váš zostatok - Odporúčame, aby ste pred deaktiváciou platieb vaše prostriedky previedli na adresu inej peňaženky. Ak sa rozhodnete, že vaše prostriedky neprevediete teraz, zostanú v peňaženke prepojenej so Molly, ak platby reaktivujete. + Odporúčame vám, aby ste pred deaktiváciou platieb previedli svoje prostriedky na adresu inej peňaženky. Ak sa rozhodnete, že svoje prostriedky neprevediete teraz, zostanú v peňaženke prepojenej so službou Molly, ak platby opätovne aktivujete. Previesť zostávajúcu čiastku Deaktivovať bez prevodu Deaktivovať @@ -4197,12 +4268,12 @@ Vytvoriť profil - Blokovaní + Blokované %1$d kontaktov Odosielanie správ Miznúce správy Zabezpečenie aplikácie - Zakázať vytváranie snímkov obrazovky v tejto aplikácii a v zozname bežiacich aplikácií + Zakázať vytváranie snímkov obrazovky v aplikácii a zozname nedávnych konverzácií Správy a hovory Signal, vždy opakované hovory a zapečatený odosielateľ Predvolený časovač pre nové konverzácie Nastavte predvolený časovač miznutia správ pre všetky nové správy ktoré ste poslali. @@ -4304,7 +4375,7 @@ Teraz nie - Prispôsobte reakcie + Prispôsobiť reakcie Ťuknutím nahradíte emotikon Resetovať Uložiť @@ -4365,7 +4436,7 @@ Stlmiť - Stlmený/á + Stlmené Hľadať Miznúce správy @@ -4386,7 +4457,7 @@ Pridať ako kontakt Zrušiť stlmenie Konverzácia bola stlmená do %1$s - Konverzácia bola navždy stlmená + Konverzácia bola permanentne stlmená Telefónne číslo skopírované do schránky. Telefónne číslo Podporou Signalu získate odznaky pre svoj profil. Ťuknutím na odznak sa dozviete viac. @@ -4536,7 +4607,7 @@ Pridať do príbehu Pridajte správu Pridať odpoveď - Poslať + Odoslať Zobraziť správu raz Jedna, alebo viac položiek bolo príliš veľkých Jedna, alebo viac položiek je neplatných @@ -4671,7 +4742,7 @@ Váš opakovaný mesačný príspevok bol zrušený, pretože sme nemohli spracovať vašu platbu. Váš odznak sa už vo vašom profile nezobrazuje. Váš opakovaný mesačný príspevok bol zrušený. %1$s Váš odznak %2$s sa už vo vašom profile nezobrazuje. - Signal môžete používať aj naďalej, ak však chcete podporiť aplikáciu a znova aktivovať svoj odznak, obnovte ho teraz. + Signal môžete používať aj naďalej, ak však chcete podporiť aplikáciu a znova aktivovať svoj odznak, obnovte svoje predplatné. Obnoviť predplatné Prejdite do služby Google Pay @@ -4714,7 +4785,7 @@ Váš príspevok nebolo možné odoslať z dôvodu chyby siete. Skontrolujte pripojenie a skúste to znovu. - Príspevok pre používateľa %1$s + Príspevok v mene používateľa %1$s Používateľ %1$s prispel na Signal vo vašom mene @@ -5071,7 +5142,7 @@ Odpovede - Reagujte na tento príbeh + Reagovať na tento príbeh Súkromne odpovedáte %1$s @@ -5311,7 +5382,7 @@ Potvrdiť príspevok - Poslať + Odoslať používateľovi Príjemca bude o príspevku informovaný v správe. Pridajte svoj vlastný text správy nižšie. @@ -5851,5 +5922,15 @@ Vymazať používateľské meno + + + h + + min + + Nastaviť + + Minimálny čas do aktivácie zámku obrazovky je 1 minúta. + diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 7a01bd965e..dd4d5087d4 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -14,6 +14,7 @@ + Da Ne @@ -97,8 +98,8 @@ Blokirani_e uporabniki_ce - Dodaj blokiranega uporabnika_co - Blokirani_e uporabniki_ce vas ne bodo mogli_e klicati ali vam pošiljati sporočila. + Dodaj blokiranega_o uporabnika_co + Blokirani_e uporabniki_ce vas ne bodo mogli_e klicati ali vam pošiljati sporočil. Ni blokiranih uporabnikov_ic Želite blokirati uporabnika_co? \"%1$s\" vas ne bo mogel_la klicati ali vam pošiljati sporočil. @@ -138,8 +139,8 @@ Nadaljuj - Blokiram in zapustim skupino %1$s? - Blokiram uporabnika_co %1$s? + Blokiraj in zapusti skupino %1$s? + Blokiraj uporabnika_co %1$s? Od skupine ne boste več prejemali sporočil in posodobitev. Člani_ce skupine vas ne bodo več mogli_e dodati nazaj v skupino. Člani_ce skupine vas ne bodo mogli_e dodati nazaj v skupino. Drugi_e člani_ce vas bodo lahko ponovno dodali_e v skupino. @@ -501,15 +502,15 @@ Prebrano - Prebrano - Prebrano - Prebrano + Prebrani + Prebrana + Prebranih Neprebrano - Neprebrano - Neprebrano - Neprebrano + Neprebrani + Neprebrana + Neprebranih Pripni @@ -543,10 +544,10 @@ Premakni v arhiv - Premakni v nabiralnik - Premakni v nabiralnik - Od-arhiviraj - Premakni v nabiralnik + Odstrani iz arhiva + Odstrani iz arhiva + Odstrani iz arhiva + Odstrani iz arhiva Izbriši @@ -582,6 +583,15 @@ +%1$d + + Znova povežite svoje naprave + + Povezava z napravami, ki ste jih dodali, je bila prekinjena, ko je bila vaša naprava odjavljena. Pojdite v Nastavitve, da znova povežete naprave. + + Odpri nastavitve + + Kasneje + Izbira članov_ic @@ -1011,7 +1021,7 @@ Obveščaj me o omembah - Želite prejemati obvestila, kadar bo vaše ime omenjeno v pogovorih, ki ste jih utišali? + Želite prejeti obvestilo, ko je vaše ime omenjeno v pogovorih, ki ste jih utišali? Vedno me obveščaj Ne obveščaj me @@ -1029,6 +1039,16 @@ Uporabniško ime je ustvarjeno Uporabniško ime je kopirano + + Uporabniškega imena ni bilo mogoče izbrisati. Poskusite znova kasneje. + + Uporabniško ime izbrisano + + + + Nekaj je šlo narobe z vašim uporabniškim imenom, ni več dodeljeno vašemu računu. Lahko ga poskusite znova nastaviti ali izberete novega. + + Popravi @@ -1252,8 +1272,8 @@ Nova skupina Vabilo prijateljem_icam Uporabi SMS - Izgled - Dodaj fotografijo + Barve klepeta + Dodaj profilno fotografijo Odgovori @@ -1588,7 +1608,7 @@ Odblokiraj Želite omogočiti uporabniku_ci %1$s, da vam pošilja sporočila in z njim_njo deliti svoje ime in fotografijo? Dokler ne sprejmete vabila, pošiljatelj_ica ne bo vedel_a, da ste prejeli njegovo_njeno sporočilo. - Želite omogočiti uporabniku_ci %1$s da vam pošilja sporočila in z njim_njo deliti svoje ime in fotografijo? Dokler ga_je ne odblokirate, od njega_nje ne boste prejemali sporočil. + Želite omogočiti uporabniku_ci %1$s, da vam pošilja sporočila in z njim_njo deliti svoje ime in fotografijo? Dokler ga_je ne odblokirate, od njega_nje ne boste prejemali sporočil. Bi želeli omogočiti uporabniku_ci %1$s, da vam pošilja sporočila? Dokler ga/je ne odblokirate, ne boste prejemali njegovih/njenih sporočil. Prejemate novice in novosti od %1$s? Dokler jih ponovno ne dovolite, ne boste prejemali nobenih novosti. @@ -1712,9 +1732,20 @@ Ustvari nov PIN + + Pošlji SMS kodo + + Registracija storitve Signal - Potrebujem pomoč pri ponovni registraciji kode PIN za Android + + Vaš PIN je koda iz %1$d+ znakov, ki je lahko sestavljena zgolj iz številk ali iz kombinacije črk in številk.\n\nČe ste pozabili svoj PIN, lahko zdaj ustvarite novega. + + Če ste pozabili svoj PIN, lahko zdaj ustvarite novega. + + Zmanjkalo vam je ugibanj kode PIN, vendar lahko še vedno dostopate do svojega računa Signal, tako da ustvarite novo kodo PIN. + Opozorilo - Z izklopom PINa boste ob ponovni prijavi v svoj račun Signal izgubili vse tam shranjene podatke, razen če prej sami ne ustvarite varnostne kopije. Brez PINa tudi ne morete vklopiti zaklepa registracije. + Z izklopom številke PIN boste ob ponovni prijavi v svoj račun Signal izgubili vse tam shranjene podatke, razen če prej sami ne ustvarite varnostne kopije. Brez PIN-a tudi ne morete vklopiti zaklepa registracije. Izklopi PIN @@ -1871,7 +1902,7 @@ Njihovega zvoka in slike ne boste prejemali in oni ne vaših. Ne morete prejemati zvoka & videa od uporabnika_ce %1$s Ne morete prejemati zvoka in videa od uporabnika_ce %1$s - To je zato, ker niso overili vašega spremenjenega varnostnega števila, ker je nek problem na njihovi napravi ali pa so vas blokirali. + To je lahko zato, ker niso overili vašega spremenjenega varnostnega števila, ker obstaja problem na njihovi napravi ali pa so vas blokirali. Za ogled delitve zaslona podrsajte @@ -1908,11 +1939,18 @@ Signal potrebuje dovoljenje za dostop do vaših stikov in medijskih datotek, da lahko komunicirate s svojimi prijatelji_cami in pošiljate sporočila. Vaši stiki so prenešeni na strežnik preko Signalovga zasebnega odkrivanja stikov, kar pomeni, da so šifrirani od-konca-do-konca in niso nikdar vidni storitvi Signal. Signal potrebuje dovoljenje za dostop do vaših stikov, da lahko komunicirate s svojimi prijatelji_cami. Vaši stiki so prenešeni na strežnik preko Signalovga zasebnega odkrivanja stikov, kar pomeni, da so šifrirani od-konca-do-konca in niso nikdar vidni storitvi Signal. Preveč neuspešnih poskusov registracije te telefonske številke. Prosimo poskusite znova kasneje. + + Preveč neuspešnih poskusov registracije številke. Prosimo, poskusite znova čez %1$s. Povezava s storitvijo ni mogoča. Preverite omrežje in poskusite znova. Nestandardni format številk Številka, ki ste jo vpisali (%1$s) je v nestandardnem formatu.\n\nSte morda mislili %2$s? Molly Android - Format telefonskih številk + Zahtevek za klic + + Zahtevan SMS + + Zahtevana koda za preverjanje Ste %1$d korak pred oddajo sistemske zabeležbe. Ste %1$d koraka pred oddajo sistemske zabeležbe. @@ -1934,6 +1972,16 @@ Kliči Verifikacijska koda Ponovno mi pošlji kodo + + Imate težave z registracijo? + + • Prepričajte se, da ima vaš telefon mobilni signal za sprejem SMS-a ali klica\n • Potrdite, da lahko sprejmete telefonski klic na številko\n • Preverite, ali ste pravilno vnesli svojo telefonsko številko. + + Za več informacij sledite tem korakom za odpravljanje težav ali se obrnite na Podporo + + tem korakom za odpravljanje težav + + Obrnite se na Podporo Želite vklopiti zaklep registracije? @@ -2093,13 +2141,17 @@ Vnovčili ste značko - Na vašo zgodbo se je odzval_a: %1$s + %1$s se je odzval_a na vašo zgodbo - Vaš odziv na zgodbo: %1$s + Odzvali ste se na zgodbo uporabnika_ce %1$s Plačilo Načrtovano sporočilo + + Vaša zgodovina sporočil je bila združena + + %1$s pripada %2$s Nadgradnja aplikacije Molly @@ -2231,14 +2283,16 @@ Nezavarovan SMS %1$s %2$s Stik - %1$s spremenjeno v: \"%2$s\". - %1$s spremenjeno v vaš video. - %1$s spremenjeno v vašo sliko. - Reagiral_a z %1$s na vaš GIF. - %1$s spremenjeno v vašo datoteko. - %1$s spremenjeno v vaš zvok. - Reakcija %1$s na vaše medijsko sporočilo za enkraten ogled. - %1$s spremenjeno v vašo nalepko. + Odziv na \"%2$s\": %1$s + Odziv vaš video: %1$s. + Odziv na vašo sliko: %1$s + Odziv na vaš GIF: %1$s + Odziv na vašo datoteko: %1$s + Odziv na vaš zvok: %1$s + Odziv na vaše medijsko sporočilo za enkraten ogled: %1$s + + Odziv na vaše plačilo: %1$s + Odziv na vašo nalepko: %1$s Sporočilo je bilo izbrisano Želite izklopiti obvestila o novih članih Signalove skupine? Kasneje jih lahko spet vklopite v Signal > Nastavitve > Obvestila. @@ -2653,8 +2707,8 @@ Problem pri dostavi - Sporočilo, nalepka, odziv ali potrdilo o branju uporabnika_ce %1$s. vam ni bilo mogoče dostaviti. Lahko so bili poslani osebno ali prek skupine. - Sporočilo, nalepka, odziv ali potrdilo o branju uporabnika_ce %1$s vam ni bilo mogoče dostaviti. + Sporočila, nalepke, odziva ali potrdila o branju uporabnika_ce %1$s vam ni bilo mogoče dostaviti. Lahko so bili poslani osebno ali prek skupine. + Sporočila, nalepke, odziva ali potrdila o branju uporabnika_ce %1$s vam ni bilo mogoče dostaviti. Ime (zahtevano) @@ -2801,10 +2855,10 @@ Uporabi prednastavljeno Uporabi po meri - Izklopi za 1 uro + Utišaj za 1 uro Utišaj za 8 ur - Izklopi za 1 dan - Izklopi za 7 dni + Utišaj za 1 dan + Utišaj za 7 dni Za vedno Sistemsko privzeto @@ -3471,6 +3525,8 @@ Vnesite svoj PIN Vnesite PIN, ki ste ga ustvarili za zaklep svojega računa. Tu ne gre za SMS potrditveno kodo. + + Vnesite PIN, ki ste ga ustvarili za svoj račun. Vnesite alfanumerični PIN Vnesite številčni PIN Napačen PIN. Poskusite znova. @@ -3584,7 +3640,10 @@ Vaša varnostna kopija vsebuje zelo veliko datoteko, ki je ni mogoče varnostno kopirati. Izbrišite jo in ustvarite novo varnostno kopijo. Tapnite za urejanje varnostnih kopij. Napačna številka? + Pokliči me (%1$02d:%2$02d) + + Ponovno pošlji kodo (%1$02d:%2$02d) Kontaktiraj center za podporo Signal Registracija za storitev Signal - verifikacijska koda za Android Napačna koda @@ -3592,6 +3651,18 @@ Neznano vidijo mojo telefonsko številko me poiščejo, če poznajo mojo številko + + Telefonska številka + + Izberite, kdo lahko vidi vašo telefonsko številko in kdo vas lahko z njo kontaktira v Mollyu. + + Kdo lahko vidi mojo številko + + Vaše telefonske številke ne bo videl nihče v Mollyu + + Kdo me lahko najde preko telefonske številke + + Vaša telefonska številka bo vidna osebam in skupinam, ki jim pošiljate sporočila. Ljudje, ki imajo vašo številko med svojimi telefonskimi stiki, jo bodo videli tudi v Mollyu. Kdorkoli Moji stiki Nikogar @@ -3857,17 +3928,17 @@ Šibka Wi-Fi povezava. Preklopljeno na mobilno omrežje. - Z izbrisom vašega računa boste: + Z izbrisom svojega računa boste: Vnesite svojo telefonsko številko - Izbriši račun Signal - Izbriši podatke in profilno fotografijo računa - Izbriši vsa svoja sporočila + Izbris računa + izbrisali podatke in profilno fotografijo računa + izbrisali vsa svoja sporočila Izbriši %1$s v računu plačil Ni vnesene kode države Ni vnesene telefonske številke Vnesena številka se ne ujema s telefonsko številko vašega računa. Ste prepričani, da želite izbrisati svoj račun? - S tem boste izbrisali svoj račun Signal in ponastavili aplikacijo. Ko bo proces končan se bo aplikacija zaprla. + S tem boste izbrisali svoj račun Signal in ponastavili aplikacijo. Ko bo proces končan, se bo aplikacija zaprla. Lokalnih podatkov ni bilo mogoče izbrisati. To lahko storite ročno v sistemskih nastavitvah aplikacije. Zaženi nastavitve aplikacije @@ -3976,7 +4047,7 @@ Deaktiviraj denarnico Vaše stanje - Pred deaktivacijo plačilnega računa je zaželeno, da prenesete svoja sredstva v drugo denarnico. Če jih ne boste prenesli zdaj, bodo ostala v vaši denarnici, ki je povezana s Mollyom, za primer, da boste kdaj ponovno aktivirali plačila. + Pred deaktivacijo plačil je priporočeno, da prenesete svoja sredstva v drugo denarnico. Če jih ne boste prenesli zdaj, bodo ostala v vaši denarnici, ki je povezana s Mollyom, za primer, da boste kdaj ponovno aktivirali plačila. Prenesi preostala sredstva Deaktiviraj brez prenosa Deaktiviraj @@ -4202,7 +4273,7 @@ Sporočanje Izginjajoča sporočila Varnost aplikacije - Prepreči zajem slike zaslona + Prepreči zajem slike zaslona na seznamu nedavnih sporočil in v aplikaciji Sporočila in klici Signal, posredovanje klicev in zakriti pošiljatelj Privzet odštevalnik za nove pogovore Nastavite privzet čas poteka sporočil za vse nove pogovore, ki jih začnete sami. @@ -4671,7 +4742,7 @@ Vaše mesečne donacije so bile preklicane, ker nismo mogli procesirati plačila. Značka ni več vidna na vašem profilu. Vaša tekoča mesečna donacija je bila preklicana. %1$s Vaša značka %2$s ni več vidna na vašem profilu. - Še naprej lahko uporabljate Signal, za nadaljevanje podpore in reaktiviranje vaše značke pa obnovite naročnino. + Še naprej lahko uporabljate Signal, za nadaljevanje podpore aplikaciji in reaktiviranje vaše značke pa obnovite naročnino zdaj. Obnovitev naročnine Pojdi na Google Pay @@ -4714,7 +4785,7 @@ Vaše donacije ni bilo mogoče poslati zaradi napake v omrežju. Preverite povezavo in poskusite znova. - Donacija za: %1$s + Donacija v imenu uporabnika/-ce %1$s %1$s je doniral_a Signalu v vašem imenu @@ -5123,11 +5194,11 @@ Izberite, kdo lahko vidi vašo zgodbo. Spremembe ne bodo vplivale na že poslane zgodbe. - Odgovori & odzivi + Odgovori in odzivi - Dovoli odgovore & odzive + Dovoli odgovore in odzive - Dovolite ljudem, ki lahko vidijo vašo zgodbo, odgovore in odzive + Dovolite odgovore in odzive ljudem, ki lahko vidijo vašo zgodbo Signal povezave @@ -5277,11 +5348,11 @@ - Reagirali ste na zgodbo uporabnika_ce %1$s + Odzvali ste se na zgodbo uporabnika_ce %1$s - Reakcije na vašo zgodbo + Odzivi na vašo zgodbo - Reakcije na zgodbo + Odzivi na zgodbo @@ -5851,5 +5922,15 @@ Izbriši uporabniško ime + + + h + + m + + Nastavi + + Najmanjši čas pred uporabo zaklepanja zaslona je 1 minuta. + diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 47a61dd831..5965d5bf33 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -14,13 +14,14 @@ + Po Jo Fshije Ju lutemi, prisni… Ruaje - Shënim për Veten + Shënim për veten @@ -96,13 +97,13 @@ Duke kontrolluar për mesazhe… - Përdorues të bllokuar - Shtoni përdorues të bllokuar - Përdoruesit e bllokuar nuk do të mund t\'ju telefonojnë ose t\'ju dërgojnë mesazhe. + Përdoruesit e bllokuar + Shto përdorues të bllokuar + Përdoruesit e bllokuar nuk do të mund të të telefonojnë apo të të dërgojnë mesazhe. Nuk ka përdorues të bllokuar - Të bllokohet përdoruesi? + Të bllokohet përdoruesin? %1$s nuk do të jetë në gjendje t\'ju telefonojë ose t\'ju dërgojë mesazhe. - Bllokoje + Blloko @@ -138,8 +139,8 @@ Vazhdo - Blloko dhe dil %1$s? - Të bllokohet %1$s? + Blloko dhe largohu nga %1$s? + Dëshiron të bllokosh %1$s? Ju nuk do të merrni më mesazhe ose përditësime nga ky grup dhe anëtarët nuk do të mund t\'ju shtojnë më në këtë grup. Anëtarët e grupit nuk do të mund t\'ju shtojnë përsëri në këtë grup. Anëtarët e grupit do të mund t\'ju shtojnë sërish në këtë grup. @@ -147,16 +148,16 @@ Ju do të jeni në gjendje t\'i dërgoni mesazhe dhe t\'i telefononi njëri-tjetrit dhe emri e fotografia juaj do të ndahen me ta. Ju do të jeni në gjendje t\'i dërgoni mesazhe njëri-tjetrit. - Personat e bllokuar nuk do të mund t\'ju telefonojnë ose t\'ju dërgojnë mesazhe. - Personat e bllokuar nuk do të mund t\'ju dërgojnë mesazhe. + Personat e bllokuar nuk do të mund të të telefonojnë apo të të dërgojnë mesazhe. + Personat e bllokuar nuk do të mund të të dërgojnë mesazhe. - Blloko marrjen e përditësimeve dhe lajmeve të Signal-it. + Blloko marrjen e përditësimeve dhe lajmeve të Signal. Rifilloni marrjen e përditësimeve dhe lajmeve të Signal-it. - Zhbllokoni %1$s? - Bllokoje - Blloko dhe Dil - Raportoni mesazhet e padëshiruara dhe bllokojini + Dëshiron të zhbllokosh %1$s? + Blloko + Blloko dhe largohu + Raporto mesazhet e padëshiruara dhe bllokoji Sot @@ -447,11 +448,11 @@ %1$s në linjë - Dëshironi të bllokoni kërkesën? + Dëshiron të bllokosh kërkesën? %1$s nuk do të jetë në gjendje t\'i bashkohet ose të kërkojë t\'i bashkohet këtij grupi nëpërmjet liinkut të grupit. Ato ende mund të shtohen në grup manualisht. - Bllokoje kërkesën + Blloko kërkesën Anuloje @@ -496,12 +497,12 @@ Çfiksoji - Heshtoje - Pa zë + Hiqi zërin + Hiqu zërin - Çheshtoje - Me zë + Ktheja zërin + Kthejua zërin Përzgjidhni @@ -542,6 +543,15 @@ +%1$d + + Rilidh pajisjet e tua + + Pajisjet që shtove u shkëputën kur pajisja u çregjistrua. Shko tek parametrat për të rilidhur çdo pajisje. + + Hap parametrat + + Më vonë + Përzgjidhni anëtarët @@ -935,7 +945,7 @@ Njoftomë për Përmendje - Të merren njoftime, kur jeni përmendur në fjalosje të heshtuara? + Dëshiron të marrësh njoftime kur të përmendin në biseda pa zë? Njoftomë përherë Mos më njofto @@ -953,6 +963,16 @@ Emri i përdoruesit u krijua Emri i përdoruesit u kopjua + + Emri i përdoruesit nuk mund të fshihet. Provo sërish më vonë. + + Emri i përdoruesit u fshi + + + + Ka ndodhur një gabim me emrin tënd të përdoruesit, i cili nuk është më i lidhur me llogarinë tënde. Mund të provosh dhe ta vendosësh sërish ose të zgjedhësh një të ri. + + Rregulloje tani @@ -1156,8 +1176,8 @@ Grup i ri Ftoni shokë Use SMS - Dukje - Shtoni foto + Ngjyrat e bisedës + Shto foto profili Përgjigje @@ -1468,14 +1488,14 @@ Pranoje Vazhdo Fshije - Bllokoje - Zhbllokoje + Blloko + Zhblloko Të lejohet %1$s t\\’ju dërgojë mesazh dhe të ndajë emrin dhe foton tuaj me të tjerë? S\\’do ta dijë se e keni parë mesazhin e tij, para se të pranoni këtë. - Të lejohet %1$s t\\’ju dërgojë mesazh dhe të ndajë emrin dhe foton tuaj me të tjerë? S\\’do të merrni ndonjë mesazh, para se ta zhbllokoni. + Dëshiron që %1$s të të dërgojnë mesazh dhe të ndash emrin dhe foton tënde me ta? Nuk do të marrësh asnjë mesazh derisa t\'i zhbllokosh. - Të lejohet %1$s t’ju dërgojë mesazh? S’do të merrni ndonjë mesazh, para se ta zhbllokoni. - Merrni përditësime dhe lajme nga 1%1$s? S’do të merrni ndonjë përditësim, para se t’i zhbllokoni. + Dëshiron që %1$s të të dërgojnë mesazh? Nuk do të marrësh asnjë mesazh derisa t\'i zhbllokosh. + Dëshiron të marrësh përditësime dhe lajme nga %1$s? Nuk do të marrësh asnjë përditësim derisa t\'i zhbllokosh. Të vazhdohet biseda juaj me këtë grup dhe t\\’u jepet emri dhe fotoja juaj anëtarëve të tij? Përmirësojeni këtë grup, që të aktivizohen veçori të reja, të tilla si @përmendje dhe përgjegjës. Anëtarët që nuk kanë dhënë emrin ose foton e tyre te ky grup, do të ftohen të bëhen pjesë. Ky Grup i Dikurshëm s\\’mund të përdoret më, ngaqë është shumë i madh. Madhësia maksimum për grupet është %1$d. @@ -1483,7 +1503,7 @@ Të bëhet anëtarësim në këtë grup dhe të ndahet emri dhe fotoja juaj me anëtarët e tij? S\\’do ta dinë se i keni parë mesazhet e tyre, para se të pranoni këtë. Dëshironi t\'i bashkoheni këtij grupi dhe të ndani me anëtarët e tij emrin dhe foton tuaj? Ju nuk mund t\'i shihni mesazhet e tyre derisa të konfirmoni. Doni të bëheni pjesë e këtij grupi? S\\’do ta dinë se i keni parë mesazhet e tyre, deri sa të pranoni. - Të zhbllokohet ky grup dhe të lejohet të ndajë emrin dhe foton tuaj me anëtarët e tij? S\\’do të merrni ndonjë mesazh, para se ta zhbllokoni. + Dëshiron të zhbllokosh këtë grup dhe të ndash me anëtarët e tij emrin dhe foton tënde? Nuk do të marrësh asnjë mesazh derisa t\'i zhbllokosh. Shihni Anëtar i %1$s @@ -1584,9 +1604,20 @@ Krijoni PIN të ri + + Dërgo kodin SMS + + Regjistrimi i Signal - Ke nevojë për ndihmë për riregjistrimin e PIN-it për Android + + PIN-i yt është një kod %1$d + shifror që ke krijuar që mund të jetë numerik ose alfanumerik.\n\nNëse nuk e mbani mend PIN-in tënd, mund të krijosh një të ri. + + Nëse nuk e mbani mend PIN-in tënd, mund të krijosh një të ri. + + Të kanë mbaruar opsionet e kodit PIN, por mund të hysh ende në llogarinë tënde të Signal duke krijuar një PIN të ri. + Paralajmërim - Nëse çaktivizoni PIN-in, do të humbni krejt të dhënat, kur riregjistroni Signal-in, veç në i kopjeruajtshi dhe rikthefshi dorazi. S\\’mund të aktivizoni Kyçje Regjistrimi, teksa PIN-i është i çaktivizuar. + Nëse çaktivizoni kodin PIN, do t\'i humbni të gjitha të dhënat kur të regjistroni sërish Signal, veç nëse bëni manualisht kopje rezervë dhe rivendosje. Nuk mund të aktivizoni Çelësin e Regjistrimit kur kodi PIN është i çaktivizuar. Çaktivizoje PIN-in @@ -1616,8 +1647,8 @@ Postimi im i përkohshëm - Bllokoje - Zhbllokoje + Blloko + Zhblloko @@ -1711,9 +1742,9 @@ Kamerë - Çheshtoji + Ktheja zërin - Heshtoje + Hiqi zërin Bjeri ziles @@ -1731,7 +1762,7 @@ S\\’do të merrni audio ose video prej tyre dhe as ata prej jush. S\\’mund të merret audio & video prej %1$s S\\’mund të merret audio dhe video prej %1$s - Kjo mund të vijë ngaqë s\\’kanë verifikuar ndryshimin e numrit tuaj të sigurisë, ka një problem me pajisjen e tyre, ose ju kanë bllokuar. + Kjo mund të ndodhë sepse ata nuk e kanë verifikuar ndryshimin e numrit tënd të sigurisë, ka një problem me pajisjen e tyre ose të kanë bllokuar. Fërkojeni që të shihni ndarje ekrani @@ -1768,11 +1799,18 @@ Për t\\’ju ndihmuar të lidheni me shokët tuaj dhe dërgoni mesazhe, Signal-i lyp leje mbi kontaktet dhe median. Kontaktet tuaja ngarkohen duke përdorur pikasje private kontaktesh të Signal-it, që do të thotë se janë të fshehtëzuara skaj-më-skaj dhe kurrë të dukshme për shërbimin Signal. Për t\\’ju ndihmuar të lidheni me shokët tuaj, Signal-i lyp leje mbi kontaktet. Kontaktet tuaja ngarkohen duke përdorur pikasje private kontaktesh të Signal-it, që do të thotë se janë të fshehtëzuara skaj-më-skaj dhe kurrë të dukshme për shërbimin Signal. Keni bërë shumë përpjekje për të regjistruar këtë numër. Ju lutemi, riprovoni më vonë. + + Ke bërë shumë përpjekje për të regjistruar këtë numër. Të lutem provoje përsëri për %1$s. S\\’arrihet të bëhet lidhja te shërbimi. Ju lutemi, kontrolloni lidhjen me rrjetin dhe riprovoni. Format jostandard numrash Numri që dhatë (%1$s) duket të jetë në një format jo standard.\n\nKishit ndërmend %2$s? Molly për Android - Format Numrash Telefoni + U kërkua thirrje + + Kërkohet SMS + + Kërkohet kodi i verifikimit Ju ndan %1$d hap nga parashtrimi i një regjistri diagnostikimesh. Ju ndajnë %1$d hapa nga parashtrimi i një regjistri diagnostikimesh. @@ -1792,6 +1830,16 @@ Thirrje Kod verifikimi Ridërgo kodin + + Ke probleme me regjistrimin? + + • Sigurohu që telefoni yt të ketë sinjal celular për të marrë SMS ose thirrje • Konfirmo se mund të marrësh telefonata në numër\n • Kontrollo nëse ke vendosur saktë numrin tënd të telefonit. + + Për më shumë informacion, ndiqni këto hapa për zgjidhjen e problemeve ose kontaktoni me asistencën + + me këto hapa për zgjidhjen e problemeve + + Kontaktoni asistencën Të aktivizohet Kyçja e Regjistrimeve? @@ -1958,6 +2006,10 @@ Pagesë Mesazh i planifikuar + + Historiku i mesazheve të tua është bashkuar + + %1$s i përket %2$s Përditësim i Molly-it @@ -2030,7 +2082,7 @@ Mesazh MMS i fshehtëzuar për sesion që s\\’ekziston - Heshtoji njoftimet + Hiqi zërin njoftimeve Importim në ecuri e sipër @@ -2088,13 +2140,15 @@ %1$s %2$s Kontakt Reagoi me %1$s ndaj: \"%2$s\". - Reagoi me %1$s ndaj videos tuaj. - Reagoi me %1$s ndaj figurës tuaj. - Reagoi me %1$s ndaj GIF-it tuaj. - Reagoi me %1$s ndaj kartelës tuaj. - Reagoi me %1$s ndaj audios tuaj. - Reagoi me %1$s te media juaj për një parje të vetme. - Reagoi me %1$s ndaj ngjitësit tuaj. + Reagoi me %1$s ndaj videos tënde. + Reagoi me %1$s ndaj figurës tënde. + Reagoi me %1$s ndaj GIF-it tënd. + Reagoi me %1$s ndaj kartelës tënde. + Reagoi me %1$s ndaj audios tënde. + Reagoi me %1$s te media jote, të cilën mund ta shohësh veç një herë. + + Reagoi me %1$s ndaj pagesës tënde. + Reagoi me %1$s ndaj ngjitësit tënd. Ky mesazh u fshi. Të çaktivizohen njoftime Signal për kontakte të sapoardhur? Mund t\\’i aktivizoni sërish që nga Signal > Rregullime > Njoftime. @@ -2298,7 +2352,7 @@ Aktivizo Njoftime Thirrjesh Përditësoni kontaktin - Bllokoje kërkesën + Blloko kërkesën Pa grupe të përbashkët. Shqyrtojini me kujdes kërkesat. Pa kontakte në këtë grup. Shqyrtojini me kujdes kërkesat. Shihni @@ -2487,8 +2541,8 @@ Problem Dërgimi - S\\’u dërgua dot te ju një mesazh, ngjitës, reagim, ose dëftesë leximi nga %1$s. Mund të ketë provuar ta dërgojë drejtpërsëdrejti për ju, ose në një grup. - S\\’u dërgua dot te ju një mesazh, ngjitës, reagim, ose dëftesë leximi nga %1$s. + Një mesazh, ngjitës, reagim, ose verifikim leximi i %1$s nuk u dërgua dot te ti. Mund të ketë provuar ta dërgojë drejtpërsëdrejti te ti ose në një grup. + Një mesazh, ngjitës, reagim, ose verifikim leximi i %1$s nuk u dërgua dot te ti. Emër (i domosdoshëm) @@ -2635,10 +2689,10 @@ Përdor parazgjedhje Përdor vetjake - Heshtoje për 1 orë - Heshtoji për 8 orë - Heshtoje për 1 ditë - Heshtoje për 7 ditë + Hiqi zërin për 1 orë + Hiqi zërin për 8 orë + Hiqi zërin për 1 ditë + Hiqi zërin për 7 ditë Përherë Rregullime parazgjedhje @@ -2677,7 +2731,7 @@ Mbaj të arkivuara bisedat pa zë - Bisedat pa zë të arkivuara do të mbeten të të tilla edhe kur të vijë një mesazh i ri. + Bisedat pa zë të arkivuara do të mbeten të tilla edhe kur të vijë një mesazh i ri. Prodho lidhje për paraparje Merrni, për mesazhet që dërgoni, paraparje lidhjesh drejt e nga sajtet. Ndryshoje frazëkalimin @@ -2971,7 +3025,7 @@ Pagesë e dërguar Pagesë e marrë Pagesë e plotësuar %1$s - Numër blloku + Numër i bllokuar Shpërngule @@ -3056,7 +3110,7 @@ Mesazh i ri për… - Bllokoje përdoruesin + Blloko përdoruesin Shtoje te grupi @@ -3128,10 +3182,10 @@ - Çheshtoji + Bëje me zë - Heshtoji njoftimet + Heshti njoftimet Rregullime grupi @@ -3295,6 +3349,8 @@ Jepni PIN-in tuaj Jepni PIN-in që krijuar për llogarinë tuaj. Ky është tjetër gjë nga kodi juaj i verifikimit me SMS. + + Vendos kodin PIN që krijove për llogarinë tënde. Jepni PIN alfanumerik Jepni PIN numerik PIN i pasaktë. Riprovoni. @@ -3398,7 +3454,10 @@ Rezerva jote përmban një skedar shumë të madh, që nuk mund të rezervohet. Të lutem, fshije dhe krijo një kopje të re rezervë. Prekeni që të administroni kopjeruajtje. Numër i gabuar? + Më telefono (%1$02d:%2$02d) + + Ridërgo kodin (%1$02d:%2$02d) Lidhuni Me Asistencën e Signal-it Regjistrim Signal-i - Kod Verifikimi për Android Kod i gabuar @@ -3406,6 +3465,18 @@ E panjohur Shohë numrin tim të telefonit Të më gjejë përmes numrit të telefonit + + Numër telefoni + + Zgjidh se kush mund ta shohë numrin tënd të telefonit dhe kush mund të të kontaktojë nëpërmjet tij në Molly. + + Kush mund të shohë numrin tim + + Askush nuk do ta shohë numrin tënd të telefonit në Molly + + Kush mund të më gjejë nëpërmjet numrit + + Numri yt i telefonit do të jetë i dukshëm për personat dhe grupet, të cilëve u dërgon mesazhe. Personat që kanë numrin tënd të telefonit në kontaktet e tyre, do ta shohin edhe te Molly. Cilido Kontaktet e mia Askush @@ -3562,8 +3633,8 @@ - Bllokoje - Zhbllokoje + Blloko + Zhblloko Shtoje tek kontaktet S\\’gjendet dot një aplikacion i aftë për hapje kontaktesh. @@ -3615,8 +3686,8 @@ %1$s/%2$s - \"%1$s\" u bllokua. - S\\’u arrit të bllokohej \"%1$s\" + \"%1$s\" është bllokuar. + Bllokimi i \"%1$s\" dështoi \"%1$s\" u zhbllokua. @@ -3644,7 +3715,7 @@ Kontakti juaj Hiqe nga grupi Përditësoni kontaktin - Bllokoje + Blloko Fshije Ndryshoi së fundi emrin e vet të profilit nga %1$s në %2$s @@ -3667,11 +3738,11 @@ Wi-Fi i dobët. Kalo me internetin e celularit. - Fshirja e llogarisë tuaj do të: + Fshirja e llogarisë tënde do të: Jepni numrin e telefonit tuaj Fshije llogarinë - Fshij të dhënat e llogarisë dhe foton e profilit - Fshiji krejt mesazhet e tua + Fshi të dhënat e llogarisë dhe foton e profilit + Fshi të gjitha mesazhet e tua Fshi %1$s te llogaria jote e pagesave S\\’u dha kod vendi S\\’u tregua numër @@ -3784,12 +3855,12 @@ Çaktivizoni Portofolin Depozita juaj - Rekomandohet që të shpërngulni fondet tuaja në një portofol tjetër, përpara se të çaktivizoni pagesat. Nëse zgjidhni të mos shpërngulen fonte tuaja tani, do të mbeten në portofolin tuaj të lidhur me Molly-in, nëse riaktivizoni pagesat. + Rekomandohet që të transferosh fondet e tua në një portofol tjetër, para se të çaktivizosh pagesat. Nëse zgjedh të mos transferosh fondet e tua tani, do të mbeten në portofolin tënd të lidhur me Molly, nëse riaktivizon pagesat. Shpërngulni depozitën e mbetur Çaktivizoje pa e shpërngulur Çaktivizoje Të çaktivizohet pa e shpërngulur? - Depozita juaj do të mbetet në portofolin tuaj të lidhur me Molly-in, nëse zgjidhni të riaktivizoni pagesat. + Depozita jote do të mbetet në portofolin tënd të lidhur me Molly, nëse zgjedh të riaktivizosh pagesat. Gabim gjatë çaktivizimit të portofolit. @@ -4008,7 +4079,7 @@ Shkëmbim Mesazhesh Zhdukje mesazhesh Siguri aplikacioni - Blloko foto ekrani te lista e të rejave dhe brenda aplikacionit + Blloko foto të ekranit te lista e të rejave dhe brenda aplikacionit Mesazhe dhe thirrje Signal, thirrjet kaloji përherë përmes relesh, dhe dërgues të vulosur Kohëmatës parazgjedhje për fjalosje të reja Ujdisni një kohëmatës tretjeje mesazhesh për krejt fjalosjet e reja të nisura nga ju. @@ -4106,7 +4177,7 @@ Jo tani - Përshtatni reagime + Personalizo reagimet Prekeni që të zëvendësoni një emoxhi Riktheje Ruaje @@ -4165,7 +4236,7 @@ Thirrje - Heshtoje + Hiqi zërin Pa zë @@ -4175,10 +4246,10 @@ Hollësi kontakti Shihni numër sigurie - Bllokoje - Blloko grup - Zhbllokoje - Zhbllokoje grupin + Blloko + Blloko grupin + Zhblloko + Zhblloko grupin Shtoje te grup Shihini krejt Shtoni anëtarë @@ -4186,9 +4257,9 @@ Kërkesa & ftesa Lidhje grupi Shtojeni si kontakt - Çheshtoji - Bisedë e heshtuar deri më %1$s - Bisedë e heshtuar përgjithmonë + Ktheja zërin + Biseda pa zë deri më %1$s + Biseda është pa zë përgjithmonë Numri i telefonit u kopjua në të papastër. Numër telefoni Merr distinktivët për profilin tënd, duke përkrahur Signal. Për të mësuar më shumë, kliko mbi një distinktiv. @@ -4204,8 +4275,8 @@ Cilët mund të dërgojnë mesazhe? - Heshtoji njoftimet - S\\’është heshtuar + Hiqu zërin njoftimeve + Nuk është pa zë Përmendje Njofto përherë Mos njofto @@ -4234,7 +4305,7 @@ Fshije - Bllokoje + Blloko Të hiqet %1$s? @@ -4330,7 +4401,7 @@ Shto në postimin e përkohshëm Shtoni një mesazh Shtoni një përgjigje - Dërgoja + Dërguar Mesazh për parje vetëm një herë Një ose më tepër objekte qenë shumë të mëdhenj Një ose më tepër objekte qenë të pavlefshëm @@ -4508,7 +4579,7 @@ Dhurata jote nuk u dërgua dot për shkak të një gabimi në rrjet. Kontrollo lidhjen e internetit dhe riprovo. - Dhurim për %1$s + Dhurim në emër të %1$s %1$s dhuroi për Signal në emrin tënd @@ -4905,9 +4976,9 @@ Zgjidh personat që mund ta shohin postimin e përkohshëm. Ndryshimet nuk zbatohen te postimet e përkohshme që tashmë i ke dërguar. - Përgjigje & reagime + Përgjigjet & reagimet - Lejo përgjigje & reagime + Lejo përgjigjet & reagimet Lëri njerëzit që mund të shohin postimin tënd të përkohshëm të reagojnë dhe të përgjigjen @@ -5057,7 +5128,7 @@ - Ti reagove ndaj postimit të përkohshëm të %1$s + Reagove ndaj postimit të përkohshëm të %1$s Reagoi ndaj postimit tënd të përkohshëm @@ -5322,7 +5393,7 @@ Mesazhet SMS po eksportohen - Kjo mund të dojë pak kohë + Mund të duhet pak kohë Po eksportohet %1$d nga %2$d… @@ -5601,5 +5672,15 @@ Fshi emrin e përdoruesit + + + o + + m + + Vendose + + Koha minimale përpara se të aplikohet kyçja e ekranit është 1 minutë. + diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index aa7925dedd..9c2ee185d9 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -14,13 +14,14 @@ + Да Не Обриши Сачекајте… Сачувај - Подсетник + Моја белешка @@ -98,9 +99,9 @@ Блокирани корисници Додајте блокираног корисника - Блокирани корисници вас не могу звати нити слати поруке. + Блокирани корисници неће моћи да вас зову нити да вам шаљу поруке. Нема блокираних корисника - Блокирати корисника? + Желите ли да блокирате корисника? „%1$s“ неће моћи да вас позове нити шаље поруке. Блокирај @@ -138,8 +139,8 @@ Nastavi - Блокирати и напустити групу %1$s? - Блокирати групу %1$s? + Желите ли да блокирате и напустите групу %1$s? + Желите ли да блокирате преписку %1$s? Више нећете примати поруке или ажурирања од ове групе, чланови више неће бити у могућности да Вас поново додају у ову групу. Чланови групе неће бити у могућности да Вас поново додају у ову групу. Чланови групе ће бити у могућности да Вас поново додају у ову групу. @@ -147,16 +148,16 @@ Моћи ћете да размењујете поруке и позиве и Ваше име и фотографија ће бити подељени са тим контактом. Моћи ћете да размењујете поруке. - Блокиране особе неће бити у могућности да Вас зову или да Вам шаљу поруке. - Блокиране особе неће бити у могућности да Вам шаљу поруке. + Блокирани људи неће моћи да вас зову нити да вам шаљу поруке. + Блокирани људи неће моћи да вам шаљу поруке. - Блокирати ажурирање Signal-а и вести. + Блокирајте добијање новости и обавештења од Signal-а. Поново примати Signal ажурирања и вести. - Одблокирати контакт %1$s? + Желите ли да одблокирате овог корисника (%1$s)? Блокирај Блокирај и напусти - Пријавити нежељену пошту и блокирати + Пријави спам и блокирај Данас @@ -366,7 +367,7 @@ Грешка приликом слања медија - Пријављено као нежељена пошта и блокирано. + Пријављено је као спам и блокирано. Размена SMS порука је тренутно онемогућена. Можете да извезете поруке у другу апликацију на телефону. @@ -447,15 +448,15 @@ %1$s упаљено - Блокирати захтев? + Желите ли да блокирате захтев? %1$s neće moći da se pridruži grupi ili zatraži da se pridruži grupi preko linka. I dalje će moći ručno da se doda u grupu. - Блокирати захтев + Блокирај захтев Поништи - Блокиран/на + Корисник је блокиран Уклони филтер @@ -496,25 +497,25 @@ Откачи - Утишај - Утишај + Искључи обавештења + Искључи обавештења - Поново укључи - Поново укључи + Укључи обавештења + Укључи обавештења - Изабери више + Изабери Архивирај Архивирај - Опозови архивирање - Опозови архивирање + Распакуј + Распакуј - Обриши - Обриши + Избриши + Избриши Изабери све @@ -542,6 +543,15 @@ +%1$d + + Поново повезивање уређаја + + Веза са уређајима које сте додали је прекинута када је регистрација вашег уређаја опозвана. Идите у подешавања да бисте поново повезали све уређаје. + + Отвори подешавања + + Касније + Одаберите чланове @@ -935,7 +945,7 @@ Обавести ме за помињања - Желите ли да примате обавештења када сте поменути у утишаним ћаскањима? + Желите ли да примате обавештења када вас неко помене у ћаскањима за која су искључена обавештења? Увек ме обавештавај Немој да ме обавештаваш @@ -953,6 +963,16 @@ Korisničko ime je izbrisano Korisničko ime je kopirano + + Нисмо успели да избришемо корисничко име. Пробајте поново касније. + + Корисничко име је избрисано + + + + Нешто није у реду са вашим корисничким именом – више није повезано са вашим налогом. Можете покушати поново да га подесите или да изаберете ново. + + Исправите сад @@ -1156,8 +1176,8 @@ Нова група Позивница пријатељима Користи СМС - Изглед - Додајте слику + Боја ћаскања + Додајте профилну фотографију Одговора @@ -1472,10 +1492,10 @@ Одблокирај Да ли дозвољавате да вам %1$s шаље поруке и да подели ваше име и слику са њима? Неће знати да сте прочитали њихове поруке док не прихватите. - Да ли дозвољавате да вам %1$s шаље поруке и да подели ваше име и слику са њима? Нећете примати поруке док их не деблокирате. + Желите ли да дозволите да вам %1$s шаље поруке и види ваше име и слику? Нећете примати поруке док не одблокирате овог корисника. - Допустите %1$s да Вам пише? Нећете примати поруке док га не деблокирате. - Добијајте ажурирања и вести од %1$s? Нећете добијати ажурирања док их не деблокирате. + Желите ли да дозволите да вам %1$s пише? Нећете примати поруке док не одблокирате овог корисника. + Желите ли да вам %1$s шаље новости? Нећете примати новости док не одблокирате овог корисника. Наставити разговор са овом групом и поделите своје име и слику са њеним члановима? Надоградите ову групу да бисте активирали нове функције попут @помињања и администратора. Чланови који нису поделили своје име или слику у овој групи биће позвани да се придруже. Ова Стара Група не може више да се користи јер је превелика. Максимална величина групе је %1$d. @@ -1483,7 +1503,7 @@ Да ли желите да се придружите овој групи и да са њеним члановима поделите ваше име и слику? Неће знати да сте прочитали поруке док не прихватите. Придружите се овој групи и поделите своје име и фотографију са њеним члановима? Нећете видети њихове поруке док не прихватите. Придружите се групи? Неће знати да сте им прочитали поруке док не прихватите. - Да ли желите да одблокирате ову групу и да са њеним члановима поделите ваше име и слику? Нећете добити ни једну поруку док је не одблокирате. + Желите ли да одблокирате ову групу и да са њеним члановима поделите своје име и слику? Нећете примати поруке док је не одблокирате. Приказ Члан %1$s @@ -1584,9 +1604,20 @@ Направи нови PIN + + Пошаљи SMS шифру + + Регистрација Signal-а – Помоћ при поновној регистрацији PIN-а на Android-у + + Ваш PIN је шифра од %1$d или више цифара коју сте креирали и која може бити нумеричка или алфанумеричка.\n\nАко не можете да се сетите PIN-а, можете да креирате нови. + + Ако не можете да се сетите PIN-а, можете да креирате нови. + + Искористили сте све покушаје уноса PIN-а, али и даље можете да приступите свом налогу на Signal-у тако што ћете креирати нови PIN. + Упозорење - Ако онемогућите PIN, изгубићете све податке када поново региструјете Signal, осим ако ручно направите резервне копије и вратите податке. Не можете укључити закључавање регистрације док је PIN онемогућен. + Ако искључите PIN, изгубићете све податке када поново региструјете Signal, осим ако ручно не направите резервне копије и вратите податке. Не можете укључити закључавање регистрације ако је PIN искључен. Искључи PIN @@ -1711,9 +1742,9 @@ Камера - Обавести + Укључи обавештења - Утишај + Искључи обавештења Звони @@ -1726,12 +1757,12 @@ - %1$s је блокиран + Корисник је блокиран (%1$s) Више информација Нећете добити њихов аудио или видео, а ни они ваш. Не може да се прими звук & видео од %1$s Не може да се прими звук и видео од %1$s - То је можда зато што нису потврдили промену вашег сигурносног броја, постоји проблем са њиховим уређајем или су вас блокирали. + То је можда зато што корисник није потврдио промену вашег сигурносног броја, постоји проблем са његовим уређајем или вас је блокирао. Превуците да бисте видели дељење екрана @@ -1768,11 +1799,18 @@ Signal-у су потребна дозволе за контакте и медије да би се лакше прикачили са пријатељима и слали поруке. Ваши контакти су послани са Signal-ом, што знаћи са си шифровани и невидљиви са стране Signal сервиса. Signal-у су потребна дозволе за контакте да би се лакше прикачили са пријатељима. Ваши контакти су послани са Signal-ом, што знаћи са си шифровани и невидљиви са стране Signal сервиса. Превише пута сте покушали да региструјете овај број. Покушајте поново касније. + + Превише пута сте покушали да региструјете овај број. Пробајте поново за %1$s. Није могуће успоставити везу са сервисом. Проверите да ли сте повезани на интернет и покушајте поново. Не стандардни формат цифре Унета цифра (%1$s) нема стандардни формат.\n\nДа ли сте мислили на %2$s? Molly Android - Формат броја телефона + Затражен позив + + SMS је затражен + + Шифра за верификацију је затражена Преостаје вам још %1$d корак да бисте послали евиденцију отклањања грешака. Преостаје вам још %1$d корака да бисте послали евиденцију отклањања грешака. @@ -1792,6 +1830,16 @@ Позови Шифра за верификацију Пошаљи шифру поново + + Имате проблема са регистрацијом? + + • Проверите да ли ваш телефон има мобилни сигнал за примање SMS-а или позива\n • Проверите да ли можете да примате телефонске позиве на овај број\n • Проверите да ли сте исправно унели свој број телефона. + + Ако вам је потребно више информација, пратите ове кораке за решавање проблема или се обратите подршци + + ове кораке за решавање проблема + + Обратите се подршци Укључи закључавање регистрације? @@ -1951,13 +1999,17 @@ Искористили сте значку - Реаговао/ла је %1$s на вашу причу. + Реаговао/ла је %1$s на вашу причу - Реаговао/ла је %1$s на причу. + Реаговао/ла је %1$s на причу Плаћање Планирана порука + + Историја порука је спојена + + %2$s је власник броја %1$s Надоградња Molly-a @@ -2030,7 +2082,7 @@ ММС порука шифрована за непостојећу сесију - Утишај обавештења + Искључи обавештења Увоз у току @@ -2087,14 +2139,16 @@ Необезбеђени СМС %1$s %2$s Контакт - Реаговао %1$s на: „%2$s”. - Реакција %1$s на ваш видео. - Реакција %1$s на вашу слику. - Реакција %1$s на ваш GIF. - Dobili ste reakciju %1$s na vaš fajl. - Реакција %1$s на ваш звучни запис. - Реакција %1$s на ваш једнократни медиј. - Реакција %1$s на вашу налепницу. + Реаговао/ла је %1$s на: „%2$s“. + Реаговао/ла је %1$s на ваш видео. + Реаговао/ла је %1$s на вашу слику. + Реаговао/ла је %1$s на ваш GIF. + Реаговао/ла је %1$s на ваш фајл. + Реаговао/ла је %1$s на ваш аудио. + Реаговао/ла је %1$s на ваш једнократни садржај. + + Реаговао/ла је %1$s на вашу уплату. + Реаговао/ла је %1$s на вашу налепницу. Ова порука је обрисана. Искључити нотификације контакта који се придружио Signal-у? Можете их поново омогућити у Signal > Подешавања > Нотификације. @@ -2298,7 +2352,7 @@ Обавештења о позиву Ажурирај контакт - Блокирати захтев + Блокирај захтев Нема заједничких група. Пажљиво прегледајте захтеве. Нема контаката у овој групи. Пажљиво прегледајте захтеве. Приказ @@ -2487,8 +2541,8 @@ Проблем у испоруци - Порука, налепница, реакција, потврда о читању или медиј од %1$s није могло да се испоручи. Можда су то покушали да вам пошаљу директно или у групи. - Порука, налепница, реакција или потврда о читању од %1$s није вам достављена. + Порука, налепница, реакција или потврда о читању коју шаље %1$s није вам достављена. Овај корисник је можда покушао да вам је пошаље директно или у групи. + Порука, налепница, реакција или потврда о читању коју шаље %1$s није вам достављена. Име (потребно) @@ -2555,7 +2609,7 @@ На чекању - Послато + Послато кориснику Послао Достављено Прочитао @@ -2635,10 +2689,10 @@ Користи подразумевано Користи посебно - Утишај 1 сат - Утишај 8 сати - Утишај 1 дан - Утишај 7 сати + Искључи обавештења 1 сат + Искључи обавештења 8 сати + Искључи обавештења 1 дан + Искључи обавештења 7 сати Увек Подразумевана поставка @@ -2675,9 +2729,9 @@ Користите фотографије из адресара Користите фотографије контакта из Вашег адресара ако је доступна - Ostavi ćaskanja sa isključenim obaveštenjima arhivirana + Остави ћаскања са искљученим обавештењима архивирана - Ćaskanja sa isključenim obaveštenjima koja su arhivirana ostaće arhivirana kada stigne nova poruka. + Ћаскања са искљученим обавештењима која су архивирана остаће архивирана када стигне нова порука. Generisanje prikaza linka Direktno sa veb-sajtova preuzimajte prikaze linkova za poruke koje šaljete. Измени лозинку @@ -2757,7 +2811,7 @@ Напредна PIN подешавања Бесплатне приватне поруке и позиви корисницима Signal-a Пошаљи дневник исправљања грешака - Обриши налог + Брисање налога Компатибилност са „бежичним позивањем“ Омогућите ако ваш уређај доставља СМС/ММС преко бежичне мреже (омогућите само ако је „WiFi Calling“ омогућено на вашем уређају) Тајна тастатура @@ -2971,7 +3025,7 @@ Пошаљи уплату Примљена уплата Уплата завршена %1$s - Блокирај број + Број блока Пренос @@ -3056,7 +3110,7 @@ Нова порука за… - Блокирати корисника + Блокирај корисника Додај у групу @@ -3131,7 +3185,7 @@ Укључи обавештења - Утишај обавештења + Искључи обавештења Поставке групе @@ -3295,6 +3349,8 @@ Унесите ваш PIN Унесите PIN вашег налога. Ово није исто што и ваш SMS верификациони кôд. + + Унесите PIN који сте креирали за свој налог. Унеси алфанумерички PIN Унеси нумерички PIN Нетачан PIN. Покушајте поново. @@ -3398,7 +3454,10 @@ Ваша резервна копија садржи веома велики фајл за који се не може креирати резервна копија. Обришите га и креирајте нову резервну копију. Додирните за управљање резервним копијама. Погрешан број? + Позови ме (%1$02d:%2$02d) + + Поново пошаљи шифру (%1$02d:%2$02d) Контактирајте Signal подршку Signal регистрација - верификациони кôд за Android Погрешан кôд @@ -3406,6 +3465,18 @@ Непозната Види мој број телефона Проналази ме по броју телефона + + Број телефона + + Одаберите ко може да види ваш број телефона и ко може да вам се обрати у Molly-у преко њега. + + Ко може да види мој број + + Нико неће видети ваш број телефона у Molly-у + + Ко може да ме пронађе преко броја телефона + + Ваш број телефона ће бити видљив људима и групама којима шаљете поруке. Људи који имају ваш број у својим контактима на телефону такође ће га видети у Molly-у. Сви Моји контакти никога @@ -3615,9 +3686,9 @@ %1$s/%2$s - „%1$s“ је блокиран/на. - Неуспешно блокирање „%1$s“ - „%1$s“ је деблокиран/на. + Корисник је блокиран (%1$s). + Блокирање корисника није успело (%1$s) + Корисник је одблокиран (%1$s). Прегледајте чланове @@ -3667,17 +3738,17 @@ Slab Wi-Fi – prebačeno na korišćenje mobilnog interneta. - Брисање налога ће: + Ако избришете налог: Унесите ваш број телефона - Обриши налог - Обрисати информације о налогу и слику профила - Обрисати све ваше поруке + Избриши налог + Биће вам избрисане све информације о налогу и профилна фотографија + Биће вам избрисане све поруке Обрисати %1$s на рачуну плаћања Није изабран позивни број државе Није наведен број Унет телефонски број се не подудара са вашим налогом. Да ли заиста желите да обришете ваш налог? - Ово ће обрисати ваш Signal налог и ресетовати апликацију. Апликација ће се затворити након завршетка поступка. + Овим ћете избрисати налог на Signal-у и ресетовати апликацију. Апликација ће се затворити након завршетка поступка. Брисање локалних података није успело. Можете их ручно обрисати у поставкама апликације система. Поставке покретања апликације @@ -3784,12 +3855,12 @@ Деактивирати новчаник Ваш салдо - Препоручује се да преносите средства на другу адресу новчаника пре деактивирања плаћања. Ако одлучите да не преносите своја средства сада, они ће остати у вашем новчанику везаном за Molly ако поново активирате уплате. + Препоручујемо вам да пренесете средства на другу адресу новчаника пре деактивирања плаћања. Ако не пренесете средства сада, остаће у вашем новчанику повезаном за Molly ако поново активирате плаћања. Пренос преосталог салда Деактивирање без преноса података Деактивирати Деактивирање без преноса података? - Ваш салдо ће остати у вашем новчанику везаном за Molly ако поново активирате уплате. + Ваша средства ће остати у вашем новчанику повезаном за Molly ако поново активирате плаћања. Грешка деактивирајући новчаник. @@ -4003,12 +4074,12 @@ Креирај профил - Блокиран/на + Корисник је блокиран %1$d контаката Поруке Самонестајуће поруке Безбедност апликације - Блокирање снимка екрана у списку недавних апликација и унутар апликације + Блокирајте снимке екрана у листи недавних апликација и унутар апликације Поруке и позиви преко Signal-а, увек преузмеравај позиве, запечаћени пошиљалац Подразумевани тајмер за нове преписке Подесите подразумевани тајмер нестајања поруке за све нове преписке које сте започели. @@ -4165,9 +4236,9 @@ Позови - Утишај + Искључи обавештења - Утишано + Обавештења су искључена Претрага Самонестајуће поруке @@ -4187,8 +4258,8 @@ Link za grupu Додај у контакте Укључи обавештења - Преписка је утишана до %1$s - Преписка је заувек утишана + Обавештења у преписци су искључена до %1$s + Обавештења за преписку су трајно искључена Број телефона је копиран у оставу. Број телефона Добијте значке за профил тако што ћете донирати Signal-у. Додирните значку да сазнате више. @@ -4204,8 +4275,8 @@ Ко може да шаље поруке? - Утишај обавештења - Није утишано + Искључи обавештења + Обавештења нису искључена Помињања Увек обавести Немој обавештавати @@ -4242,7 +4313,7 @@ %1$s је уклољен(а) - %1$s је блокиран(а) + Корнисник је блокиран (%1$s) Нисмо успели да уклонимо корисника (%1$s) @@ -4330,7 +4401,7 @@ Додај садржај у причу Додати поруку Додај одговор - Пошаљи у + Пошаљи кориснику Порука виђена једном Jedna ili više stavki su prevelike Jedna ili više stavki su nevažeće @@ -4508,7 +4579,7 @@ Ваша донација није послата због грешке у мрежи. Проверите да ли сте повезани и пробајте поново. - Донација за пријатеља (%1$s) + Донација у име корисника (%1$s) %1$s је дао/ла донацију Signal-у у ваше име @@ -4905,9 +4976,9 @@ Бирајте ко може да види вашу причу. Промене неће утицати на приче које сте већ послали. - Одговори & реакције + Одговори и реакције - Дозволи одговоре & реакције + Активирај одговоре и реакције Дозволите људима који могу да виде вашу причу да реагују и одговоре на њу @@ -5087,7 +5158,7 @@ Потврдите донацију - Пошаљи у + Пошаљи кориснику Прималац ће бити обавештен о донацији у приватној поруци. Унесите поруку у наставку. @@ -5601,5 +5672,15 @@ Избришите корисничко име + + + h + + m + + Подеси + + Минимално време за активирање закључавања екрана је 1 минут. + diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5c91c5b5ce..8596ac476f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -14,6 +14,7 @@ + Ja Nej @@ -150,7 +151,7 @@ Blockerade personer kommer inte att kunna ringa eller skicka meddelanden till dig. Blockerade personer kommer inte att kunna skicka meddelanden till dig. - Blockera att ta emot Signal-uppdateringar och nyheter. + Blockera att få uppdateringar och nyheter från Signal. Återuppta att ta emot Signal-uppdateringar och nyheter. Avblockera %1$s? @@ -500,8 +501,8 @@ Tysta - Av-tysta - Av-tysta + Avtysta + Avtysta Markera @@ -542,6 +543,15 @@ +%1$d + + Länka om dina enheter + + Enheterna du lade till länkades av när din enhet avregistrerades. Gå till Inställningar för att länka om alla enheter. + + Öppna inställningar + + Senare + Välj medlemmar @@ -953,6 +963,16 @@ Användarnamn skapat Användarnamn kopierat + + Det gick inte att ta bort användarnamn. Försök igen senare. + + Användarnamn raderat + + + + Något gick fel med ditt användarnamn. Det är inte längre tilldelat ditt konto. Du kan försöka ställa in det igen eller välja ett nytt. + + Åtgärda nu @@ -1156,8 +1176,8 @@ Ny grupp Bjud in vänner Använd SMS - Utseende - Lägg till foto + Chattfärger + Lägg till en profilbild Svar @@ -1472,7 +1492,7 @@ Avblockera Tillåt att %1$s meddelar dig och dela ditt namn och foto? Personen vet inte att du har sett personens meddelande förrän du accepterar. - Tillåt att %1$s meddelar dig och dela ditt namn och foto? Du får inte några meddelanden förrän du har avblockerat personen. + Tillåt att %1$s skickar meddelanden till dig och dela ditt namn och foto? Du får inte några meddelanden förrän du har avblockerat personen. Låta %1$s skicka meddelanden till dig? Du kommer inte att få några meddelanden förrän du avblockerar dem. Få uppdateringar och nyheter från %1$s? Du kommer inte att få några uppdateringar förrän du avblockerar dem. @@ -1483,7 +1503,7 @@ Gå med i denna grupp och dela ditt namn och foto med dess medlemmar? De vet inte att du har sett deras meddelanden förrän du accepterar. Gå med i denna grupp och dela ditt namn och foto med dess medlemmar? Du kommer inte se deras meddelanden förrän du accepterar. Gå med i denna grupp? De vet inte att du har sett deras meddelanden förrän du accepterar det. - Avblockeringen av denna grupp och dela ditt namn och foto med dess medlemmar? Du får inte några meddelanden förrän du har avblockerat dem. + Avblockera denna grupp och dela ditt namn och foto med dess medlemmar? Du får inte några meddelanden förrän du har avblockerat dem. Visa Medlem av %1$s @@ -1584,6 +1604,17 @@ Skapa ny PIN-kod + + Skicka sms-kod + + Signal-registrering – Behöver hjälp med att registrera pinkoden för Android + + Din pinkod är en kod på minst %1$d siffror som du har skapat. Den kan vara numerisk eller alfanumerisk.\n\nOm du inte kommer ihåg din pinkod kan du skapa en ny. + + Om du inte kommer ihåg din pinkod kan du skapa en ny. + + Du har gjort för många försök att gissa din pinkod, men du kan fortfarande komma åt ditt Signal-konto genom att skapa en ny. + Varning Om du inaktiverar PIN-koden kommer du att förlora alla data när du registrerar om Signal om du inte säkerhetskopierar och återställer manuellt. Du kan inte aktivera registreringslåset medan PIN-koden är inaktiverad. @@ -1711,9 +1742,9 @@ Kamera - Av-tysta + Ljud på - Tysta + Ljud av Ring @@ -1768,11 +1799,18 @@ Signal behöver kontakter och mediabehörigheter för att hjälpa dig att ansluta till vänner och skicka meddelanden. Dina kontakter laddas upp med Signals privata kontaktupptäckt, vilket innebär att de är ände-till-ände-krypterade och aldrig synliga för Signal-tjänsten. Signal behöver kontaktbehörighet för att hjälpa dig att få kontakt med vänner. Dina kontakter laddas upp med Signals privata kontaktupptäckt, vilket innebär att de är ände-till-ände-krypterade och aldrig synliga för Signal-tjänsten. Du har gjort för många försök att registrera detta nummer. Försök igen senare. + + Du har gjort för många försök att registrera detta nummer. Försök igen om %1$s. Det går inte att ansluta till tjänsten. Kontrollera nätverksanslutning och försök igen. Icke-standardiserat sifferformat Siffran du angav (%1$s) verkar vara ett icke-standardformat.\n\nMenade du %2$s? Molly Android - Telefonnummerformat + Samtal begärt + + Sms begärt + + Verifieringskod begärd Du är nu %1$d steg bort från att skicka in en felsökningslogg. Du är nu %1$d steg bort från att skicka in en felsökningslogg. @@ -1792,6 +1830,16 @@ Ring Verifieringskod Skicka koden på nytt + + Har problem med att registrera? + + • Se till att din telefon har en mobilsignal för att ta emot ditt sms eller samtal\n • Bekräfta att du kan ta emot ett telefonsamtal till numret\n • Kontrollera att du har angett ditt telefonnummer korrekt. + + För mer information, följ dessa felsökningssteg eller kontakta supporten + + dessa felsökningssteg + + Kontakta supporten Aktivera registreringslåset? @@ -1958,6 +2006,10 @@ Betalning Schemalagt meddelande + + Din meddelandehistorik har slagits samman + + %1$s tillhör %2$s Molly-uppdatering @@ -2094,6 +2146,8 @@ Reagerade %1$s på din fil Reagerade %1$s på din ljudfil. Reagerade %1$s på din visa-en-gång media. + + Reagerade %1$s på din betalning. Reagerade %1$s på dina klistermärken. Detta meddelande togs bort. @@ -2487,7 +2541,7 @@ Leveransproblem - Ett meddelande, klistermärke, reaktion eller läskvitto kunde inte levereras till dig från %1$s. Det kan ha försökt att skickas direkt till dig eller i en grupp. + Ett meddelande, klistermärke, reaktion eller läskvitto kunde inte levereras till dig från %1$s. De kan ha försökt att skicka det direkt till dig eller i en grupp. Ett meddelande, klistermärke, reaktion eller läskvitto kunde inte levereras till dig från %1$s. @@ -2675,9 +2729,9 @@ Använd foton från adressboken Visa kontaktfoton från din adressbok om tillgängliga - Håll avstängda chattar arkiverade + Håll tystade chattar arkiverade - Avstängda chattar som är arkiverade förblir arkiverade när ett nytt meddelande kommer. + Tystade chattar som är arkiverade förblir arkiverade när ett nytt meddelande kommer. Skapa länkförhandsgranskningar Hämta länkförhandsgranskningar direkt från webbplatser för meddelanden du skickar. Ändra lösenord @@ -3128,7 +3182,7 @@ - Av-tysta + Avtysta Tysta aviseringar @@ -3295,6 +3349,8 @@ Ange din PIN-kod Ange PIN-koden du skapade för ditt konto. Denna skiljer sig från din SMS-verifieringskod. + + Ange pinkoden du skapade för ditt konto. Ange alfanumerisk PIN-kod Ange numerisk PIN-kod Felaktig PIN-kod. Försök igen. @@ -3398,7 +3454,10 @@ Din säkerhetskopia innehåller en stor fil som inte kan säkerhetskopieras. Radera den och skapa en ny säkerhetskopia. Tryck för att hantera säkerhetskopior. Fel nummer? + Ring mig (%1$02d:%2$02d) + + Skicka kod igen (%1$02d:%2$02d) Kontakta Signal-support Signal-registrering - Verifieringskod för Android Felaktig kod @@ -3406,6 +3465,18 @@ Okänd Se mitt telefonnummer Hitta mig med telefonnummer + + Telefonnummer + + Välj vem som kan se ditt telefonnummer och vem som kan använda det för att kontakta dig på Molly. + + Vem kan se mitt nummer + + Ingen kommer att se ditt telefonnummer på Molly + + Vem kan hitta mig med nummer + + Ditt telefonnummer kommer att vara synligt för personer och grupper som du skickar meddelanden till. Personer som har ditt nummer i sina telefonkontakter ser det också på Molly. Alla Mina kontakter Ingen @@ -3667,11 +3738,11 @@ Svagt wifi. Växlat till mobilnät. - Borttagning av ditt konto kommer att: + Om du tar bort ditt konto kommer: Ange ditt telefonnummer Ta bort konto - Ta bort din kontoinformation och profilfoto - Ta bort alla dina meddelanden + Din kontoinformation och profilfoto raderas + Alla dina meddelanden raderas Ta bort %1$s i ditt betalningskonto Ingen landskod specificerad Inget nummer specificerat @@ -4165,7 +4236,7 @@ Ring - Ljud av + Tysta Tystad @@ -4186,7 +4257,7 @@ Förfrågningar & inbjudningar Grupplänk Lägg till som en kontakt - Av-tysta + Avtysta Konversation tystad till %1$s Konversation tystad för alltid Kopierat telefonnummer till urklipp. @@ -4242,7 +4313,7 @@ %1$s har tagits bort - %1$s har blivit blockerad + %1$s har blockerats Det går inte att ta bort %1$s @@ -4453,7 +4524,7 @@ Månatlig donation avbruten Ditt Boost-märke har upphört att gälla och är inte längre synligt på din profil. - Du kan återaktivera ditt Boost-märke för ytterligare 30 dagar med ett engångsbidrag. + Du kan återaktivera ditt Boost-märke i ytterligare 30 dagar med ett engångsbidrag. Du kan fortsätta att använda Signal men för att stödja teknik byggd för dig, överväg att bli en upprätthållare genom att göra en månatlig donation. Bli en upprätthållare @@ -4465,7 +4536,7 @@ Din återkommande månatliga donation avbröts eftersom vi inte kunde behandla din betalning. Ditt märke visas inte längre i din profil. Din återkommande månatliga donation avbröts. %1$s Ditt %2$s-märke är inte längre synligt på din profil. - Du kan fortsätta använda Signal men för att stödja appen och återaktivera ditt märke, förnya nu. + Du kan fortsätta använda Signal men förnya nu för att stödja appen och återaktivera ditt märke. Förnya abonnemang Gå till Google Pay @@ -4508,7 +4579,7 @@ Din donation kunde inte skickas på grund av ett nätverksfel. Kontrollera din anslutning och försök igen. - Donation till %1$s + Donation på uppdrag av %1$s %1$s har donerat till Signal å dina vägnar @@ -4905,9 +4976,9 @@ Välj vilka som kan se din story. Ändringar påverkar inte stories som du redan har skickat. - Svar & reaktioner + Svar och reaktioner - Tillåt svar & reaktioner + Tillåt svar och reaktioner Låt personer som kan se din story reagera och svara @@ -5601,5 +5672,15 @@ Ta bort användarnamn + + + tim + + min + + Ställ in + + Minsta tid innan skärmlåset aktiveras är 1 minut. + diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index b7794d356b..98fe75ae43 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -14,13 +14,14 @@ + Ndio Hapana Futa Tafadhali subiri… Hifadhi - Kiandiko Kwangu + Kikumbusho Kwangu @@ -96,10 +97,10 @@ Inatafuta ujumbe… - Watumiaji waliozuiwa - Ongeza mtumiaji aliyezuiwa - Watumiaji waliozuiwa hawataweza kukupigia simu au kukutumia ujumbe. - Hakuna watumiaji waliozuiwa + Watumiaji waliozuiliwa + Ongeza mtumiaji aliyezuiliwa + Watumiaji waliozuiliwa hawataweza kukupigia simu au kukutumia ujumbe. + Hakuna watumiaji waliozuiliwa Ungependa kumzuia mtumiaji? \"%1$s\" hawataweza kukupigia simu au kukutumia ujumbe. Zuia @@ -138,8 +139,8 @@ Endelea - Ungependa kuzuia na uondoke kwenye %1$s? - Ungependa kumzuia %1$s? + Zuia na uondoke %1$s? + Zuia %1$s? Hutapokea tena ujumbe au arifa kutoka kwa kikundi hiki, na washiriki hawataweza kukuongeza kwenye kikundi hiki tena. Washiriki wa kikundi hawataweza kukuongeza kwenye kikundi hiki tena. Washiriki wa kikundi wataweza kukuongeza kwenye kundi hili tena. @@ -147,16 +148,16 @@ Mtaweza kutumiana ujumbe na kupigiana simu, na jina na picha yako itashirikiwa nao. Mtaweza kutumiana ujumbe. - Watu waliozuiwa hawataweza kukupigia simu wala kukutumia ujumbe. - Watu waliozuiwa hawataweza kukutumia jumbe. + Watu waliozuiliwa hawataweza kukupigia simu au kukutumia ujumbe. + Watu waliozuiliwa hawataweza kukutumia jumbe. - Zuia kupata arifa na habari za Signal. + Zuia kupata masasisho na habari za Signal. Rejea kupata arifa na habari za Signal. - Ungependa kuacha kumzuia %1$s? + Ondoa kizuizi kwa %1$s? Zuia Zuia na Uondoke - Ripoti barua taka na uzuie + Ripoti spam na uzuie Leo @@ -318,7 +319,7 @@ Ujumbe wa Signal Wacha tubadili kwa Molly 1%1$s Tafadhali chagua mawasiliano - Fungua + Ondoa kizuizi Kiambatisho kimezidi ukubwa wa aina ya ujumbe unaotuma Haiwezi kurekodi sauti! Huwezi kutuma ujumbe kwenye kikundi hiki kwa sababu wewe si mwanachama tena. @@ -366,7 +367,7 @@ Hitilafu imetokea wakati wa kutuma media - Imeripotiwa kuwa taka na imezuiwa. + Imeripotiwa spam na imezuiliwa. Hauwezi kutuma jumbe za SMS. Unaweza kuhamisha jumbe zako kwenye programu nyingine katika simu yako. @@ -455,7 +456,7 @@ Ghairi - Zuiwa + Zuiliwa Futa chujio @@ -481,35 +482,35 @@ Soma - Iliyosomwa + Soma - Isiyosomwa - Haijasomwa + Haijasomwa + Hazijasomwa - Gongelea - Bandika + Pin + Pin - Bandua - Bandua + Unpin + Unpin - Toa sauti + Nyamazisha Nyamazisha - Weka sauti + Washa Washa Chagua - Weka pembeni + Hifadhi Hifadhi - Toa pembeni + Toa hifadhini Toa hifadhini @@ -542,6 +543,15 @@ +%1$d + + Unganisha tena vifaa vyako + + Vifaa ulivyoviongeza viliondolewa wakati kifaa chako kilipokuwa hakijasajiliwa. Nenda kwenye Mipangilio ili kuunganisha tena vifaa. + + Fungua mipangilio + + Baadaye + Chagua wanachama @@ -953,6 +963,16 @@ Jina la mtumiaji limeundwa Jina la mtumiaji limenakiliwa + + Imeshindikana kufuta jina la mtumiaji. Jaribu tena baadaye. + + Jina la mtumiaji limefutwa + + + + Kuna tatizo limetokea kwenye jina lako la mtumiaji, halijaandikishwa tena katika akaunti yako. Unaweza kujaribu na kuweka upya au unaweza kuchagua jingine. + + Rekebisha sasa @@ -1156,8 +1176,8 @@ Kikundi kipya Alika marafiki Tumia SMS - Muonekano - Ongeza picha + Rangi ya gumzo + Ongeza picha ya wasifu Majibu @@ -1469,13 +1489,13 @@ Endelea Futa Zuia - Fungua + Ondoa kizuizi Ungependa kumruhusu %1$s akutumie ujumbe na ushiriki naye jina na picha yako? Hatajua umeona ujumbe wake hadi utakapokubali. - Ungependa kumruhusu %1$s akutumie ujumbe na ushiriki naye jina na picha yako? Hutapokea ujumbe wowote mpaka uache kumzuia. + Je ungependa kumruhusu %1$s akutumie ujumbe na ushiriki naye jina na picha yako? Hutapokea ujumbe wowote mpaka utakapoondoa kizuizi kwake. - Ruhusu %1$s akutumie ujumbe? Hutapata jumbe zozote mpaka utakapowaondolea zuio. - Pata sasisho na habari kutoka kwa %1$s? Hutapata sasisho zozote mpaka utakapowaondolea zuio. + Je ungependa kumruhusu %1$s akutumie ujumbe? Hutapata jumbe zozote mpaka utakapoondoa kizuizi kwake. + Pata masasisho na habari kutoka kwa %1$s? Hutapata sasisho zozote mpaka utakapoondoa kizuizi kwao. Ungependa kuendelea kuzungumza na kikundi hiki na ushiriki jina na picha yako na wanachama wake? Boresha kikundi hiki ili uwezeshe huduma mpya kama vile @kutajwa na kuwa na wasimamizi. Wanachama ambao hawajashiriki jina au picha zao katika kikundi hiki wataalikwa kujiunga. Kikundi hiki Kizee hakiwezi tena kutumiwa kwa sababu ni kikubwa mno. Ukubwa wa juu zaidi wa kikundi ni %1$d. @@ -1483,7 +1503,7 @@ Ungependa kujiunge na kikundi hiki na ushiriki jina na picha yako na washiriki wake? Hawatajua umeona ujumbe wao hadi utakapokubali. Jiunge na kundi hili na uwashirikishe wanachama picha na jina lako? Hutoona jumbe zao mpaka utakapokubali. Ungependa kujiunga na kikundi hiki? Hawatajua umeona ujumbe wao hadi utakapokubali. - Zuia kikundi hiki na ushiriki jina na picha yako na washiriki wake? Hautapokea ujumbe wowote mpaka uwafungue. + Ondoa kizuizi kwa kikundi hiki na ushiriki jina na picha yako na washiriki wake? Hautapokea ujumbe wowote mpaka utakapoondoa kizuizi kwao. Tazama Mwanachama wa %1$s @@ -1584,9 +1604,20 @@ Unda PIN mpya + + Tuma kodi ya SMS + + Usajili wa Signal - Unahitaji Msaada kusajili PIN ya Android + + PIN yako ni %1$d+ kodi ya kidijitali uliyounda inaweza kuwa ya tarakimu au ya tarakimu na maandishi.\n\nKama hukumbuki PIN yako, unaweza ukaunda mpya. + + Kama hukumbuki PIN yako, unaweza ukaunda mpya. + + Nafasi za kukisia PIN zimekwisha, lakini bado unaweza kuingia kwenye akaunti yako ya Signal kwa kuunda PIN mpya. + Onyo - Ukizima PIN, utapoteza data yote utakaposajili upya Signal isipokuwa ukihifadhi nakala yake na kuirejesha mwenyewe. Huwezi kuwasha Kufuli ya Usajili wakati PIN imezimwa. + Ukizima PIN, utapoteza data zote utakaposajili upya Signal isipokuwa kama ukihifadhi nakala yake na kuirejesha mwenyewe. Huwezi kuwasha Kufuli la Usajili wakati PIN imezimwa. Lemaza PIN @@ -1617,7 +1648,7 @@ Zuia - Fungua + Ondoa kizuizi @@ -1713,7 +1744,7 @@ Washa - Zima + Nyamazisha Piga @@ -1726,12 +1757,12 @@ - %1$s amezuiwa + %1$s amezuiliwa Maelezo zaidi Hutapokea sauti au video yake na hatapokea yako. Huwezi kupokea sauti na video kutoka kwa %1$s Huwezi kupokea sauti na video kutoka kwa %1$s - Huenda ni kwa sababu hajathibitisha mabadiliko ya nambari yako ya usalama, kifaa chake kina hitilafu au amekuzuia. + Huenda hii ni kwa sababu hawajathibitisha mabadiliko ya nambari yako ya usalama, vifaa vyao vina hitilafu au amekuzuia. Telezesha kidole ili uone skrini ya kushiriki @@ -1768,11 +1799,18 @@ Signal inahitaji idhini za mawasiliano na media ili kukuwezesha kuwasiliana na marafiki na kutuma jumbe. Wawasiliani wako wamesasishwa kwa kutumia mfumo wa siri wa kuvumbua wawasiliani wa Signal, inayomaanisha zipo kwenye mfumo wa end-to-end na haionekani kwenye huduma za Signal. Signal inahitaji idhini ya wawasiliani wako kukuwezesha kuwasiliana na marafiki. Wawasiliani wako wamesasishwa kwa kutumia mfumo wa siri wa kuvumbua wawasiliani wa Signal, inayomaanisha zipo kwenye mfumo wa end-to-end na haionekani kwenye huduma za Signal. Umefanya majaribio mengi sana ili kusajili nambari hii. Tafadhali jaribu tena baadaye. + + Umefanya majaribio mengi ya kusajili nambari hii. Tafadhali jaribu tena baada ya %1$s. Imeshindwa kuunganisha kwenye huduma. Tafadhali angalia muunganisho wako wa mtandao na ujaribu tena. Mfumo wa nambari usio wa kiwango Nambari uliyoingiza (%1$s) inaonekana kuwa isiyo ya mfumo wa nambari usio wa kiwango.\n\nUlimaanisha %2$s? Molly Android - Mfumo wa Nambari ya Simu + Ombi la kupiga simu + + SMS imeombwa + + Kodi ya uthibitisho imeombwa Sasa uko hatua %1$dkabla uwasilishe faili la rekebisho. Sasa uko hatua %1$d kabla uwasilishe faili la rekebisho. @@ -1792,6 +1830,16 @@ Piga Kodi ya uthibitisho Tuma upya Kodi + + Unasumbuka kujisajili? + + • Hakikisha namba yako inapata mtandao wa simu ili kupokea SMS au kupigiwa simu\n • Hakikisha kama unaweza kupokea simu kwenye nambari\n • Angalia kama umeingiza vyema nambari za simu. + + Kwa habari zaidi, tafadhali fuata maelekezo haya ya kutatua tatizo au Wasiliana na Msaada + + hatua hizi za urekebishaji + + Wasiliana na Msaada Je, washa Kufuli ya Usajili? @@ -1951,13 +1999,17 @@ Umekomboa beji - %1$s ame-react kwa stori yako + Ametoa hisia%1$s kwa hadithi yako - %1$s ame-react kwa stori yake + Ametoa hisia%1$s kwa hadithi zao Malipo Ujumbe uliopangwa kutumwa + + Historia ya jumbe zako imejumuishwa + + %1$s inamilikiwa na %2$s Sasisho ya Molly @@ -2030,7 +2082,7 @@ Ujumbe wa MMS umesimbwa kwa kipindi kisichopo - Zima arifa + Nyamazisha arifa Uingizaji unaendelea @@ -2088,12 +2140,14 @@ %1$s %2$s Mawasiliano Ametoa hisia %1$skwa: \"%2$s\". - Ametoa hisia%1$s kwa video yako. - Ametoa hisia%1$s kwa picha yako. - %1$s kaonyesha reaction kwa GIF yako. + Ametoa hisia %1$s kwa video yako. + Ametoa hisia %1$s kwa picha yako. + Ametoa hisia%1$s kwa GIF yako. Ametoa hisia %1$s kwa faili yako. Ametoa hisia %1$s kwa sauti yako. - Ametoa hisia ya %1$s kwenye media yako ya kutazama mara moja. + Ametoa hisia %1$s kwenye media yako ya kutazama mara moja. + + Ame-react %1$s kwa malipo yako. Ametoa hisia%1$s kwa kibandiko chako. Ujumbe huu umefutwa. @@ -2487,8 +2541,8 @@ Hitilafu ya Uwasilishaji - Ujumbe, kibandiko, majibu au ripoti ya kusoma haikuweza kuwasilishwa kwako kutoka kwa %1$s. Labda alijaribu kukutumia moja kwa moja, au kwenye kikundi. - Ujumbe, vibandiko, majibu su ripoti ya kusomwa haikuweza kuwasilishwa kwako kutoka kwa %1$s. + Ujumbe, kibandiko, hisia au ripoti ya kusomwa haikuweza kuwasilishwa kwako kutoka kwa %1$s. Labda walijaribu kukutumia moja kwa moja, au kwenye kikundi. + Ujumbe, kibandiko, hisia au ripoti ya kusomwa haikuweza kuwasilishwa kwako kutoka kwa %1$s. Jina la kwanza (linahitajika) @@ -2555,7 +2609,7 @@ Inasubiri - Umetumwa kwa + Imetumwa kwa Umetumwa kutoka kwa Umewasilishwa kwa Umesomwa na @@ -2635,10 +2689,10 @@ Tumia chaguo msingi Tumia kaida - Zima kwa saa 1 + Nyamazisha kwa saa 1 Nyamazisha kwa saa 8 - Zima kwa siku 1 - Zima kwa siku 7 + Nyamazisha kwa siku 1 + Nyamazisha kwa siku 7 Kila mara Mipangilio chaguo msingi @@ -2675,7 +2729,7 @@ Tumia picha za orodha ya waasiliani Onyesha picha za waasiliani zilizo kwenye orodha yako ya waasiliani ikiwa zipo - Magumzo yaliyonyamazishwa yaendelea kuhifadhiwa + Weka Magumzo Yaliyonyamazishwa Kwenye Hifadhi Magumzo yaliyonyamazishwa yamehifadhiwa na yatabaki yamehifadhiwa pale arafa mpya itakapoingia. Onyesha uhakiki wa viungo @@ -3131,7 +3185,7 @@ Washa - Zima arifa + Nyamazisha arifa Mipangilio ya kikundi @@ -3295,6 +3349,8 @@ Ingiza Nenosiri lako Ingiza Nenosiri ulilobuni kwa hii akaunti. Hii ni tofauti na nambari za kuthibitisha kupitia Ujumbe mfupi. + + Ingiza PIN uliyounda ya akaunti yako. Ingiza Nenosiri la herufi na nambari Ingiza Nenosiri lenye nambari Nenosiri sio sahihi. Jaribu tena. @@ -3398,7 +3454,10 @@ Nakala hifadhi yako ina faili kubwa ambalo haliwezi kuhifadhiwa. Tafadhali lifute na utengeneze nakala hifadhi mpya. Gonga ili udhibiti nakala hifadhi. Sio namba sahihi? + Nipigie (%1$02d:%2$02d) + + Tuma tena kodi (%1$02d:%2$02d) Wasiliana na Msaada wa Signal Usajili wa Signal: Msimbo wa Hakiki wa Android Kodi isiyo sahihi @@ -3406,6 +3465,18 @@ Isiyojulikana Ona nambari yangu ya simu Nitafute kwa nambari ya simu + + Nambari ya simu + + Chagua nani anayeweza kuona nambari yako ya simu na kuwasiliana nawe kwenye Molly kwa kuitumia. + + Anayeweza kuona nambari yangu + + Hakuna atakayeona nambari yako ya simu kwenye Molly + + Yule anayeweza kunipata kupitia nambari yangu + + Nambari yako ya simu itaonekana na watu na vikundi unavyovitumia ujumbe. Watu ambao wamehifadhi nambari yako ya simu pia wataiona kwenye Molly. Kila mtu Waasiliani wangu Hakuna mtu @@ -3563,7 +3634,7 @@ Zuia - Fungua + Ondoa Kizuizi Ongeza kwa wawasiliani Haijapatikana programu itakayoweza kufungua wawasiliani. @@ -3615,8 +3686,8 @@ %1$s/%2$s - \"%1$s\" amezuiwa. - Ameshindwa kuzuia \"%1$s\" + \"%1$s\" amezuiliwa. + Imeshindikana kuzuia \"%1$s\" \"%1$s\" ameondolewa kizuizi. @@ -3784,12 +3855,12 @@ Zima Mkoba Salio lako - Inashauriwa uhamishie fedha zako kwa anwani nyingine ya mkoba kabla ya kuzima malipo. Ukichagua kutohamisha pesa zako sasa, zitabaki kwenye mkoba wako uliounganishwa na Molly ikiwa utawezasha malipo tena. + Inashauriwa uhamishie fedha zako kwa anwani nyingine ya mkoba kabla ya kuzima malipo. Ukichagua kutohamisha pesa zako sasa, zitabakia kwenye mkoba wako uliounganishwa na Molly ikiwa utawezesha malipo tena. Hamisha salio lililobakia Zima bila kuhamisha Zima Ungependa kuzima bila kuhamisha? - Salio lako litabaki kwenye mkoba wako uliounganishwa kwenye Molly ukichagua kuwezesha tena malipo. + Salio lako litabakia kwenye mkoba wako uliounganishwa na Molly ukichagua kuwezesha malipo tena. Hitilafu imetokea wakati wa kuzima mkoba. @@ -4003,12 +4074,12 @@ Tengeneza wasifu - Zuiwa + Zuiliwa Waasiliani %1$d Kutuma ujumbe Jumbe zinazotoweka Usalama wa programu - Zuia viwambo vya skrini katika orodha ya rekodi na ndani ya programu + Zuia screenshots katika orodha ya rekodi na ndani ya programu Ujumbe na simu za Signal, elekeza simu kila wakati, na kutumia huduma ya kuficha ujumbe Kipima muda chaguo-msingi cha gumzo mpya Weka kipima muda cha ujumbe unaotoweka kwa gumzo zote mpya zilizoanzishwa na wewe. @@ -4106,7 +4177,7 @@ Sio sasa - Badilisha majibu yakufae + Weka hisia zitakazokufaa Gusa ili ubadilishe emoji Seti upya tena Hifadhi @@ -4165,7 +4236,7 @@ Simu - Zima + Nyamazisha Imenyamazishwa @@ -4177,8 +4248,8 @@ Tazama nambari ya usalama Zuia Zuia kikundi - Fungua - Acha kuzuia kikundi + Ondoa kizuizi + Ondoa Kizuizi kwa kikundi Ongeza kwenye kikundi Tazama zote Ongeza wanachama @@ -4188,7 +4259,7 @@ Mwongeze awe mwasiliani Washa Mazungumzo yamenyamazishwa hadi %1$s - Mazungumzo yamenyamazishwa daima + Mazungumzo yamenyamazishwa milele Umenakili nambari ya simu kwenye ubao wa kunakili. Nambari ya simu Pata beji za wasifu wako kwa kuiunga mkono Signal. Bonyeza beji ili kujifunza zaidi. @@ -4204,7 +4275,7 @@ Nani anaweza kutuma ujumbe? - Zima arifa + Nyamazisha arifa Haijanyamazishwa Kutajwa Arifu kila wakati @@ -4453,7 +4524,7 @@ Mchango wa Kila Mwezi Umeghairishwa Beji yako ya Boost imekwisha muda na haionekani tena kwenye wasifu wako. - Unaweza kuamilisha tena beji yako ya Boost kwa siku zingine 30 kwa mchango wa mara moja. + Unaweza kuwezesha tena beji yako ya Boost kwa siku zingine 30 kwa mchango wa mara moja. Unaweza kuendelea kutumia Signal lakini ili kuunga mkono teknolojia iliyotengenezwa kwa ajili yako, zingatia kuwa mfadhili kwa kutoa mchango wa kila mwezi. Kuwa Mfadhili @@ -4465,7 +4536,7 @@ Mchango wako wa kujirudia kila mwezi umeghairishwa kwa sababu tumeshindwa kuchakata malipo yako. Beji yako haionekani tena kwenye wasifu wako. Mchango wako wa kujirudia kila mwezi umeghairishwa. %1$s Beji yako ya %2$s haionekani tena kwenye wasifu wako. - Unaweza kuendelea kutumia Signal lakini ili kuunga mkono programu na kuanzisha upya beji yako, itengeneze upya sasa. + Unaweza kuendelea kutumia Signal lakini ili kuunga mkono programu na kuanzisha upya beji yako, ianzishwe upya sasa. Tengeneza upya usajili wako Nenda Google Pay @@ -4508,7 +4579,7 @@ Mchango wako umeshindwa kutumwa kwa sababu ya hitilafu za kimtandao. Angalia muunganisho wa mtandao wako kisha ujaribu tena. - Mchango kwa %1$s + Mchango kwa niaba ya %1$s %1$s ameichangia Signal kwa niaba yako @@ -4853,13 +4924,13 @@ Huwezi kujibu stori hii kwa sababu wewe si mwanachama tena wa kikundi hiki. - Ame-react kwa stori + Ametoa hisia kwa hadithi yako Mitazamo Majibu - React kwa stori hii + Toa hisia kwa hadithi hii Kujibu %1$s kwa faragha @@ -4905,11 +4976,11 @@ Chagua watakaoona stori yako. Mabadiliko hayataathiri stori ulizotuma tayari. - Majibu & reactions + Majibu na hisia - Ruhusu majibu &amo; reactions + Ruhusu majibu na hisia - Waruhusu watu wanaoweza kuona stori yako ku-react na kujibu + Waruhusu watu wanaoweza kuona hadithi yako watoe hisia na majibu Signal Connections @@ -5057,11 +5128,11 @@ - Uli-react kwa story ya %1$s + Ulitoa hisia kwa hadithi ya %1$s - Ali-react kwa stori yako + Ametoa hisia kwa hadithi yako - Ali-react kwa stori + Ametoa hisia kwa hadithi @@ -5601,5 +5672,15 @@ Futa jina la mtumiaji + + + h + + m + + Weka + + Muda wa chini zaidi kabla skrini kujifunga ni dakika 1. + diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 56a4f14af4..7ddd4be271 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -14,6 +14,7 @@ + ஆம் இல்லை @@ -96,13 +97,13 @@ செய்திகளைச் சரிபார்க்கிறது… - தடைசெய்த பயனர்கள் - தடுக்கப்பட்ட பயனரைச் சேர்க்கவும் - தடுக்கப்பட்ட பயனர்கள் உங்களை அழைக்கவோ அல்லது செய்திகளை அனுப்பவோ முடியாது. - தடைசெய்த பயனர்கள் எவருமில்லை - பயனர் அணுகலைத் தடு + தடைசெய்யப்பட்ட பயனர்கள் + தடுக்கப்பட்ட பயனரைச் சேர் + தடுக்கப்பட்ட பயனர்கள் உங்களை அழைக்கவோ அல்லது உங்களுக்கு மெசேஜ்களை அனுப்பவோ முடியாது. + தடைசெய்யப்பட்ட பயனர்கள் எவருமில்லை + பயனரை தடை செய்ய வேண்டுமா? \"%1$s\" உங்களை அழைக்கவோ அல்லது செய்திகளை அனுப்பவோ முடியாது. - தடு + தடைசெய் @@ -138,8 +139,8 @@ தொடரவும் - தடுத்து %1$sஐ விடவா? - %1$sஐ தடுத்து விடவா? + %1$s ஐத் தடுத்து விட்டு வெளியேற வேண்டுமா? + %1$sஐத் தடுக்க வேண்டுமா? இந்த குழுவிலிருந்து நீங்கள் இனி செய்திகளையோ புதுப்பிப்புகளையோ பெறமாட்டீர்கள், மேலும் உறுப்பினர்கள் உங்களை மீண்டும் இந்தக் குழுவில் சேர்க்க முடியாது. குழு உறுப்பினர்கள் உங்களை மீண்டும் இந்த குழுவில் சேர்க்க முடியாது. குழு உறுப்பினர்கள் உங்களை இந்த குழுவிற்கு மீண்டும் சேர்க்க முடியும். @@ -147,16 +148,16 @@ நீங்கள் ஒருவருக்கொருவர் செய்தி அனுப்பவும் அழைக்கவும் முடியும், மேலும் உங்கள் பெயரும் புகைப்படமும் அவர்களுடன் பகிரப்படும். உங்களால் ஒருவருக்கொருவர் செய்தி அனுப்ப முடியும். - தடுக்கப்பட்ட நபர்கள் உங்களை அழைக்கவோ அல்லது செய்திகளை அனுப்பவோ முடியாது. - தடை செய்யப்பட்ட நபர்கள் உங்களுக்கு செய்திகளை அனுப்ப முடியாது. + தடுக்கப்பட்ட நபர்கள் உங்களை அழைக்கவோ அல்லது மெசேஜ்களை அனுப்பவோ முடியாது. + தடைசெய்யப்பட்ட நபர்கள் உங்களுக்கு மெசேஜ்களை அனுப்ப முடியாது. - சிக்னல் புதுப்பிப்புகள் மற்றும் செய்திகள் பெறுவதைத் தடை செய்யவும். + Signal அறிவிப்புகள் மற்றும் செய்திகள் பெறுவதைத் தடைசெய்யலாம். சிக்னல் புதுப்பிப்புகள் மற்றும் செய்திகளைப் பெறுவதைத் தொடரவும். - %1$sஐ தடைநீக்கவா? - தடு - தடுத்து வெளியேறு - ஸ்பேம் என முறையிட மற்றும் தொகுதி + %1$sக்குத் தடையை நீக்க வேண்டுமா? + தடைசெய் + தடுத்துவிட்டு வெளியேறுக + ஸ்பேமைப் புகாரளித்து தடுக்கவும் இன்று @@ -318,7 +319,7 @@ Signal செய்தி %1$s நாம் Mollyக்கு மாறுவோம் தயவு செய்து ஒரு தொடர்பை தேர்ந்தெடு - தடுப்புநீக்க + தடைநீக்கு நீங்கள் அனுப்பும் இணைப்பு செய்தி அளவு வரம்பை மீறுகிறது . ஒலி பதிவுச் செய்ய இயலவில்லை! நீங்கள் இனி உறுப்பினராக இல்லாததால் இந்த குழுவிற்கு செய்திகளை அனுப்ப முடியாது. @@ -366,7 +367,7 @@ மீடியா அனுப்புவதில் பிழை - என புகாரளிக்கப்பட்டது ஸ்பேம் மற்றும் தடுக்கப்பட்டது. + ஸ்பேம் எனப் புகாரளிக்கப்பட்டு தடுக்கப்பட்டது. SMS மெசேஜிங் தற்போது முடக்கப்பட்டுள்ளது. உங்கள் மொபைலில் உள்ள மற்றொரு செயலிக்கு உங்கள் மெசேஜ்களை நீங்கள் ஏற்றுமதி செய்யலாம். @@ -451,11 +452,11 @@ %1$s குழு இணைப்பு வாயிலாக இந்த குழுவில் சேரவோ அல்லது சேர கோரிக்கை விடுக்கவோ முடியாது. அவர்கள் இன்னும் கைமுறையாக குழுவில் சேர்க்கப்படலாம். - கோரிக்கையைத் தடை செய் + கோரிக்கையைத் தடைசெய் ரத்து - தடுக்கப்பட்ட + தடைசெய்யப்பட்டார் ஃபில்டரை அழி @@ -488,35 +489,35 @@ படிக்காதவை - ஒட்டு - பின் + பின் + பின்கள் - விலக்கு - அவிழ் + பின்னை அகற்று + பின்களை அகற்று - முடக்கு - முடக்கு + ஒலியடக்கு + ஒலியடக்கு - ஒலிதடுப்பை நீக்கு - ஒலிதடுப்பை நீக்கு + ஒலி இயக்கு + ஒலிகளை இயக்கு - தேர்வு + தேர்ந்தெடு காப்பகப்படுத்து காப்பகப்படுத்து - காப்பகமகற்று - காப்பகம் அகற்று + காப்பகத்தில் இருந்து எடு + காப்பகத்தில் இருந்து எடு நீக்கு நீக்கு - அனைத்தையும் தேர்வுசெய் + அனைத்தையும் தேர்ந்தெடு %1$d தேர்ந்தெடுக்கப்பட்டது %1$d தேர்ந்தெடுக்கப்பட்டது @@ -542,6 +543,15 @@ +%1$d + + உங்கள் டிவைஸ்களை மீண்டும் இணைக்கவும் + + உங்கள் டிவைஸ் பதிவு செய்யப்படாதபோது நீங்கள் சேர்த்த டிவைஸ்கள் இணைக்கப்படவில்லை. எந்த டிவைஸ்களையும் மீண்டும் இணைக்க அமைப்புகளுக்குச் செல்லவும். + + அமைப்புகளைத் திற + + பிறகு + தேர்ந்தெடு உறுப்பினர்கள் @@ -935,7 +945,7 @@ என் பெயர் குறிப்புகளை எனக்கு அறிவி - ஒலியடக்கிய உரையாடல்களில் மற்றவர்களால் நீங்கள் குறிப்பிடப்படும்போது அறிவிப்புகள் பெறலாமா? + ஒலியடக்கிய சாட்களில் மற்றவர்களால் நீங்கள் குறிப்பிடப்படும்போது அறிவிப்புகள் பெறலாமா? எப்போதும் எனக்கு அறிவிக்கவும் எனக்கு அறிவிக்க வேண்டாம் @@ -953,6 +963,16 @@ பயனர்பெயர் உருவாக்கப்பட்டது பயனர்பெயர் நகலெடுக்கப்பட்டது + + பயனர்பெயரை நீக்க முடியவில்லை. பின்னர் மீண்டும் முயலவும். + + பயனர் பெயர் நீக்கப்பட்டது + + + + உங்கள் பயனர்பெயரில் ஏதோ தவறாகிவிட்டது, அது இனி உங்கள் கணக்கில் ஒதுக்கப்படாது. நீங்கள் முயற்சி செய்து மீண்டும் அமைக்கலாம் அல்லது புதியதைத் தேர்ந்தெடுக்கலாம். + + இப்பொழுதே சரிபார் @@ -1156,8 +1176,8 @@ புதிய குழு நண்பர்களை அழை எஸ்எம்எஸ் பயன்படுத்தவும் - தோற்றம் - கூட்டு புகைப்படம் + சாட் கலர்ஸ் + சுயவிவரப் புகைப்படத்தைச் சேர்க்கவும் பதிலகள் @@ -1468,14 +1488,14 @@ ஒப்புக்கொள் தொடர்ந்து செல்  நீக்கு - தடு + தடைசெய் தடைநீக்கு %1$s உங்களுக்கு செய்தி அனுப்ப அனுமதித்து, உங்கள் பெயரையும் புகைப்படத்தையும் அவர்களுடன் பகிர்ந்து கொள்ள விரும்புகிறீர்களா? நீங்கள் ஏற்றுக்கொள்ளும் வரை அவர்களின் செய்தியை நீங்கள் பார்த்திருப்பதை அவர்கள் அறிய மாட்டார்கள். - %1$s உங்களுக்கு செய்தி அனுப்ப அனுமதித்து, உங்கள் பெயரையும் புகைப்படத்தையும் அவர்களுடன் பகிர்ந்து கொள்ள விரும்புகிறீர்களா? நீங்கள் அவரை தடை நீக்கம் செய்யும் வரை எந்த செய்திகளையும் பெற மாட்டீர்கள். + %1$s உங்களுக்கு மெசேஜ் அனுப்ப அனுமதித்து, உங்கள் பெயரையும் புகைப்படத்தையும் அவர்களுடன் பகிர்ந்து கொள்ள விரும்புகிறீர்களா? நீங்கள் அவரை தடை நீக்கம் செய்யும் வரை எந்த மெசேஜ்களையும் பெற மாட்டீர்கள். - உங்களுக்கு %1$s செய்தி அனுப்ப அனுமதிக்கிறீர்களா? நீங்கள் தடை நீக்கம் செய்யும் வரை நீங்கள் எந்த செய்தியையும் பெறமாட்டீர்கள். - %1$s -இடமிருந்து புதுப்பிப்புகள் மற்றும் செய்திகளைப் பெற வேண்டுமா? நீங்கள் தடை நீக்கம் செய்யும் வரை எந்த புதுப்பிப்புகளையும் பெறமாட்டீர்கள். + உங்களுக்கு %1$s மெசேஜ் அனுப்ப அனுமதிக்கிறீர்களா? நீங்கள் தடை நீக்கம் செய்யும் வரை நீங்கள் எந்த மெசேஜையும் பெறமாட்டீர்கள். + %1$s -இடமிருந்து அறிவிப்புகள் மற்றும் செய்திகளைப் பெற வேண்டுமா? நீங்கள் தடை நீக்கம் செய்யும் வரை எந்த அறிவிப்புகளையும் பெறமாட்டீர்கள். இந்த குழுவுடன் உங்கள் உரையாடலைத் தொடர, உங்கள் பெயரையும் புகைப்படத்தையும் அதன் உறுப்பினர்களுடன் பகிர்ந்து கொள்ளவா? \@ குறிப்புகள் மற்றும் நிர்வாகிகள் போன்ற புதிய அம்சங்களை செயல்படுத்த இந்த குழுவை மேம்படுத்தவும். இந்த குழுவில் தங்கள் பெயரையோ புகைப்படத்தையோ பகிர்ந்து கொள்ளாத உறுப்பினர்கள் சேர அழைக்கப்படுவார்கள். இந்த மரபு குழு மிகப் பெரியது, எனவே இதை இனி பயன்படுத்த முடியாது. அதிகபட்ச குழு அளவு: %1$d @@ -1483,7 +1503,7 @@ இந்த குழுவில் சேர்ந்து உங்கள் பெயர் மற்றும் புகைப்படத்தை அதன் உறுப்பினர்களுடன் பகிர்ந்து கொள்ள விரும்புகிறீர்களா? நீங்கள் ஏற்றுக்கொள்ளும் வரை அவர்களின் செய்திகளை நீங்கள் பார்த்திருப்பதை அவர்கள் அறிய மாட்டார்கள். இந்த குழுவில் சேர்ந்து உங்கள் பெயரையும் புகைப்படத்தையும் அதன் உறுப்பினர்களுடன் பகிர்ந்துகொள்கிறீர்களா? நீங்கள் ஏற்றுக்கொள்ளும் வரை அவர்களின் செய்திகளைப் பார்க்க மாட்டீர்கள். இந்த குழுவில் சேரவா? நீங்கள் ஏற்றுக்கொள்ளும் வரை அவர்களின் செய்திகளைப் பார்த்திருப்பதை அவர்கள் அறிய மாட்டார்கள். - இந்த குழுவைத் தடை நீக்கம் செய்து, உங்கள் பெயரையும் புகைப்படத்தையும் அதன் உறுப்பினர்களுடன் பகிர்ந்து கொள்ளலாமா? நீங்கள் தடை நீக்கம் செய்யும் வரை எந்த செய்திகளையும் பெற மாட்டீர்கள். + இந்த குழுவைத் தடை நீக்கம் செய்து, உங்கள் பெயரையும் புகைப்படத்தையும் அதன் உறுப்பினர்களுடன் பகிர்ந்து கொள்ளலாமா? நீங்கள் தடை நீக்கம் செய்யும் வரை எந்த மெசேஜ்களையும் பெற மாட்டீர்கள். காண்க உறுப்பினர்%1$s @@ -1584,9 +1604,20 @@ புதிய பின்னை உருவாக்கவும் + + SMS குறியீட்டை அனுப்பு + + Signal பதிவு - Androidக்கான மறுபதிவு PIN உடன் செய்ய உதவி தேவை + + உங்கள் பின் என்பது நீங்கள் உருவாக்கிய %1$d+ இலக்கக் குறியீடாகும், இது எண் அல்லது எண்ணெழுத்து ஆகும்.\n\nஉங்கள் பின்னை நினைவில் கொள்ள முடியாவிட்டால், புதிய ஒன்றை உருவாக்கலாம். + + உங்கள் பின்னை நினைவில் கொள்ள முடியாவிட்டால், புதிய ஒன்றை உருவாக்கலாம். + + உங்களது பின் யூகங்கள் தீர்ந்துவிட்டன, ஆனால் புதிய பின்னை உருவாக்குவதன் மூலம் உங்கள் Signal கணக்கைத் தொடர்ந்து அணுகலாம். + எச்சரிக்கை - நீங்கள் PIN-ஐ முடக்கினால், நீங்கள் கைமுறையாக காப்புப்பிரதி எடுத்து மீட்டெடுக்காவிட்டால் Signal-ஐ மீண்டும் பதிவுசெய்யும்போது எல்லா தரவையும் இழப்பீர்கள். PIN முடக்கப்பட்டிருக்கும் போது நீங்கள் பதிவு பூட்டை இயக்க முடியாது. + நீங்கள் PIN-ஐ முடக்கினால், நீங்கள் கைமுறையாக பேக்அப் செய்து மீட்டெடுக்காமல் Signalஐ மீண்டும் பதிவுசெய்யும்போது எல்லா தரவையும் இழப்பீர்கள். PIN முடக்கப்பட்டிருக்கும் போது நீங்கள் பதிவு பூட்டை இயக்க முடியாது. PIN-ஐ முடக்கு @@ -1616,8 +1647,8 @@ எனது ஸ்டோரி - தடு - தடுப்புநீக்க + தடைசெய் + தடைநீக்கு @@ -1711,9 +1742,9 @@ புகைப்பட கருவி - ஒலியடக்கை நீக்கு + ஒலி இயக்கு - முடக்கு + ஒலியடக்கு அழை @@ -1768,11 +1799,18 @@ உங்கள் நண்பர்களுடன் இணைவதற்கும் செய்திகளை அனுப்புவதற்கும் உதவ சிக்னலுக்கு தொடர்புகள் மற்றும் மீடியா அனுமதிகள் தேவை. உங்கள் தொடர்புகள் சிக்னலின் தனிப்பட்ட தொடர்பு கண்டறிதலைப் பயன்படுத்தி பதிவேற்றப்படுகின்றன, அதாவது அவை முடிவிலிருந்து-முடிவுவரை மறையாக்கப்பட்டவை மற்றும் சிக்னல் சேவைக்கு ஒருபோதும் தெரியப்படுவதில்லை. நண்பர்களுடன் நீங்கள் இணைவதற்கு உதவ சிக்னலுக்கு தொடர்புகளின் அனுமதி தேவை. உங்கள் தொடர்புகள் சிக்னலின் தனிப்பட்ட தொடர்பு கண்டறிதலைப் பயன்படுத்தி பதிவேற்றப்படுகின்றன, அதாவது அவை முடிவிலிருந்து-முடிவுவரை மறையாக்கப்பட்டவை மற்றும் சிக்னல் சேவைக்கு ஒருபோதும் தெரியப்படுவதில்லை. இந்த எண்ணை பதிவு செய்ய நீங்கள் பல முயற்சிகளை செய்துள்ளீர்கள். சிறிது நேரம் கழித்து மீண்டும் முயற்சிக்கவும். + + இந்த எண்ணை பதிவு செய்ய நீங்கள் பல முயற்சிகளை செய்துள்ளீர்கள். %1$s இல் மீண்டும் முயற்சிக்கவும். சேவையுடன் இணைக்க முடியவில்லை. பிணைய இணைப்பைச் சரிபார்த்து மீண்டும் முயற்சிக்கவும். நான்-ஸ்டாண்டர்ட் எண் வடிவம் நீங்கள் உள்ளிட்ட எண் (%1$s) நான்-ஸ்டாண்டர்ட் எண் வடிவத்தைக் கொண்டுள்ளது.\n\nநீங்கள் %2$s-ஐ நினைத்தீர்களா? சிக்னல் ஆண்ட்ராய்டு - தொலைபேசி எண் வடிவம் + அழைப்பு கோரப்பட்டது + + SMS கோரப்பட்டது + + சரிபார்ப்புக் குறியீடு கோரப்பட்டது பிழைத்திருத்த பதிவைச் சமர்ப்பிப்பதில் இருந்து இப்போது நீங்கள் %1$d விலகிவிட்டீர்கள். பிழைத்திருத்த பதிவைச் சமர்ப்பிப்பதில் இருந்து இப்போது நீங்கள் %1$d படிகள் தொலைவில் உள்ளீர்கள். @@ -1792,6 +1830,16 @@ அழை சரிபார்ப்புக் குறியீடு குறியீட்டை மீண்டும் அனுப்பு + + பதிவு செய்வதில் சிக்கல் உள்ளதா? + + • உங்கள் SMS அல்லது அழைப்பு பெற உங்கள் ஃபோனில் செல்லுலார் சிக்னல் உள்ளதா என்பதை உறுதிப்படுத்தவும் \n • எண்\n க்கு ஃபோன் அழைப்பைப் பெற முடியுமா என்பதை உறுதிப்படுத்தவும் • உங்கள் ஃபோன் எண்ணை சரியாக உள்ளிட்டுள்ளீர்களா எனச் சரிபார்க்கவும். + + மேலும் தகவலுக்கு, இந்த சரிசெய்தல் படிகளைப் பின்பற்றவும் அல்லது ஆதரவைத் தொடர்பு கொள்ளவும் + + இந்த சரிசெய்தல் படிகள் + + தொடர்பு ஆதரவு பதிவு பூட்டை இயக்கவா? @@ -1958,6 +2006,10 @@ பேமெண்ட் திட்டமிடப்பட்ட செய்தி + + உங்கள் மெசேஜ் வரலாறு இணைக்கப்பட்டது + + %1$s %2$s க்கு சொந்தமானது Molly புதுப்பிப்பு @@ -2030,7 +2082,7 @@ இல்லாத அமர்வுக்கு மறையாக்கப்பட்ட MMS செய்தி - அறிவிப்பை ஒலியடக்கு + அறிவிப்புகளை ஒலியடக்கு இறக்குமதி முன்னேறுகிறது @@ -2087,14 +2139,16 @@ பாதுகாப்பற்ற SMS %1$s %2$s தொடர்பு - %1$s என வினைபுரிந்தார்: \"%2$s\" க்கு. - உங்கள் காணொளிக்கு %1$s என வினைபுரிந்தார். - உங்கள் படத்திற்கு %1$s என வினைபுரிந்தார். - உங்கள் ஜிஃப்-க்கு %1$s என்று பதிலளித்துள்ளார். - உங்கள் கோப்பிற்கு %1$s என வினைபுரிந்தார். - உங்கள் கேட்பொலிக்கு %1$s என வினைபுரிந்தார். - %1$sஉங்கள் பார்வைக்கு ஒரு முறை ஊடகங்களுக்கு அவர் பதில்செயல் காட்டியுள்ளீர்கள் - உங்கள் ஒட்டிக்கு %1$s என வினைபுரிந்தார். + %1$s என எதிர்வினையாற்றினார்: \"%2$s\" மெசேஜில். + உங்கள் வீடியோவுக்கு %1$s என்று எதிர்வினையாற்றினார். + உங்கள் படத்திற்கு %1$s என்று எதிர்வினையாற்றினார். + உங்கள் GIFக்கு %1$s என்று எதிர்வினையாற்றினார். + உங்கள் கோப்பிற்கு %1$s என்று எதிர்வினையாற்றினார். + உங்கள் ஆடியோவுக்கு %1$s என்று எதிர்வினையாற்றினார். + உங்கள் ஒரு முறை-பார்வைக்கு ஊடகங்களுக்கு %1$s என்று எதிர்வினையாற்றினார். + + உங்கள் பேமெண்ட்டிற்கு %1$s என்று எதிர்வினையாற்றினார். + உங்கள் ஸ்டிக்கருக்கு %1$s என்று எதிர்வினையாற்றினார். இந்த மெசேஜ் நீக்கப்பட்டது. இணைந்த சிக்னல் Signal அறிவிப்புகளை முடக்குவதா? அமைப்புகள் மற்றும் அறிவிப்புகளுக்குச் சென்று அவற்றை Signal settings சிக்னலில் மீண்டும் இயக்கலாம். @@ -2298,7 +2352,7 @@ அழைப்பு அறிவிப்புகளை இயக்கு தொடர்பைப் புதுப்பிக்கவும் - கோரிக்கையைத் தடை செய் + கோரிக்கையைத் தடைசெய் இல்லை குழுக்கள்பொதுவாக. கோரிக்கைகளை கவனமாக மதிப்பாய்வு செய்யவும். இல்லை இதில் தொடர்புகள் குழு. கோரிக்கைகளை கவனமாக மதிப்பாய்வு செய்யவும். காண்க @@ -2487,8 +2541,8 @@ டெலிவரி பிரச்சினை - ஒரு செய்தி, ஓட்டி, எதிர்வினை, அல்லது ரசீதைப் படியுங்கள் உங்களிடமிருந்து வழங்க முடியவில்லை %1$s. அவர்கள் அதை உங்களுக்கு நேரடியாக அனுப்ப முயற்சித்திருக்கலாம், அல்லது ஒருகுழு. - ஒரு செய்தி, ஓட்டி, எதிர்வினை, அல்லது ரசீதைப் படியுங்கள் உங்களிடமிருந்து வழங்க முடியவில்லை %1$s. + ஒரு செய்தி, ஸ்டிக்கர், எதிர்வினை அல்லது வாசிப்பு ரசீதை %1$s -இலிருந்து உங்களுக்கு வழங்க முடியவில்லை. அவர்கள் அதை உங்களுக்கு நேரடியாகவோ அல்லது குழுவாகவோ அனுப்ப முயன்றிருக்கலாம். + ஒரு செய்தி, ஸ்டிக்கர், எதிர்வினை அல்லது வாசிப்பு ரசீதை %1$s -இலிருந்து உங்களுக்கு வழங்க முடியவில்லை. முதல் பெயர் (தேவை) @@ -2555,7 +2609,7 @@ நிலுவையில் உள்ளது - அனுப்பப்பட்டது + இவருக்கு அனுப்பப்பட்டது அனுப்புனர் வழங்கப்பட்டது படித்தவர்கள் @@ -2636,7 +2690,7 @@ விருப்பம் பயன்படுத்தவும் 1 மணி நேரம் ஒலியடக்கு - முடக்கு 8 மணி நேரம் + 8 மணி நேரம் ஒலியடக்கு 1 நாள் ஒலியடக்கு 7 நாட்கள் ஒலியடக்கு எப்போதும் @@ -2675,9 +2729,9 @@ முகவரி புத்தக புகைப்படங்களைப் பயன்படுத்தவும் உங்கள் முகவரி புத்தகத்திலிருந்து தொடர்பு புகைப்படங்கள் இருந்தால் காண்பி - முடக்கப்பட்ட அரட்டைகளைக் காப்பகப்படுத்தவும் + முடக்கப்பட்ட சாட்களைக் காப்பகப்படுத்துக - புதிய செய்தி வரும்போது காப்பகப்படுத்தப்பட்ட முடக்கப்பட்ட அரட்டைகள் காப்பகப்படுத்தப்பட்டதாகவே இருக்கும். + புதிய மெசேஜ் வரும்போது காப்பகப்படுத்தப்பட்ட முடக்கப்பட்ட சாட்கள் காப்பகப்படுத்தப்பட்டதாகவே இருக்கும். இணைப்பு மாதிரிக்காட்சிகளை உருவாக்கவும் நீங்கள் அனுப்பும் செய்திகளுக்கு வலைத்தளங்களிலிருந்து இணைப்பு முன்னோட்டங்களை நேரடியாக மீட்டெடுக்கவும். கடவுச்சொல்லை மாற்று @@ -2971,7 +3025,7 @@ கட்டணம் அனுப்பப்பட்டது கட்டணம் அனுப்பப்பட்டது கட்டணம் முடிந்தது %1$s - தடு எண் + எண்ணைத் தடைசெய் இடமாற்றம் @@ -3056,7 +3110,7 @@ புதிய செய்தி இவருக்கு… - பயனரை தடை செய் + பயனரை தடைசெய் குழுவில் சேர் @@ -3128,10 +3182,10 @@ - ஒலியடக்கை நீக்கு + ஒலி இயக்கு - அறிவிப்பை ஒலியடக்கு + அறிவிப்புகளை ஒலியடக்கு குழு அமைப்புகள் @@ -3295,6 +3349,8 @@ உங்கள் பின்னை உள்ளிடவும் உங்கள் கணக்கிற்கு நீங்கள் உருவாக்கிய பின்னை உள்ளிடவும். இது உங்கள் SMS சரிபார்ப்புக் குறியீட்டிலிருந்து வேறுபட்டது. + + உங்கள் கணக்கிற்கு நீங்கள் உருவாக்கிய பின்னை உள்ளிடவும். எண்ணெழுத்து பின்னை உள்ளிடவும் எண் பின்னை உள்ளிடவும் தவறான பின். மீண்டும் முயற்சி செய்க. @@ -3398,7 +3454,10 @@ உங்கள் பேக்அப்பில் நகலெடுக்க முடியாத மிகப் பெரிய கோப்பு ஒன்று உள்ளது. அதை நீக்கிவிட்டு ஒரு புதிய பேக்அப்பை உருவாக்கவும். மறுபிரதிகளை நிர்வகிக்க தட்டவும் தவறான எண்? + என்னை அழைக்கவும் (%1$02d:%2$02d) + + குறியீட்டை மீண்டும் அனுப்புக (%1$02d:%2$02d) Signal ஆதரவைத் தொடர்பு கொள்ளுங்கள் Signal பதிவு - அண்ட்ராய்டு க்கான சரிபார்ப்புக் குறியீடு தவறான குறியீடு @@ -3406,6 +3465,18 @@ முன் தெரிந்திராத எனது தொலைபேசி எண்ணைக் காண்க தொலைபேசி எண் மூலம் என்னைக் கண்டுபிடிக்க + + ஃபோன் எண் + + உங்கள் ஃபோன் எண்ணை யார் பார்க்கலாம் மற்றும் யார் உங்களை Molly இல் தொடர்பு கொள்ளலாம் என்பதைத் தேர்வுசெய்யவும். + + எனது எண்ணை யார் பார்க்கலாம் + + Molly இல் உங்கள் ஃபோன் எண்ணை யாரும் பார்க்க மாட்டார்கள் + + எண் மூலம் என்னை யார் கண்டுபிடிக்க முடியும் + + நீங்கள் மெசேஜ் அனுப்பும் நபர்களுக்கும் குழுக்களுக்கும் உங்கள் ஃபோன் எண் தெரியும். ஃபோன் தொடர்புகளில் உங்கள் எண்ணை வைத்திருப்பவர்கள் அதை Molly இலும் பார்ப்பார்கள். எல்லோரும் எனது தொடர்புகள் ஒருவருமில்லை @@ -3562,7 +3633,7 @@ - தடு + தடைசெய் தடைநீக்கு தொடர்புகளுடன் சேர்க்க @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\"தடுக்கப்பட்டார் - \"%1$s\" தடுப்பதில் தோல்வி - \"%1$s\" தடைநீக்கப்பட்டார் + \"%1$s\" தடைசெய்யப்பட்டார். + \"%1$s\" ஐத் தடைசெய்ய முடியவில்லை + \"%1$s\" தடைநீக்கப்பட்டார். மதிப்பாய்வு உறுப்பினர்கள் @@ -3644,7 +3715,7 @@ உங்கள் தொடர்பு குழுவிலிருந்து அகற்று தொடர்பைப் புதுப்பிக்கவும் - தடு + தடைசெய் நீக்கு சமீபத்தில் அவர்கள் தங்கள் சுயவிவரப் பெயரை %1$s இலிருந்து %2$s ஆக மாற்றினர் @@ -3667,7 +3738,7 @@ பலவீனமான வை-ஃபை. செல்லுலார்க்கு மாறியது. - உங்கள் நீக்குகிறது கணக்கு விருப்பம்: + உங்கள் கணக்கை நீக்குவதால்: உள்ளிடவும் உங்கள் தொலைபேசி எண் கணக்கை நீக்கு உங்கள் கணக்குத் தகவல் மற்றும் புரொஃபைல் ஃபோட்டோவை நீக்கவும் @@ -3677,7 +3748,7 @@ இல்லை எண் குறிப்பிடப்பட்டுள்ளது தி தொலைபேசி நீங்கள் உள்ளிட்ட எண் உங்களுடன் பொருந்தவில்லை கணக்கு. உங்கள் கணக்கை நிச்சயமாக நீக்க வேண்டுமா? - இது உங்கள் Signal கணக்கை நீக்கி செயலியை மீட்டமைக்கும். செயல்பாடு நிறைவடைந்ததும் செயலி மூடப்படும். + இது உங்களது Signal கணக்கை நீக்கி செயலியை மீட்டமைக்கும். செயல்பாடு நிறைவடைந்ததும் செயலி மூடப்படும். உள்ளே சேமிக்கப்பட்டிருக்கும் தரவை நீக்க முடியவில்லை. சிஸ்டம் அப்ளிகேஷன் செட்டிங்ஸில் அதை நீங்கள் நேரடியாக அழிக்கலாம். பயன்பாட்டு அமைப்புகளை தொடங்கு @@ -3784,12 +3855,12 @@ செயலிழக்க Wallet உங்கள் சமநிலை - நீங்கள் பரிந்துரைக்கப்படுகிறது பரிமாற்றம் உங்கள் நிதி மற்றொருவருக்கு பணப்பை முகவரி செயலிழக்க முன் கொடுப்பனவுகள். வேண்டாம் என்று நீங்கள் தேர்வுசெய்தால்பரிமாற்றம் உங்கள் நிதி இப்போது, ​​அவை உங்களிடம் இருக்கும் பணப்பை இணைக்கப்பட்டுள்ளது க்கு Molly நீங்கள் மீண்டும் செயல்படுத்தினால் கொடுப்பனவுகள். + பணம் செலுத்துவதை செயலிழக்கச் செய்வதற்கு முன், உங்கள் நிதியை வேறொரு வாலட் முகவரிக்கு மாற்றுவது பரிந்துரைக்கப்படுகிறது. உங்கள் நிதியை இப்போது மாற்ற வேண்டாம் என நீங்கள் தேர்வுசெய்தால், நீங்கள் பேமெண்ட்டுகளை மீண்டும் செயல்படுத்தினால், அவை சிக்னலுடன் இணைக்கப்பட்ட உங்கள் வாலட்டில் இருக்கும். இடமாற்றம் மீதமுள்ள சமநிலை செயலிழக்க இல்லாமல் இடமாற்றம் செயலிழக்க செயலிழக்க இல்லாமல் இடமாற்றம்? - கொடுப்பனவுகளை மீண்டும் செயல்படுத்த நீங்கள் தேர்வுசெய்தால், Molly லுடன் இணைக்கப்பட்ட உங்கள் பணப்பையில் உங்கள் இருப்பு இருக்கும். + கட்டணங்களை மீண்டும் செயல்படுத்த நீங்கள் தேர்வுசெய்தால், Molly உடன் இணைக்கப்பட்ட உங்கள் வாலட்டில் உங்கள் இருப்பு இருக்கும். செயலிழக்க பிழை பணப்பை. @@ -4003,12 +4074,12 @@ சுயவிவரத்தை உருவாக்கவும் - தடுக்கப்பட்ட + தடைசெய்யப்பட்டார் %1$d contacts செய்தி அனுப்புதல் காணாமல் போகும் செய்திகள் பயன்பாட்டு பாதுகாப்பு - அண்மை பட்டியல் மற்றும் மென்பொருளுக்கு உள்ளே திரையை படம் எடுப்பதை தடுக்கவும் + சமீபத்தியவை பட்டியல் மற்றும் செயலிக்கு உள்ளே ஸ்கிரீன்ஷாட் எடுப்பதைத் தடுக்கவும் Signal செய்திகள் மற்றும் அழைப்புகள், எப்போதும் ரிலே அழைப்புகள் மற்றும் சீல் அனுப்பியவர் இயல்புநிலை நேரக்குறிப்பான் புதிய அரட்டைகளுக்கு நீங்கள் தொடங்கும் அனைத்து புதிய அரட்டைகளுக்கும் இயல்புநிலை காணாமல் போகும் செய்திகள் நேரக்குறிப்பானை அமைக்கவும். @@ -4106,7 +4177,7 @@ இப்போது இல்லை - தனிப்பயனாக்கலாம் எதிர்வினைகள் + எதிர்வினைகளை தனிப்பயனாக்கவும் தட்டவும் ஒரு மாற்ற ஈமோஜி மீட்டமைக்க சேமி @@ -4165,9 +4236,9 @@ அழைப்பு - முடக்கு + ஒலியடக்கு - முடக்கியது + ஒலியடக்கப்பட்டது தேடு காணாமல் போகும் செய்திகள் @@ -4175,10 +4246,10 @@ தொடர்பு விவரங்கள் பாதுகாப்பு எண்ணைக் காண்பி - தடு - குழுவைத் தடு + தடைசெய் + குழுவைத் தடைசெய் தடைநீக்கு - குழுவைத் தடைநீக்கு + குழுவுக்குத் தடைநீக்கு ஒரு குழுவில் சேர்க்கவும் அனைத்தையும் பார் உறுப்பினர்களைச் சேர்க்கவும் @@ -4186,9 +4257,9 @@ கோரிக்கைகள் & அழைக்கிறது குழு இணைப்பு கூட்டு ஒரு தொடர்பாக - ஒலியடக்கை நீக்கு - வரை உரையாடல் முடக்கப்பட்டது %1$s - உரையாடல் எப்போதும் முடக்கப்பட்டது + ஒலி இயக்கு + %1$s வரை உரையாடல் ஒலியடக்கப்பட்டது + உரையாடல் நிரந்தரமாக ஒலியடக்கப்பட்டது கிளிப்போர்டுக்கு தொலைபேசி எண் நகலெடுக்கப்பட்டது. தொலைபேசி எண் Signalஐ ஆதரிப்பதன் மூலம் உங்கள் சுயவிவரத்திற்கான பேட்ஜ்களைப் பெறுங்கள். மேலும் அறிய ஒரு பேட்ஜில் தட்டவும். @@ -4204,8 +4275,8 @@ யாரால் முடியும் அனுப்பு செய்திகள்? - அறிவிப்பை ஒலியடக்கு - முடக்கப்படவில்லை + அறிவிப்புகளை ஒலியடக்கு + ஒலியடக்கப்படவில்லை பெயர் குறிப்பிடுவது எப்போதும் அறிவிக்கவும் அறிவிக்க வேண்டாம் @@ -4234,7 +4305,7 @@ அகற்று - தடு + தடைசெய் %1$sஐ அகற்ற வேண்டுமா? @@ -4242,7 +4313,7 @@ %1$s என்ற தொடர்பு அகற்றப்பட்டது - %1$s தடுக்கப்பட்டார் + %1$s தடைசெய்யப்பட்டார் %1$s ஐ அகற்ற முடியவில்லை @@ -4330,7 +4401,7 @@ ஸ்டோரியில் சேர்க்கவும் கூட்டு ஒரு செய்தி ஒரு பதிலைச் சேர்க்கவும் - அனுப்பவும் + இவருக்கு அனுப்பு ஒருமுறை பார்க்கும் செய்தி ஒன்று அல்லது அதற்கு மேற்பட்ட பொருட்கள் மிகப் பெரியதாக இருந்தன ஒன்று அல்லது அதற்கு மேற்பட்ட பொருட்கள் செல்லாதவை @@ -4508,7 +4579,7 @@ நெட்வொர்க் பிழையின் காரணமாக உங்கள் நன்கொடையை அனுப்ப முடியவில்லை. உங்கள் இணைப்பைச் சரிபார்த்து மீண்டும் முயலவும். - %1$s க்கு நன்கொடை + %1$s சார்பாக நன்கொடை உங்கள் சார்பாக Signalக்கு %1$s நன்கொடை அளித்துள்ளார் @@ -4905,9 +4976,9 @@ உங்கள் ஸ்டோரியை யார் பார்வையிடலாம் என்பதைத் தேர்வுசெய்யவும். நீங்கள் ஏற்கனவே அனுப்பிய ஸ்டோரீஸ்-ஐ மாற்றங்கள் பாதிக்காது. - பதில்கள் &ஆம்ப்; எதிர்வினைகள் + பதில்கள் & எதிர்வினைகள் - பதில்களை அனுமதிக்கவும் &ஆம்ப்; எதிர்வினைகள் + பதில்கள் & எதிர்வினைகளை அனுமதிக்கவும் உங்கள் ஸ்டோரியைப் பார்க்கக்கூடியவர்கள் எதிர்வினையாற்றி பதிலளிக்கட்டும் @@ -5057,7 +5128,7 @@ - %1$s-யின் ஸ்டோரிக்கு நீங்கள் எதிர்வினையாற்றி உள்ளீர்கள் + %1$s -யின் ஸ்டோரிக்கு நீங்கள் எதிர்வினையாற்றி உள்ளீர்கள் உங்கள் ஸ்டோரிக்கு எதிர்வினையாற்றினார் @@ -5087,7 +5158,7 @@ நன்கொடையை உறுதிப்படுத்துதல் - அனுப்பவும் + இவருக்கு அனுப்பு 1 இல் 1 செய்தியில் நன்கொடை பெறுபவருக்கு அறிவிக்கப்படும். உங்கள் சொந்த செய்தியை கீழே சேர்க்கவும். @@ -5601,5 +5672,15 @@ பயனர் பெயரை அழிக்கவும் + + + + + நி + + அமை + + திரைப் பூட்டு பயன்படுத்தப்படுவதற்கு குறைந்தபட்ச நேரம் 1 நிமிடம். + diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 72d4027d85..b0985ac234 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -14,6 +14,7 @@ + అవును కాదు @@ -96,13 +97,13 @@ సందేశాల కోసం తనిఖీ చేస్తోంది… - నిరోధించిన వినియోగదారులు - నిరోధించిన వినియోగదారుని జోడించు - నిరోధించిన వినియోగదారులు మీకు కాల్ చేయలేరు లేదా మీకు సందేశాలు పంపలేరు. - మీరు ఎవరిని బ్లాక్ చేయలేదు - వినియోగదారుని నిరోధించాలా? + బ్లాక్ చేయబడిన యూజర్లు + బ్లాక్ చేయబడిన యూజర్‌ను జోడించండి + బ్లాక్ చేయబడిన యూజర్లు మీకు కాల్ చేయలేరు లేదా మీకు సందేశాలు పంపలేరు. + బ్లాక్ చేయబడిన యూజర్లు లేరు + యూజర్‌ను బ్లాక్ చేసేదా? \"%1$s\" ఇక మీకు కాల్ ఇంకా మెసేజ్ చేయలేరు. - నిరోధించు + బ్లాక్ చేయండి @@ -138,8 +139,8 @@ కొనసాగు - అడ్డగించు మరియు వదిలివేయి 1%1$s - అడ్డగించు 1%1$s + %1$sను బ్లాక్ చేసి మరియు వదలివేసేదా? + %1$sను బ్లాక్ చేసేదా? ఇకపై మీరు ఈ సమూహము నుండి సందేశాలను లేదా నవీకరణాలను అందుకోరు, మరియు సమూహములో ఉన్న వారు మిమ్మల్ని తిరిగి అందులో చేర్చలేరు. సమూహములో ఉన్న వారు మిమ్మల్ని తిరిగి అందులో చేర్చలేరు. సమూహములో ఉన్న వారు మిమ్మల్ని తిరిగి అందులో చేర్చగలరు. @@ -147,16 +148,16 @@ మీరు ఒకరికొకరు సందేశం పంపగలరు మరియు మీ పేరు మరియు ఫోటో వారితో భాగస్వామ్యం చేయబడతారు. మీరు ఒకరినొకరు సందేశాలు పంపుకోగలుగుతారు. - అడ్డగింపబడిన వ్యక్తులు మీతో మాట్లాడడం లేదా మీకు సందేశములు పంపడం చేయలేరు. - బ్లాక్ చేయబడ్డ వ్యక్తులు మీకు సందేశాలను పంపలేరు. + బ్లాక్ చేయబడిన వ్యక్తులు మీకు కాల్ చేయలేరు లేదా మీకు సందేశాలు పంపలేరు. + బ్లాక్ చేయబడిన వ్యక్తులు మీకు సందేశాలను పంపలేరు. - Signal అప్‌డేట్‌లు మరియు న్యూస్ పొందడాన్ని బ్లాక్ చేయండి + Signal అప్‌డేట్స్ మరియు వార్తలు పొందడాన్ని బ్లాక్ చేయండి. Signal అప్‌డేట్‌లు మరియు న్యూస్ పొందడాన్ని పునరుద్ధరించండి. - అడ్డం తొలగించు 1%1$s - నిరోధించు - అడ్డగించు మరియు వదిలివేయి - స్పామ్ మరియు బ్లాక్‌ను నివేదించండి + %1$sను అన్‌బ్లాక్ చేసేదా? + బ్లాక్ చేయండి + బ్లాక్ చేసి మరియు వదిలేయండి + స్పామ్‌ను నివేదించండి మరియు బ్లాక్ చేయండి నేడు @@ -318,7 +319,7 @@ Signal మెసేజ్ మనం Molly %1$sకు మారుదాం దయచేసి ఒక పరిచయం ఎంచుకోండి - అనుమతించు + అన్‌బ్లాక్ చేయండి జోడింపు మీరు పంపే సందేశం రకం పరిమాణ పరిమితి మించిపోయింది. ఆడియో రికార్డ్ చేయడం సాధ్యపడలేదు! మీరు ఇకపై ఈ సమూహం సభ్యులు కానందున మీరు ఈ సమూహంకు సందేశాలను పంపలేరు. @@ -366,7 +367,7 @@ మీడియాను పంపడంలో లోపం చోటుచేసుకుంది - స్పామ్‌గా నివేదించబడింది మరియు నిరోధించబడింది. + స్పామ్‌గా నివేదించబడింది మరియు బ్లాక్ చేయబడింది. SMS సందేశం పంపడం ప్రస్తుతం నిలిపివేయబడింది. మీ ఫోన్‌లోని మరో యాప్‌కు మీ సందేశాలను మీరు ఎగుమతి చేయవచ్చు. @@ -447,15 +448,15 @@ %1$s ఆన్ చేయబడింది - అభ్యర్ధన బ్లాక్ చేయాలా? + అభ్యర్ధనను బ్లాక్ చేసేదా? %1$s గ్రూపు లింక్ ద్వారా ఈ గ్రూపులో చేరడం లేదా చేరడానికి అభ్యర్ధించడం సాధ్యం కాదు. వారిని ఇంకా గ్రూపుకు మాన్యువల్‌గా జోడించవచ్చు. - అభ్యర్ధన బ్లాక్ చేయండి + అభ్యర్ధనను బ్లాక్ చేయండి రద్దు - నిరోధించిన   + బ్లాక్ చేయబడింది ఫిల్టర్‌ను ఖాళీ చేయండి @@ -480,43 +481,43 @@ %1$d సంభాషణలు ఇన్‌బాక్స్‌కు తరలించబడ్డాయి - చదవడం + చదివినది చదివినవి - చదవనివి + చదవనిది చదవనివి పిన్ - పిన్ + పిన్స్ - - అన్‌పిన్ + అన్‌పిన్ + అన్‌పిన్స్ మ్యూట్ - మ్యూట్ + మ్యూట్స్ అన్‌మ్యూట్ - అన్‌మ్యూట్ + అన్‌మ్యూట్స్ ఎంచుకోండి ఆర్కైవ్ - ఆర్కైవ్‌లు + ఆర్కైవ్స్ అన్‌ఆర్కైవ్ - అన్‌ఆర్కైవ్ + అన్‌ఆర్కైవ్స్ తొలగించండి తొలగించండి - అన్నీ ఎంచుకో + అన్నిటిని ఎంచుకోండి %1$dఎంచుకోబడింది %1$d ఎంచుకోబడింది @@ -542,6 +543,15 @@ +%1$d + + మీ పరికరాలను తిరిగి లింక్ చేయండి + + మీ పరికరం నమోదు చేయబడనప్పుడు మీరు జోడించిన పరికరాలు అన్‌లింక్ చేయబడ్డాయి. ఏవైనా పరికరాలను తిరిగి లింక్ చేయడానికి సెట్టింగ్‌లకు వెళ్ళండి. + + సెట్టింగ్‌లను తెరవండి + + తర్వాత + సభ్యులను ఎంచుకోండి @@ -935,7 +945,7 @@ నాకు ప్రస్తావనల తెలియజేయండి - మీరు మ్యూట్ చేసిన మాటామంతీలో పేర్కొన్నప్పుడు నోటిఫికేషన్‌లను స్వీకరించాలా? + మ్యూట్ చేయబడిన చాట్స్‌లో మీరు పేర్కొనబడినప్పుడు నోటిఫికేషన్‌లను అందుకునేదా? ఎల్లప్పుడూ నాకు తెలియజేయండి నాకు తెలియజేయవద్దు @@ -953,6 +963,16 @@ యూజర్‌నేమ్ సృష్టించబడింది యూజర్‌నేమ్ కాపీ చేయబడింది + + యూజర్‌నేమ్‌ను తొలగించలేకపోయాము. తరువాత మళ్ళీ ప్రయత్నించండి. + + యూజర్‌నేమ్ తొలగించబడింది + + + + మీ యూజర్‌నేమ్‌తో ఏదో తప్పు జరిగింది, ఇది మీ ఖాతాకు ఇక ఏమాత్రం కేటాయించబడలేదు. మీరు ప్రయత్నించి, దానిని మళ్లీ సెట్ చేయవచ్చు లేదా ఒక కొత్త దానిని ఎంచుకోవచ్చు. + + ఇప్పుడు పరిష్కరించండి @@ -1156,8 +1176,8 @@ కొత్త సమూహం స్నేహితులను ఆహ్వానించండి సందేశాన్ని వాడుటకు - స్వరూపం - ఫోటో జోడించండి + చాట్ రంగులు + ప్రొఫైల్ చిత్రాన్ని జోడించండి రిప్లైలు @@ -1468,14 +1488,14 @@ అంగీకరించండి కొనసాగించు తొలగించండి - నిరోధించు - అనుమతించు + బ్లాక్ చేయండి + అన్‌బ్లాక్ చేయండి %1$s మీకు సందేశం ఇవ్వనివ్వండి మరియు మీ పేరు మరియు ఫోటోను వారితో పంచుకోవాలా? మీరు అంగీకరించే వరకు మీరు వారి సందేశాన్ని చూశారని వారికి తెలియదు. - మీకు %1$s సందేశం పంపండి మరియు మీ పేరు మరియు ఫోటోను వారితో పంచుకోవాలా? మీరు వాటిని అన్‌బ్లాక్ చేసే వరకు మీకు సందేశాలు రావు. + %1$s మీకు సందేశం పంపనివ్వండి మరియు మీ పేరు మరియు ఫోటోను వారితో పంచుకునేదా? మీరు వారిని అన్‌బ్లాక్ చేసేంతవరకు మీరు ఎటువంటి సందేశాలు అందుకోరు. - %1$s మీకు సందేశం పంపాలని అనుకుంటున్నారా? మీరు వారిని అన్‌బ్లాక్ చేసేంత వరకు మీరు ఎలాంటి సందేశాలను పొందలేరు. - %1$s నుంచి అప్‌డేట్‌లు మరియు న్యూస్ పొందాలా? మీరు వాటిని అన్‌బ్లాక్ చేసేంత వరకు ఎలాంటి అప్‌డేట్‌లను పొందలేరు. + %1$s మీకు సందేశం పంపనివ్వమంటారా? మీరు వారిని అన్‌బ్లాక్ చేసేంతవరకు మీరు ఎలాంటి సందేశాలను అందుకోరు. + %1$s నుంచి అప్‌డేట్స్ మరియు వార్తలను పొందేదా? మీరు వారిని అన్‌బ్లాక్ చేసేంతవరకు మీరు ఎలాంటి అప్‌డేట్‌లను అందుకోలేరు. ఈ గ్రూపు తో మీ సంభాషణను కొనసాగించండి మరియు మీ పేరు మరియు ఫోటోను దాని సభ్యులతో పంచుకోవాలా? \@ప్రస్తావనలు మరియు నిర్వాహకులు వంటి క్రొత్త లక్షణాలను చైతన్యవంతం చేయడానికి ఈ సమూహాన్ని అప్‌గ్రేడ్ చేయండి. ఈ గుంపులో తమ పేరు లేదా ఛాయాచిత్రంను పంచుకోని సభ్యులను చేరడానికి ఆహ్వానించబడతారు. ఈ లెగసీ సమూహం ఇకపై ఉపయోగించబడదు ఎందుకంటే ఇది చాలా పెద్దది. గరిష్ట సమూహ పరిమాణం %1$d @@ -1483,7 +1503,7 @@ ఈ గుంపులో చేరండి మరియు మీ పేరు మరియు ఫోటోను దాని సభ్యులతో పంచుకోవాలా? మీరు అంగీకరించే వరకు మీరు వారి సందేశాలను చూశారని వారికి తెలియదు. ఈ గ్రూపులో చేరి, మీ పేరు మరియు ఫోటోని దాని సభ్యులతో పంచుకుంటారా? మీరు ఆమోదించేంత వరకు వారి సందేశాలను చూడలేరు. ఈ గుంపులో చేరాలా? మీరు అంగీకరించే వరకు మీరు వారి సందేశాలను చూశారని వారికి తెలియదు. - ఈ గుంపును అన్‌బ్లాక్ చేసి, మీ పేరు మరియు ఫోటోను దాని సభ్యులతో పంచుకోవాలా? మీరు వాటిని అన్‌బ్లాక్ చేసే వరకు మీకు సందేశాలు రావు. + ఈ గ్రూప్‌ను అన్‌బ్లాక్ చేసి, మీ పేరు మరియు ఫోటోను దాని సభ్యులతో పంచుకునేదా? మీరు వారిని అన్‌బ్లాక్ చేసేంతవరకు మీరు సందేశాలు అందుకోరు. వీక్షణ %1$s సభ్యుడు @@ -1584,9 +1604,20 @@ క్రొత్త పిన్ సృష్టించండి + + SMS కోడ్ పంపండి + + Signal నమోదు చేసుకోవడం - Android కొరకు PIN ను మళ్ళీ నమోదు చేయడంలో సహాయం కావాలి + + మీ PIN అనేది మీరు సృష్టించిన %1$d+ అంకెల కోడ్, ఇది సంఖ్య లేదా ఆల్ఫాన్యూమరిక్‌గా ఉండవచ్చు.\n\nఒకవేళ మీ PIN ను మీరు గుర్తు పెట్టుకోలేకపోతే, ఒక కొత్త దానిని మీరు సృష్టించవచ్చు. + + ఒకవేళ మీ PIN ను మీరు గుర్తు పెట్టుకోలేకపోతే, ఒక కొత్త దానిని మీరు సృష్టించవచ్చు. + + మీ PIN అంచనాలు అయిపోయాయి, అయితే ఒక కొత్త PIN ను సృష్టించడం ద్వారా మీ Signal ఖాతాను మీరు ఇప్పటికీ యాక్సెస్ చేయవచ్చు. + హెచ్చరిక - మీరు పిన్‌ను నిలిపివేస్తే, మీరు మాన్యువల్‌గా ప్రత్యామ్నాయము చేసి పునరుద్ధరించకపోతే Signal‌ను తిరిగి నమోదు చేసినప్పుడు మీరు మొత్తం డేటాను కోల్పోతారు. పిన్ నిలిపివేయబడినప్పుడు మీరు రిజిస్ట్రేషన్ లాక్‌ని ఆన్ చేయలేరు. + ఒకవేళ మీరు PIN ను నిలిపివేస్తే, మీరు మాన్యువల్‌గా బ్యాకప్ చేసి పునరుద్ధరించకపోతే మీరు Signal ను తిరిగి నమోదు చేసినప్పుడు మీరు మొత్తం డేటాను కోల్పోతారు. PIN నిలిపివేయబడిప్పుడు మీరు నమోదు లాక్‌ను ఆన్ చేయలేరు. పిన్‌ను ఆపివేయి @@ -1616,8 +1647,8 @@ నా స్టోరీ - నిరోధించు - అనుమతించు + బ్లాక్ చేయండి + అన్‌బ్లాక్ చేయండి @@ -1711,9 +1742,9 @@ కెమెరా - మ్యూట్ తీసివేయి + అన్‌మ్యూట్ - నిశబ్ధం + మ్యూట్ రింగ్ @@ -1726,12 +1757,12 @@ - %1$s బ్లాక్ చేయబడింది + %1$s బ్లాక్ చేయబడ్డారు మరింత సమాచారం మీరు వారి ఆడియో లేదా వీడియోను స్వీకరించరు మరియు వారు మీది స్వీకరించరు. ఆడియోను స్వీకరించలేరు & %1$s నుండి వీడియో %1$s నుండి ఆడియో మరియు వీడియోను స్వీకరించలేరు - మీ భద్రతా సంఖ్య మార్పును వారు ధృవీకరించకపోవడమే దీనికి కారణం కావచ్చు, వారి పరికరంలో సమస్య ఉంది లేదా వారు మిమ్మల్ని నిరోధించారు. + మీ భద్రతా నెంబర్ మార్పును వారు ధృవీకరించకపోవడమే దీనికి కారణం కావచ్చు, వారి పరికరంలో సమస్య ఉంది లేదా వారు మిమ్మల్ని బ్లాక్ చేశారు. స్క్రీన్ షేర్ వీక్షించడానికి స్వైప్ చేయండి @@ -1768,11 +1799,18 @@ మీరు స్నేహితులతో అనుసంధానం కావడానికి మరియు సందేశాలను పంపడానికి సాయపడేందుకు Signal కు కాంటాక్ట్‌లు మరియు మీడియా పర్మిషన్‌లు అవసరం. Signal ప్రైవేట్ కాంటాక్ట్ డిస్కవరీ ఉపయోగించి మీ కాంటాక్ట్‌లు అప్‌లోడ్ చేయబడతాయి, అంటే అవి ఎండ్- టూ-ఎండ్ ఎన్‌క్రిప్ట్ చేయబడ్డవి మరియు Signal సర్వీస్‌కు ఎన్నడూ కనిపించవు. స్నేహితులతో అనుసంధానం కావడానికి సాయపడేందుకు Signal కు కాంటాక్ట్‌ల పర్మిషన్ కావాలి. Signal ప్రైవేట్ కాంటాక్ట్ డిస్కవరీ ఉపయోగించి మీ కాంటాక్ట్‌లు అప్‌లోడ్ చేయబడతాయి, అంటే అవి ఎండ్- టూ-ఎండ్ ఎన్‌క్రిప్ట్ చేయబడ్డవి మరియు Signal సర్వీస్‌కు ఎన్నడూ కనిపించవు. మీరు ఈ నంబరును నమోదు చేయడానికి చాలా ప్రయత్నాలు చేసారు. దయచేసి తర్వాత మళ్లీ ప్రయత్నించండి. + + ఈ నంబర్‌ను నమోదు చేయడానికి మీరు చాలా ఎక్కువ ప్రయత్నాలు చేశారు. దయచేసి %1$sలో మళ్ళీ ప్రయత్నించండి. కనెక్ట్ చేయడం సాధ్యం కాలేదు. దయచేసి నెట్వర్క్ కనెక్షన్ను తనిఖీ చేసి మళ్ళీ ప్రయత్నించండి. నాన్-స్టాండర్డ్ నెంబర్ ఫార్మెట్ మీరు నమోదు చేసిన నెంబరు (%1$s) నాన్-స్టాండర్డ్ ఫార్మెట్‌లో కనిపిస్తోంది.\n\n%2$s అని మీ అర్ధమా? Molly Android - ఫోన్ నెంబర్ ఫార్మెట్ + కాల్ అభ్యర్ధించబడింది + + SMS అభ్యర్ధించబడింది + + ధృవీకరణ కోడ్ అభ్యర్ధించబడింది డీబగ్ లాగ్ను సమర్పించకుండా మీరు ఇప్పుడు %1$d దూరంగా ఉన్నారు. డీబగ్ లాగ్ను సమర్పించకుండా మీరు ఇప్పుడు %1$d మళ్లే ఉన్నారు. @@ -1792,6 +1830,16 @@ కాల్ ధృవీకరణ కోడ్ కోడ్ మళ్ళీ పంపండి + + నమోదు చేసుకోవడంలో సమస్య ఉన్నదా? + + • మీ SMS లేదా కాల్‌ను అందుకోవడానికి మీ ఫోన్‌కు సెల్యూలర్ సిగ్నల్ ఉన్నట్లుగా ధృవీకరించుకోండి\n • నంబర్‌కు ఫోన్ కాల్‌ను మీరు అందుకోగలరని నిర్ధారించండి\n • మీ ఫోన్ నెంబర్‌ను మీరు సరిగ్గా ఎంటర్ చేసినట్లుగా తనిఖీ చేయండి. + + మరింత సమాచారం కొరకు, దయచేసి ఈ దిగువ సమస్య పరిష్కార దశలను అనుసరించండి లేదా మద్దతును సంప్రదించండి + + ఈ సమస్య పరిష్కార దశలు + + మద్దతును సంప్రదించండి రిజిస్ట్రేషన్ లాక్ ఆన్ చేయాలా? @@ -1951,13 +1999,17 @@ మీరు ఒక బ్యాడ్జీని రీడిమ్ చేసుకున్నారు - మీ స్టోరీకు %1$s ప్రతిస్పందించారు + మీ కథకు %1$s ప్రతిస్పందించారు - వారి స్టోరీకు %1$s ప్రతిస్పందించారు + వారి కథకు %1$s ప్రతిస్పందించారు చెల్లింపు షెడ్యూల్ చేయబడ్డ సందేశం + + మీ సందేశ చరిత్ర విలీనం చేయబడింది + + %1$s %2$s కు చెందినది Molly నవీకరణ @@ -2030,7 +2082,7 @@ ఎంఎంఎస్ సందేశాన్ని మనుగడలో కాని సెషన్ కోసం గుప్తీకరించబడింది - ప్రకటనలను మ్యూట్లో ఉంచు + నోటిఫికేషన్‌లను మ్యూట్ చేయండి దిగుమతి పురోగతిలో ఉంది @@ -2087,14 +2139,16 @@ భద్రతలేని సందేశం %1$s %2$s పరిచయం - దీనికి %1$sను ప్రతిస్పందించారు: %2$s - మీ వీడియోకు %1$s స్పందించింది. - మీ చిత్రానికి %1$s ప్రతిస్పందించింది. - మీ GIFలో %1$sకు ప్రతిస్పందించారు. - మీ ఫైల్‌కు %1$s ప్రతిస్పందించింది. - మీ ఆడియోకు %1$s స్పందించింది. - మీ వీక్షణ-ఒకసారి మీడియాకు %1$s స్పందించింది. - మీ స్టిక్కర్‌కు %1$s స్పందించింది. + దీనికి %1$s ప్రతిస్పందించారు: \"%2$s\". + మీ వీడియోకు %1$s ప్రతిస్పందించారు. + మీ చిత్రానికి %1$s ప్రతిస్పందించారు. + మీ GIF కు %1$s ప్రతిస్పందించారు. + మీ ఫైల్‌కు %1$s ప్రతిస్పందించారు. + మీ ఆడియోకు %1$s ప్రతిస్పందించారు. + మీ ఒక్కసారి-వీక్షించండి మీడియాకు %1$s ప్రతిస్పందించారు. + + మీ చెల్లింపుకు %1$s ప్రతిస్పందించారు. + మీ స్టిక్కర్‌కు %1$s ప్రతిస్పందించారు. ఈ సందేశం తొలగించబడింది. చేరిన Signal నోటిఫికేషన్‌లను ఆపివేయాలా? మీరు వాటిని Signal > సెట్టింగులు > నోటిఫికేషన్‌లు. @@ -2298,7 +2352,7 @@ కాల్ నోటిఫికేషన్‌లను ప్రారంభించండి పరిచయాన్ని నవీకరించండి - అభ్యర్ధన బ్లాక్ చేయండి + అభ్యర్ధనను బ్లాక్ చేయండి సాధారణంగా సమూహాలు లేవు. అభ్యర్థనలను జాగ్రత్తగా సమీక్షించండి. సాధారణంగా సమూహాలు లేవు. అభ్యర్థనలను జాగ్రత్తగా సమీక్షించండి. వీక్షణ @@ -2487,7 +2541,7 @@ నిర్వహణ సమస్య - %1$s నుంచి సందేశం, స్టిక్కర్, ప్రతిస్పందన, లేదా రీడ్ రిసిప్ట్‌ని మీకు డెలివరీ చేయలేం. వారు మీకు నేరుగా లేదా గ్రూపులో పంపడానికి ప్రయత్నించి ఉండవచ్చు. + %1$s నుంచి సందేశం, స్టిక్కర్, ప్రతిస్పందన, లేదా రీడ్ రిసిప్ట్ మీకు డెలివరీ చేయబడదు. వారు మీకు నేరుగా లేదా గ్రూపులో పంపడానికి ప్రయత్నించి ఉండవచ్చు. %1$s నుంచి సందేశం, స్టిక్కర్, ప్రతిస్పందన, లేదా రీడ్ రిసిప్ట్ మీకు డెలివరీ చేయబడదు. @@ -2555,7 +2609,7 @@ పెండింగ్‌లో ఉంది - పంపాను + వీరికి పంపబడింది నుండి పంపబడింది కు పంపిణీ చేయబడింది చదివిన వారు @@ -2635,10 +2689,10 @@ అప్రమేయం ఉపయోగించు నియమము ఉపయోగించు - 1 గంట నిశబ్దంగా ఉంచు - 8 గంటలపాటు మ్యూట్ చేయండి - 1 రోజు నిశబ్దంగా ఉంచు - 7 రోజులు నిశబ్దంగా ఉంచు + 1 గంట పాటు మ్యూట్ చేయండి + 8 గంటల పాటు మ్యూట్ చేయండి + 1 రోజుంతా మ్యూట్ చేయండి + 7 రోజుల పాటు మ్యూట్ చేయండి ఎల్లప్పుడూ అప్రమేయ అమరికలు @@ -2675,9 +2729,9 @@ చిరునామా పుస్తక చిత్రాలను ఉపయోగించండి అందుబాటులో ఉంటే మీ చిరునామా పుస్తకం నుండి సంప్రదింపు చిత్రాలను ప్రదర్శించండి - మ్యూట్ చేసిన చాట్‌లను ఆర్కైవల్‌లోనే ఉంచండి + మ్యూట్ చేయబడిన చాట్‌లను ఆర్కైవల్‌లో ఉంచండి - ఆర్కైవ్ చేసిన మ్యూటెడ్ చాట్‌లు, కొత్త సందేశం వచ్చినప్పుడు మ్యూట్ చేయబడతాయి. + ఆర్కైవ్ చేయబడిన మ్యూటెడ్ చాట్‌లు, కొత్త సందేశం వచ్చినప్పుడు ఆర్కైవ్ చేయబడి ఉంటాయి. లింక్ పూర్వప్రదర్శన రూపొందించండి మీరు పంపే సందేశాల కోసం వెబ్‌సైట్ల నుండి నేరుగా లింక్ పూర్వప్రదర్శనలు తిరిగి పొందండి. సంకేతపదమును మార్చు @@ -2971,7 +3025,7 @@ చెల్లింపు పంపబడింది పేమెంట్ అందుకోబడింది పేమెంట్ పూర్తయింది %1$s - నెంబర్ బ్లాక్ చేయండి + బ్లాక్ నెంబర్ బదిలీ చేయి @@ -3056,7 +3110,7 @@ దీనికి కొత్త సందేశం… - వినియోగదారుని నిరోధించాలా + యూజర్‌ను బ్లాక్ చేయండి సమూహంకి కలపండి @@ -3128,10 +3182,10 @@ - మ్యూట్ తీసివేయి + అన్‌మ్యూట్ చేయండి - ప్రకటనలను మ్యూట్లో ఉంచు + నోటిఫికేషన్‌లను మ్యూట్ చేయండి సమూహ సెట్టింగులు @@ -3295,6 +3349,8 @@ మీ పిన్ను నమోదు చేయండి మీ ఖాతా కోసం మీరు సృష్టించిన పిన్‌ను నమోదు చేయండి. ఇది మీ SMS ధృవీకరణ కోడ్‌కు భిన్నంగా ఉంటుంది. + + మీ ఖాతా కొరకు మీరు సృష్టించిన PIN ను ఎంటర్ చేయండి. ఆల్ఫాన్యూమరిక్ పిన్ను నమోదు చేయండి సంఖ్యా పిన్ను నమోదు చేయండి తప్పు పిన్. మళ్ళీ ప్రయత్నించండి. @@ -3398,7 +3454,10 @@ మీ బ్యాకప్‌లో బ్యాకప్ చేయలేని చాలా పెద్ద ఫైలు ఉంది. దయచేసి దానిని తొలగించండి మరియు కొత్త బ్యాకప్‌ను సృష్టించండి. ప్రత్యామ్నాయములు ను నిర్వహించడానికి నొక్కండి. తప్పు సంఖ్యా? + నాకు కాల్ చేయండి (%1$02d:%2$02d) + + కోడ్ మళ్ళీ పంపండి (%1$02d:%2$02d) Signal మద్దతును సంప్రదించండి Signal రిజిస్ట్రేషన్ - Android కోసం ధృవీకరణ కోడ్ తప్పు కోడ్ @@ -3406,6 +3465,18 @@ తెలియని నా ఫోన్ నంబర్ చూడండి ఫోన్ నంబర్ ద్వారా నన్ను కనుగొనండి + + ఫోన్ నంబర్ + + మీ ఫోన్ నంబర్‌ను ఎవరు చూడగలరు మరియు దానితో Molly లో మిమ్మల్ని ఎవరు సంప్రదించగలరో ఎంచుకోండి. + + నా నంబర్‌ను ఎవరు చూడగలరు + + Molly లో మీ ఫోన్ నంబర్‌ను ఎవరూ చూడరు + + నంబర్ ద్వారా నన్ను ఎవరు కనుగొనగలరు + + మీరు సందేశం పంపే వ్యక్తులు మరియు గ్రూప్‌లకు మీ ఫోన్ నంబర్ కనిపిస్తుంది. వారి ఫోన్ పరిచయాలలో మీ నంబర్ ఉన్న వ్యక్తులు కూడా దీన్ని Molly లో చూస్తారు. ప్రతి ఒక్కరూ నా పరిచయాలు యెవరు లేరు @@ -3562,8 +3633,8 @@ - నిరోధించు - అనుమతించు + బ్లాక్ చేయండి + అన్‌బ్లాక్ చేయండి పరిచయాలకు జోడించండి కాంటాక్ట్‌లను ఓపెన్ చేయగల యాప్‌ని కనుగొనలేకపోయింది. @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" బ్లాక్ చేయబడింది. - \"%1$s\" ని నిరోధించడంలో విఫలమైంది - \"%1$s\" అన్‌బ్లాక్ చేయబడింది. + \"%1$s\" బ్లాక్ చేయబడినారు. + \"%1$s\" ను బ్లాక్ చేయడంలో విఫలమైంది + \"%1$s\" అన్‌బ్లాక్ చేయబడినారు. సభ్యులను సమీక్షించండి @@ -3644,7 +3715,7 @@ మీ పరిచయం గ్రూప్ నుండి తొలగించండి పరిచయాన్ని నవీకరించండి - నిరోధించు + బ్లాక్ చేయండి తొలగించండి ఇటీవల వారి ప్రొఫైల్ పేరును %1$s నుండి %2$s గా మార్చారు @@ -3667,11 +3738,11 @@ బలహీనమైన Wi-Fi. సెల్యూలర్‌కు మారండి. - మీ ఖాతాను తొలగిస్తుంది: + మీ ఖాతాను తొలగించడం అనేది: మీ ఫోన్ నంబర్‌ను నమోదు చేయండి ఖాతాను తొలగించండి - మీ ఖాతా సమాచారం మరియు ప్రొఫైల్ ఫోటోను తొలగించండి - మీ అన్ని సందేశాలను తొలగించండి + మీ ఖాతా సమాచారం మరియు ప్రొఫైల్ ఫోటోను తొలగిస్తుంది + మీ సందేశాలు అన్నింటిని తొలగిస్తుంది మీ చెల్లింపుల ఖాతాలో %1$s తొలగించండి దేశ కోడ్ పేర్కొనబడలేదు సంఖ్య పేర్కొనబడలేదు @@ -3784,12 +3855,12 @@ వ్యాలెట్‌ని డీయాక్టివేట్ చేయండి మీ బ్యాలెన్స్ - పేమెంట్‌లను డీ యాక్టివేట్ చేయడానికి ముందు మీ ఫండ్స్‌ని మరో వ్యాలెట్ చిరునామాకు బదిలీ చేయాలని మీకు సిఫారసు చేయబడుతోంది. మీరు మీ ఫండ్స్‌ని ఇప్పుడు బదిలీ చేయరాదని ఎంచుకుంటే, మీరు పేమెంట్‌లను రీయాక్టివేట్ చేసినట్లయితే, Molly కు లింక్ చేయబడ్డ మీ వ్యాలెట్‌లో అవి ఉంటాయి. + చెల్లింపులను డీయాక్టివేట్ చేయడానికి ముందు మీ నిధులను మరో వ్యాలెట్ చిరునామాకు మీరు బదిలీ చేయాలని మీకు సిఫారసు చేయబడుతోంది. ఒకవేళ మీరు మీ నిధులను ఇప్పుడు బదిలీ చేయరాదని ఎంచుకుంటే, ఒకవేళ మీరు చెల్లింపులను రీయాక్టివేట్ చేస్తే, Molly కు లింక్ చేయబడ్డ మీ వ్యాలెట్‌లో అవి ఉంటాయి. మిగిలిన మొత్తాన్ని బదిలీ చేయడం బదిలీ చేయకుండానే డీయాక్టివేట్ చేయడం నిష్క్రియం చేయండి బదిలీ చేయకుండానే డీయాక్టివేట్ చేయాలా? - మీరు చెల్లింపులను తిరిగి సక్రియం చేయడానికి ఎంచుకుంటే మీ బ్యాలెన్స్ Molly తో అనుసంధానించబడిన మీ వాలెట్‌లో ఉంటుంది. + ఒకవేళ మీరు చెల్లింపులను రీయాక్టివేట్ చేయడానికి ఎంచుకుంటే మీ నిలవ Molly తో లింక్ చేయబడ్డ మీ వాలెట్‌లో ఉంటుంది. వ్యాలెట్‌ని డీయాక్టివేట్ చేయడంలో దోషం. @@ -4003,12 +4074,12 @@ ప్రొఫైల్ సృష్టించండి - నిరోధించిన   + బ్లాక్ చేయబడింది %1$d కాంటాక్ట్‌లు సందేశం అదృశ్యమవుతున్న సందేశాలు యాప్ సెక్యూరిటీ - నిరోధించు స్క్రీన్షాట్లు లో ది ఇటీవలి జాబితా మరియు లోపల ది అనువర్తనం + ఇటీవలి జాబితాలో మరియు యాప్ లోపల స్క్రీన్‌షాట్‌లను బ్లాక్ చేయండి Signal సందేశాలు మరియు కాల్స్, ఎల్లప్పుడూ రిలే కాల్స్, మరియు సీల్ చేయబడ్డ సెండర్ క్రొత్త మాటామంతిల కోసం అప్రమేయ టైమర్ మీరు ప్రారంభించిన అన్ని క్రొత్త మాటామంతిల కోసం అప్రమేయంగా అదృశ్యమయ్యే సందేశ టైమర్‌ను సెట్ చేయండి. @@ -4106,7 +4177,7 @@ ఇప్పుడు కాదు - కస్టమైజ్ రియాక్షన్‌లు + ప్రతిస్పందనలను అనుకూలపరచండి ఎమోజీని విడుదల చేయడానికి తట్టండి పునరుద్ధరించు భద్రపరుచు @@ -4165,7 +4236,7 @@ కాల్ - నిశబ్ధం + మ్యూట్ మ్యూట్ చేయబడింది @@ -4175,10 +4246,10 @@ సంప్రదింపు వివరాలు భద్రతా సంఖ్యను వీక్షించండి - నిరోధించు - బ్లాక్ సమూహం - అనుమతించు - సమూహాన్ని అన్‌బ్లాక్ చేయండి + బ్లాక్ చేయండి + గ్రూప్‌ను బ్లాక్ చేయండి + అన్‌బ్లాక్ చేయండి + గ్రూప్‌ను అన్‌బ్లాక్ చేయండి సమూహంకి కలపండి అన్నింటిని చూడు సభ్యులను జోడించండి @@ -4186,9 +4257,9 @@ అభ్యర్థనలు & ఆహ్వానాలు సమూహ లింక్ కాంటాక్ట్ వలే జోడించండి - మ్యూట్ తీసివేయి - సంభాషణ %1$s వరకు మ్యూట్ చేయబడింది - సంభాషణ శాశ్వతంగా మ్యూట్ చేయబడింది + అన్‌మ్యూట్ చేయండి + %1$s వరకు సంభాషణ మ్యూట్ చేయబడింది + శాశ్వతంగా సంభాషణ మ్యూట్ చేయబడింది ఫోన్ నెంబర్ క్లిప్‌బోర్డ్‌కు కాపీ చేయబడింది ఫోను నంబరు Signal కు మద్దతు ఇవ్వడం ద్వారా మీ ప్రొఫైల్ కొరకు బ్యాడ్జీలను పొందండి. మరింత తెలుసుకోవడానికి బ్యాడ్జి మీద తట్టండి. @@ -4204,8 +4275,8 @@ ఎవరు సందేశాలను పంపగలరు? - ప్రకటనలను మ్యూట్లో ఉంచు - మ్యూట్ చేయలేదు + నోటిఫికేషన్‌లను మ్యూట్ చేయండి + మ్యూట్ చేయబడలేదు ప్రస్తావనలు ఎల్లప్పుడూ నోటిఫై చేయండి నోటిఫై చేయవద్దు @@ -4234,7 +4305,7 @@ తొలగించండి - నిరోధించు + బ్లాక్ చేయండి %1$sను తొలగించేదా? @@ -4242,7 +4313,7 @@ %1$s తొలగించబడింది - %1$s బ్లాక్ చేయబడింది + %1$s బ్లాక్ చేయబడినారు %1$sను తొలగించలేకపోయాను @@ -4508,7 +4579,7 @@ ఒక నెట్‌వర్క్ దోషం కారణంగా మీ విరాళాన్ని పంపలేరు. మీ కనెక్షన్‌ను తనిఖీ చేయండి మరియు మళ్ళీ ప్రయత్నించండి. - %1$sకు విరాళం ఇవ్వండి + %1$s తరఫున విరాళం మీ తరఫున %1$s Signalకు విరాళం ఇచ్చారు @@ -4853,13 +4924,13 @@ ఈ గ్రూప్‌లో మీరు ఇక ఏమాత్రం సభ్యులు కానందు వల్ల, ఈ కథకు మీరు బదులివ్వలేరు. - స్టోరీకు ప్రతిస్పందించారు + కథకు ప్రతిస్పందించారు వీక్షణలు రిప్లైలు - ఈ స్టోరీకు ప్రతిస్పందించండి + ఈ కథకు ప్రతిస్పందించండి %1$sకు ప్రైవేట్‌గా బదులివ్వడం @@ -4905,11 +4976,11 @@ మీ స్టోరీను ఎవరు చూడవచ్చు అనేది ఎంచుకోండి. మీరు ఇప్పటికే పంపిన స్టోరీలను మార్పులు ప్రభావితం చేయవు. - రిప్లైలు & రియాక్షన్‌లు + ప్రత్యుత్తరాలు & ప్రతిస్పందనలు - రిప్లైలు & రియాక్షన్‌లు అనుమతించండి + ప్రత్యుత్తరాలు & ప్రతిస్పందనలను అనుమతించండి - మీ స్టోరీను వీక్షించగల వ్యక్తులను ప్రతిస్పందించడానికి మరియు ప్రత్యుత్తరం ఇచ్చేందుకు అనుమతించండి + మీ కథను వీక్షించగల వ్యక్తులను ప్రతిస్పందించడానికి మరియు ప్రత్యుత్తరం ఇచ్చేందుకు అనుమతించండి Signal కనెక్షన్‌లు @@ -5057,11 +5128,11 @@ - మీరు %1$s యొక్క స్టోరీకు ప్రతిస్పందించారు + మీరు %1$s యొక్క కథకు ప్రతిస్పందించారు - మీ స్టోరీకు ప్రతిస్పందించారు + మీ కథకు ప్రతిస్పందించారు - ఒక స్టోరీకు ప్రతిస్పందించారు + ఒక కథకు ప్రతిస్పందించారు @@ -5601,5 +5672,15 @@ యూజర్‌నేమ్‌ను తొలగించండి + + + గం + + ని + + సెట్ + + స్క్రీన్ లాక్ వర్తించడానికి ముందు కనీస సమయం 1 నిమిషం. + diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 61d4aec268..be99009941 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -14,6 +14,7 @@ + ใช่ ไม่ @@ -96,13 +97,13 @@ กำลังตรวจหาข้อความ… - ผู้ใช้ที่ถูกปิดกั้นอยู่ - เพิ่มผู้ใช้ที่ปิดกั้น - ผู้ใช้ที่ถูกปิดกั้นจะไม่สามารถโทรหรือส่งข้อความหาคุณได้ - ไม่มีผู้ใช้ที่ถูกปิดกั้นอยู่ - ปิดกั้นผู้ใช้หรือไม่? + ผู้ใช้ที่ถูกบล็อก + เพิ่มผู้ใช้ที่ถูกบล็อก + ผู้ใช้ที่ถูกบล็อกจะไม่สามารถโทรหรือส่งข้อความหาคุณได้ + ไม่มีผู้ใช้ที่ถูกบล็อก + บล็อกผู้ใช้หรือไม่ \"%1$s\" จะไม่สามารถโทรหรือส่งข้อความหาคุณได้ - ปิดกั้น + บล็อก @@ -138,8 +139,8 @@ ดำเนินการต่อ - ปิดกั้นและออกจาก %1$s หรือไม่? - ปิดกั้น %1$s หรือไม่? + บล็อกและออกจาก %1$s หรือไม่ + บล็อก %1$s หรือไม่ คุณจะไม่ได้รับข้อความหรือข่าวสารจากกลุ่มนี้อีกต่อไป และสมาชิกจะไม่สามารถเพิ่มคุณกลับเข้ามาในกลุ่มนี้อีก สมาชิกจะไม่สามารถเพิ่มคุณกลับเข้ามาในกลุ่มนี้อีก สมาชิกจะสามารถเพิ่มคุณกลับเข้ามาในกลุ่มนี้อีก @@ -147,16 +148,16 @@ คุณจะสามารถส่งข้อความและโทรหากันได้ และชื่อกับรูปถ่ายของคุณจะถูกแบ่งปันกับพวกเขา คุณส่งข้อความหากันได้ - คนที่ถูกปิดกั้นจะไม่สามารถโทรหรือส่งข้อความหาคุณได้ - คนที่ถูกปิดกั้นจะส่งข้อความหาคุณไม่ได้ + ผู้ที่ถูกบล็อกจะไม่สามารถโทรหรือส่งข้อความหาคุณได้ + ผู้ที่ถูกบล็อกจะไม่สามารถส่งข้อความหาคุณได้ - ปิดกั้นการรับการปรับปรุงและข่าวสารของ Signal + บล็อกการรับข้อมูลอัปเดตและข่าวสารของ Signal กลับมารับการปรับปรุงและข่าวสารของ Signal ต่อไป - เลิกปิดกั้น %1$sหรือไม่? - ปิดกั้น - ปิดกั้นและออก - รายงานสแปมและปิดกั้น + เลิกบล็อก %1$s หรือไม่ + บล็อก + บล็อกและออก + รายงานว่าเป็นสแปมและบล็อก วันนี้ @@ -318,7 +319,7 @@ ข้อความ Signal เปลี่ยนมาใช้ Molly กันเถอะ %1$s โปรดเลือกผู้ติดต่อ - เลิกปิดกั้น + เลิกบล็อก แฟ้มแนบมีขนาดใหญ่เกินกำหนดสำหรับชนิดของข้อความที่คุณกำลังจะส่ง ไม่สามารถอัดเสียงได้! คุณไม่สามารถส่งข้อความเข้ากลุ่มนี้ได้ เพราะคุณไม่ได้เป็นสมาชิกแล้ว @@ -366,7 +367,7 @@ เกิดข้อผิดพลาดในการส่งสื่อ - ถูกรายงานว่าเป็นสแปมและถูกปิดกั้นแล้ว + รายงานว่าเป็นสแปมและบล็อกแล้ว ระงับการให้บริการส่งข้อความ SMS แล้ว คุณสามารถส่งออกข้อความของคุณไปยังแอปอื่นบนโทรศัพท์ได้ @@ -441,15 +442,15 @@ เปิด %1$s - ปิดกั้นคำขอหรือไม่ + บล็อกคำขอหรือไม่ %1$s ไม่สามารถเข้าร่วมหรือส่งคำขอเพื่อเข้าร่วมกลุ่มนี้ได้ทางลิงก์กลุ่ม แต่สามารถเพิ่มเข้ากลุ่มได้ด้วยตัวเอง - ปิดกั้นคำขอ + บล็อกคำขอ ยกเลิก - ถูกปิดกั้น + ถูกบล็อก ล้างตัวกรอง @@ -470,29 +471,29 @@ ย้าย %1$d การสนทนาไปยังกล่องขาเข้า - อ่าน + อ่านแล้ว - ยกเลิกการอ่าน + ยังไม่ได้อ่าน ปักหมุด - ยกเลิกปักหมุด + เลิกปักหมุด - ปิดเสียง + ปิดการแจ้งเตือน - ยกเลิกปิดเสียง + เปิดการแจ้งเตือน เลือก เก็บถาวร - ยกเลิกเก็บถาวร + เลิกเก็บถาวร ลบ @@ -522,6 +523,15 @@ +%1$d + + เชื่อมโยงอุปกรณ์ของคุณอีกครั้ง + + มีการเลิกเชื่อมโยงอุปกรณ์ที่ถูกเพิ่มเข้ามาในตอนที่อุปกรณ์หลักของคุณไม่ได้ลงทะเบียน ไปที่การตั้งค่าเพื่อเชื่อมโยงอุปกรณ์อีกครั้ง + + เปิดการตั้งค่า + + ไว้ภายหลัง + เลือกสมาชิก @@ -897,7 +907,7 @@ แจ้งให้ฉันรู้สำหรับการกล่าวถึง - รับการแจ้งเตือนเมื่อคุณถูกกล่าวถึงในการพูดคุยที่ปิดเสียงไว้หรือไม่? + รับการแจ้งเตือนเมื่อคุณถูกกล่าวถึงในแชทที่ปิดเสียงไว้หรือไม่ แจ้งให้ฉันรู้ตลอด ไม่ต้องแจ้งให้ฉันรู้ @@ -915,6 +925,16 @@ สร้างชื่อผู้ใช้แล้ว คัดลอกชื่อผู้ใช้แล้ว + + ไม่สามารถลบชื่อผู้ใช้ได้ โปรดลองอีกครั้งในภายหลัง + + ลบชื่อผู้ใช้แล้ว + + + + เกิดข้อผิดพลาดเกี่ยวกับชื่อผู้ใช้ ไม่สามารถใช้ชื่อผู้ใช้นี้กับบัญชีของคุณได้อีกต่อไป โปรดลองใหม่ด้วยการใส่ชื่อผู้ใช้อีกครั้งหรือตั้งชื่อผู้ใช้ใหม่ + + แก้ไขตอนนี้ @@ -1108,8 +1128,8 @@ กลุ่มใหม่ เชิญเพื่อน ใช้ SMS - หน้าตา - เพิ่มรูปภาพ + สีแชท + เพิ่มรูปโปรไฟล์ ตอบกลับ @@ -1410,14 +1430,14 @@ ตอบรับ ทำต่อ ลบ - ปิดกั้น - เลิกปิดกั้น + บล็อก + เลิกบล็อก อนุญาตให้ %1$s ส่งข้อความและเห็นชื่อกับรูปภาพของคุณหรือไม่? พวกเขาจะไม่รู้ว่าคุณได้เห็นข้อความของพวกเขาแล้วจนกว่าคุณจะตอบรับ - อนุญาตให้ %1$s ส่งข้อความและเห็นชื่อกับรูปภาพของคุณหรือไม่? คุณจะไม่ได้รับข้อความใดจนกว่าคุณจะเลิกปิดกั้นพวกเขา + อนุญาตให้ %1$s ส่งข้อความหารวมถึงมองเห็นชื่อและรูปของคุณหรือไม่ คุณจะไม่ได้รับข้อความจนกว่าจะเลิกบล็อกผู้ใช้รายนี้ - ให้ %1$s ส่งข้อความหาคุณหรือไม่ คุณจะไม่ได้รับข้อความใดๆ จนกว่าคุณเลิกปิดกั้นเขา - รับข้อมูลใหม่ๆ และข่าวสารจาก %1$s หรือไม่ คุณจะไม่ได้รับข้อมูลใหม่ๆ จนกว่าจะยกเลิกปิดกั้นเขา + อนุญาตให้ %1$s ส่งข้อความหาคุณหรือไม่ คุณจะไม่ได้รับข้อความจนกว่าจะเลิกบล็อกผู้ใช้รายนี้ + รับข้อมูลอัปเดตและข่าวสารจาก %1$s หรือไม่ คุณจะไม่ได้รับข้อมูลใดๆ จนกว่าจะเลิกบล็อกผู้ใช้คนนี้ ต้องการสนทนาภายในกลุ่มนี้ต่อไปและเปิดเผยชื่อกับรูปภาพให้สมาชิกในกลุ่มเห็นหรือไม่? ปรับรุ่นกลุ่มนี้เพื่อเปิดใช้ความสามารถใหม่ เช่น @กล่าวถึง และระบบผู้ดูแล สมาชิกที่ไม่ได้เปิดเผยชื่อหรือรูปภาพในกลุ่มนี้จะได้รับคำเชิญให้เข้าร่วม กลุ่มแบบเก่านี้ไม่สามารถใช้งานได้อีกต่อไป เนื่องจากมีขนาดใหญ่เกินไป ขนาดที่ใหญ่ที่สุดของกลุ่มคือ %1$d @@ -1425,7 +1445,7 @@ เข้าร่วมกลุ่มนี้และเปิดเผยชื่อและรูปถ่ายของคุณให้สมาชิกในกลุ่มเห็นหรือไม่? พวกเขาจะไม่รู้ว่าคุณเห็นข้อความของพวกเขาจนกว่าคุณจะตอบรับ เข้าร่วมกลุ่มนี้และแชร์ชื่อกับรูปภาพให้กับสมาชิกในกลุ่มหรือไม่ คุณจะไม่เห็นข้อความของพวกเขาจนกว่าคุณจะรับ เข้าร่วมกลุ่มนี้หรือไม่? พวกเขาจะไม่รู้ว่าคุณเห็นข้อความของพวกเขาจนกว่าคุณจะตอบรับ - คุณต้องการเลิกปิดกั้นกลุ่มนี้และเปิดเผยชื่อและรูปภาพให้สมาชิกในกลุ่มเห็นหรือไม่? คุณจะไม่ได้รับข้อความใดจนกว่าคุณจะเลิกปิดกั้นพวกเขา + คุณต้องการเลิกบล็อกกลุ่มนี้และเปิดเผยชื่อและรูปภาพให้สมาชิกในกลุ่มเห็นหรือไม่ คุณจะไม่ได้รับข้อความจนกว่าจะเลิกบล็อกกลุ่ม ดู สมาชิกของ %1$s @@ -1520,9 +1540,20 @@ สร้างรหัส PIN ใหม่ + + ส่งรหัสทาง SMS + + การลงทะเบียน Signal - ต้องการความช่วยเหลือเกี่ยวกับการลงทะเบียน PIN อีกครั้งสำหรับ Android + + PIN ของคุณคือรหัส %1$d+ หลักที่คุณสร้างไว้โดยอาจจะเป็นตัวเลขหรือตัวอักษรผสมกับตัวเลข\n\nหากจำ PIN ของตัวเองไม่ได้ คุณสามารถสร้างขึ้นมาใหม่ได้ + + หากจำ PIN ของตัวเองไม่ได้ คุณสามารถสร้างขึ้นมาใหม่ได้ + + คุณลองใส่ PIN จนครบจำนวนครั้งที่อนุญาตแล้ว แต่คุณยังสามารถเข้าถึงบัญชี Signal ของคุณได้ด้วยการสร้าง PIN ใหม่ + คำเตือน - หากคุณปิดใช้งาน PIN คุณจะสูญเสียข้อมูลเมื่อคุณลงทะเบียนบัญชี Signal ใหม่ นอกจากว่าคุณได้สำรองข้อมูลและกู้มันคืนมาเอง คุณไม่สามารถเปิดการใช้งานกุญแจลงทะเบียนในขณะที่ PIN ปิดการใช้งานอยู่ + หากคุณปิดใช้งาน PIN คุณจะสูญเสียข้อมูลทั้งหมดเมื่อลงทะเบียนบัญชี Signal ใหม่ (เว้นแต่ว่าคุณจะกู้คืนมาเองจากการสำรองข้อมูลเอาไว้) นอกจากนี้ คุณจะไม่สามารถเปิดใช้งานกุญแจลงทะเบียนในขณะที่ PIN ปิดใช้งานอยู่ ปิดใช้งาน PIN @@ -1552,8 +1583,8 @@ สตอรี่ของฉัน - ปิดกั้น - เลิกปิดกั้น + บล็อก + เลิกบล็อก @@ -1656,12 +1687,12 @@ - %1$s ได้ถูกปิดกั้นแล้ว + %1$s ถูกบล็อกอยู่ ข้อมูลเพิ่มเติม คุณจะไม่ได้รับเสียงและวิดีโอ และพวกเขาจะไม่ได้รับเสียงและวิดีโอของคุณ ไม่สามารถรับการโทรเสียงและการโทรวิดีโอจาก %1$s ไม่สามารถรับการโทรเสียงและการโทรวิดีโอจาก %1$s - เหตุนี้อาจเกิดขึ้นเนื่องจากพวกเขายังไม่ได้ตรวจยืนยันหมายเลขความปลอดภัยของคุณ อุปกรณ์ของพวกเขามีปัญหา หรือพวกเขาได้ปิดกั้นคุณไว้ + เหตุนี้อาจเกิดขึ้นเนื่องจากผู้ใช้คนนี้ยังไม่ได้ยืนยันหมายเลขความปลอดภัยที่เปลี่ยนไปของคุณ อุปกรณ์ของผู้ใช้มีปัญหา หรือผู้ใช้บล็อกคุณไว้ ปัดเพื่อดูจอที่แบ่งปัน @@ -1698,11 +1729,18 @@ Signal จำเป็นต้องมีการอนุญาตให้ใช้รายชื่อผู้ติดต่อและสื่อเพื่อช่วยให้คุณเชื่อมต่อกับเพื่อนๆ และส่งข้อความ รายชื่อผู้ติดต่อของคุณจะถูกอัปโหลดโดยใช้การค้นหาผู้ติดต่อส่วนตัว Signal ซึ่งแปลว่าสิ่งนี้เป็นการเข้ารหัสจากต้นทางถึงปลายทางและไม่ปรากฏในบริการของ Signal Signal จำเป็นต้องได้รับการอนุญาตให้ใช้รายชื่อผู้ติดต่อเพื่อช่วยให้คุณเชื่อมต่อกับเพื่อนๆ รายชื่อผู้ติดต่อของคุณจะถูกอัปโหลดโดยใช้โดยใช้การค้นหาผู้ติดต่อส่วนตัว Signal ซึ่งแปลว่าสิ่งนี้เป็นการเข้ารหัสจากต้นทางถึงปลายทางและไม่ปรากฏในบริการของ Signal คุณได้พยายามลงทะเบียนด้วยหมายเลขนี้หลายครั้งเกินไป โปรดลองใหม่ในภายหลัง + + คุณพยายามลงทะเบียนด้วยหมายเลขนี้หลายครั้งเกินไป โปรดลองอีกครั้งใน %1$s ไม่สามารถเชื่อมต่อบริการได้ โปรดตรวจสอบการเชื่อมต่อเครือข่ายและลองอีกครั้ง ฟอร์แมทหมายเลขที่ไม่มีมาตรฐาน ตัวเลขที่คุณป้อน (%1$s) เป็นฟอร์แมทที่ไม่ได้มาตรฐาน\n\n คุณต้องการใส่ %2$sหรือเปล่า Molly Android - ฟอร์แมทหมายเลขโทรศัพท์ + ส่งคำขอการโทรแล้ว + + ขอรหัสทาง SMS แล้ว + + ขอรหัสยืนยันแล้ว เหลืออีก %1$d ขั้นตอนในการส่งปูมดีบัก @@ -1721,6 +1759,16 @@ โทร รหัสยืนยัน ส่งรหัสอีกครั้ง + + หากประสบปัญหาในการลงทะเบียน + + • ตรวจสอบว่าโทรศัพท์ของคุณมีสัญญาณมือถือที่จะรับข้อความ SMS หรือสายโทรเข้าได้\n • ตรวจสอบว่าหมายเลขโทรศัพท์ของคุณสามารถรับสายโทรเข้าได้\n • ตรวจสอบว่าคุณใส่หมายเลขโทรศัพท์ถูกต้อง + + สำหรับข้อมูลเพิ่มเติม โปรดทำตามขั้นตอนการแก้ไขปัญหาที่นี่หรือติดต่อฝ่ายสนับสนุน + + ขั้นตอนการแก้ไขปัญหาที่นี่ + + ติดต่อฝ่ายสนับสนุน เปิดกุญแจลงทะเบียนหรือไม่? @@ -1880,13 +1928,17 @@ คุณแลกรับโล่แล้ว - แสดงความรู้สึกต่อสตอรี่ของคุณด้วย %1$s + แสดงความรู้สึก %1$s ต่อสตอรี่ของคุณ - คุณแสดงความรู้สึกต่อสตอรี่ด้วย %1$s + คุณแสดงความรู้สึก %1$s ต่อสตอรี่ การชำระเงิน ข้อความที่ตั้งเวลาไว้ + + ประวัติการส่งข้อความของคุณถูกรวมเข้าด้วยกันเรียบร้อยแล้ว + + %1$s เป็นหมายเลขโทรศัพท์ของ %2$s ปรับรุ่น Molly @@ -2015,14 +2067,16 @@ SMS ที่ไม่ปลอดภัย %1$s %2$s ผู้ติดต่อ - ตอบสนอง %1$s ต่อ: \"%2$s\" - ตอบสนอง %1$s ต่อวิดีโอของคุณ - ตอบสนอง %1$s ต่อรูปภาพของคุณ - %1$s ที่ตอบสนองกับ GIF ของคุณ - ตอบสนอง %1$s ต่อแฟ้มของคุณ - ตอบสนอง %1$s ต่อเสียงของคุณ - ตอบสนอง %1$s กับสื่อที่ดูได้ครั้งเดียวของคุณ - ตอบสนอง %1$s ต่อสติกเกอร์ของคุณ + แสดงความรู้สึก %1$s ต่อ: \"%2$s\" + แสดงความรู้สึก %1$s ต่อวิดีโอของคุณ + แสดงความรู้สึก %1$s ต่อรูปภาพของคุณ + แสดงความรู้สึก %1$s ต่อ GIF ของคุณ + แสดงความรู้สึก %1$s ต่อไฟล์ของคุณ + แสดงความรู้สึก %1$s ต่อเสียงของคุณ + แสดงความรู้สึก %1$s ต่อไฟล์สื่อแบบที่ดูได้ครั้งเดียวของคุณ + + แสดงความรู้สึก %1$s ต่อการชำระเงินของคุณ + แสดงความรู้สึก %1$s ต่อสติกเกอร์ของคุณ ข้อความนี้ถูกลบแล้ว ปิดการแจ้งเตือนเมื่อผู้ติดต่อได้เข้าร่วม Signal? คุณสามารถเปิดมันได้อีกครั้งในSignal > Settings > Notifications. @@ -2222,7 +2276,7 @@ เปิดใช้การแจ้งเตือนการโทร ปรับปรุงผู้ติดต่อ - ปิดกั้นคำขอ + บล็อกคำขอ ไม่มีกลุ่มที่เหมือนกันเลย โปรดพิจารณาคำขออย่างระวัง ไม่มีผู้ติดต่อในกลุ่มนี้ โปรดพิจารณาคำขออย่างระวัง ดู @@ -2404,8 +2458,8 @@ ปัญหาการจัดส่ง - ข้อความ สติกเกอร์ การตอบสนอง การตอบรับเมื่ออ่าน หรือสื่อไม่สามารถส่งให้คุณได้จาก %1$s พวกเขาอาจลองส่งให้คุณโดยตรงหรือในกลุ่มแล้ว - ข้อความ สติกเกอร์ การตอบสนอง หรือการตอบรับเมื่ออ่านไม่สามารถส่งให้คุณจาก %1$s + มีข้อความ สติกเกอร์ การแสดงความรู้สึก หรือการแจ้งว่าอ่านแล้วจาก %1$s ที่ไม่สามารถส่งถึงคุณได้ ผู้ใช้คนนี้อาจลองส่งให้คุณโดยตรงหรือส่งเข้ากลุ่มแล้ว + มีข้อความ สติกเกอร์ การแสดงความรู้สึก หรือการแจ้งว่าอ่านแล้วจาก %1$s ที่ไม่สามารถส่งถึงคุณได้ ชื่อ (ต้องมี) @@ -2472,7 +2526,7 @@ รอดำเนินการ - ส่งถึง + ส่งแล้ว: ส่งจาก ส่งถึงแล้วที่ อ่านโดย @@ -2591,9 +2645,9 @@ ใช้รูปภาพของรายชื่อผู้ติดต่อปัจจุบัน แสดงรูปภาพผู้ติดต่อจากรายชื่อผู้ติดต่อปัจจุบันหากสามารถทำได้ - เก็บแชตที่ปิดเสียงถาวร + เก็บแชทที่ปิดเสียงถาวร - แชตที่ปิดเสียงไว้ในที่เก็บถาวรจะยังคงเก็บถาวรอยู่เมื่อมีข้อความใหม่มาถึง + แชทที่ปิดเสียงไว้ในที่เก็บถาวรจะยังคงเก็บถาวรอยู่เมื่อมีข้อความใหม่มาถึง กำลังสร้างภาพตัวอย่างลิงก์ เรียกภาพตัวอย่างลิงก์โดยตรงจากเว็บไซต์สำหรับข้อความที่คุณส่ง เปลี่ยนวลีรหัสผ่าน @@ -2887,7 +2941,7 @@ การชำระเงินที่ส่งแล้ว การชำระเงินที่ได้รับ การชำระเงินสมบูรณ์ %1$s - หมายเลขบล็อค + บล็อกหมายเลข โอน @@ -2972,7 +3026,7 @@ ข้อความใหม่ถึง… - ปิดกั้นผู้ใช้ + บล็อกผู้ใช้ เพิ่มเข้ากลุ่ม @@ -3043,7 +3097,7 @@ - เลิกปิดเสียง + เปิดเสียง ปิดเสียงการแจ้งเตือน @@ -3207,6 +3261,8 @@ ใส่รหัส PIN ของคุณ ใส่รหัส PIN ที่คุณสร้างขึ้นสำหรับบัญชีของคุณ รหัสนี้แตกต่างจากรหัสตรวจยืนยันทาง SMS + + ใส่ PIN ที่คุณสร้างไว้สำหรับบัญชีของคุณ ใส่รหัส PIN ด้วยตัวอักษรและตัวเลข ใส่รหัส PIN ด้วยตัวเลข รหัส PIN ไม่ถูกต้อง ลองอีกครั้ง @@ -3305,7 +3361,10 @@ ข้อมูลสำรองของคุณมีไฟล์ขนาดใหญ่ที่ไม่สามารถเก็บสำรองได้ กรุณาลบไฟล์ดังกล่าวและสร้างข้อมูลสำรองใหม่ แตะเพื่อจัดการข้อมูลสำรอง หมายเลขไม่ถูกต้องหรือเปล่า + โทรหาฉัน (%1$02d:%2$02d) + + ส่งรหัสอีกครั้งใน (%1$02d:%2$02d) ติดต่อทีมซัพพอร์ตของ Signal การลงทะเบียน Signal - รหัสตรวจยืนยันสำหรับ Android รหัสไม่ถูกต้อง @@ -3313,6 +3372,18 @@ ไม่ทราบ ดูหมายเลขโทรศัพท์ของฉัน ค้นหาฉันด้วยหมายเลขโทรศัพท์ + + หมายเลขโทรศัพท์ + + เลือกว่าจะให้ใครเห็นหมายเลขโทรศัพท์ของคุณและใช้หมายเลขโทรศัพท์นั้นติดต่อคุณบน Molly ได้ + + คนที่สามารถเห็นหมายเลขโทรศัพท์ของฉัน + + จะไม่มีใครเห็นหมายเลขโทรศัพท์ของคุณบน Molly + + คนที่สามารถใช้หมายเลขโทรศัพท์ค้นหาฉันได้ + + ผู้ใช้และกลุ่มที่คุณส่งข้อความหาจะเห็นหมายเลขโทรศัพท์ของคุณ และคนที่มีหมายเลขโทรศัพท์ของคุณอยู่ในรายชื่อผู้ติดต่อบนโทรศัพท์ก็จะสามารถดูหมายเลขของคุณบน Molly ได้ด้วยเช่นกัน ทุกคน ผู้ติดต่อของฉัน คนที่คุณไม่รู้จัก @@ -3469,8 +3540,8 @@ - ปิดกั้น - เลิกปิดกั้น + บล็อก + เลิกบล็อก เพิ่มลงในรายชื่อผู้ติดต่อ ไม่พบแอปที่เปิดรายชื่อผู้ติดต่อได้ @@ -3522,9 +3593,9 @@ %1$s/%2$s - \"%1$s\" ถูกปิดกั้นแล้ว - ล้มเหลวในการปิดกั้น \"%1$s\" - \"%1$s\" ถูกเลิกปิดกั้นแล้ว + บล็อก \"%1$s\" แล้ว + บล็อก \"%1$s\" ไม่สำเร็จ + เลิกบล็อก \"%1$s\" แล้ว พิจารณาสมาชิกกลุ่ม @@ -3549,7 +3620,7 @@ ผู้ติดต่อของคุณ ลบจากกลุ่ม ปรับปรุงผู้ติดต่อ - ปิดกั้น + บล็อก ลบ เพิ่งพึ่งเปลี่ยนชื่อโปรไฟล์ของเธอจาก %1$s เป็น %2$s @@ -3572,17 +3643,17 @@ Wi-Fi สัญญาณอ่อน กำลังใช้อินเทอร์เน็ตมือถือ - การลบบัญชีของคุณจะส่งผลให้: + การลบบัญชีของคุณจะเป็นการ: ป้อนหมายเลขโทรศัพท์ของคุณ ลบบัญชี ลบข้อมูลบัญชีและรูปโปรไฟล์ของคุณ - ลบข้อความของคุณทั้งหมด + ลบข้อความทั้งหมดของคุณ ลบ %1$s ในบัญชีชำระเงินของคุณ ไม่มีรหัสประเทศระบุไว้ ไม่มีหมายเลขระบุไว้ หมายเลขโทรศัพท์ที่คุณป้อนไม่ตรงกับหมายเลขในบัญชีคุณ คุณแน่ใจหรือไม่ว่าต้องการลบบัญชี - การกระทำนี้จะลบบัญชี Signal ของคุณและรีเซ็ตแอปใหม่ โดยแอปจะปิดลงหลังจากที่กระบวนการนี้เสร็จสิ้น + การดำเนินการนี้จะลบบัญชี Signal ของคุณและรีเซ็ตแอปใหม่ โดยแอปจะปิดลงหลังจากที่กระบวนการนี้เสร็จสิ้น ลบข้อมูลในเครื่องไม่สำเร็จ คุณสามารถล้างข้อมูลเองได้ในการตั้งค่าแอปพลิเคชันของระบบ การตั้งค่าการเริ่มแอป @@ -3906,12 +3977,12 @@ สร้างโปรไฟล์ - ถูกปิดกั้น + ถูกบล็อก %1$d ผู้ติดต่อ การส่งข้อความ ข้อความที่ลบตัวเอง ความปลอดภัยของแอป - ไม่อนุญาตให้จับภาพหน้าจอในรายการล่าสุดและภายในแอป + ไม่อนุญาตให้ถ่ายภาพหน้าจอในรายการสนทนาล่าสุดและภายในแอป Signal ส่งข้อความและโทร ถ่ายทอดการโทร และผู้ส่งที่ถูกปกปิด ตัวจับเวลาค่าเริ่มต้นสำหรับแชตใหม่ ตั้งค่าเริ่มต้นของตัวจับเวลาข้อความหายไปสำหรับแชตใหม่ที่คุณเริ่มต้น @@ -4007,7 +4078,7 @@ ไม่ใช่ตอนนี้ - ปรับแต่งการตอบสนอง + ปรับแต่งค่าการแสดงความรู้สึก แตะเพื่อเปลี่ยนอีโมจิ รีเซ็ต บันทึก @@ -4076,10 +4147,10 @@ รายละเอียดผู้ติดต่อ ดูหมายเลขความปลอดภัย - ปิดกั้น - ปิดกั้นกลุ่ม - เลิกปิดกั้น - เลิกปิดกั้นกลุ่ม + บล็อก + บล็อกกลุ่ม + เลิกบล็อก + เลิกบล็อกกลุ่ม เพิ่มเข้ากลุ่ม ดูทั้งหมด เพิ่มสมาชิก @@ -4089,7 +4160,7 @@ เพิ่มเป็นผู้ติดต่อ เปิดเสียง ปิดเสียงการสนทนาจนถึง %1$s - ปิดเสียงการสนทนาตลอดไป + ปิดเสียงการสนทนาตลอด คัดลอกหมายเลขโทรศัพท์ไปยังคลิปบอร์ดแล้ว หมายเลขโทรศัพท์ รับโล่ติดโปรไฟล์ด้วยการสนับสนุน Signal แตะที่โล่เพื่อเรียนรู้เพิ่มเติม @@ -4105,8 +4176,8 @@ ใครที่ส่งข้อความได้บ้าง? - ปิดการแจ้งเตือน - ไม่ปิดเสียง + ปิดเสียงการแจ้งเตือน + ไม่ได้ปิดเสียงอยู่ การกล่าวถึง แจ้งเตือนตลอด ไม่ต้องแจ้งเตือน @@ -4135,7 +4206,7 @@ ลบ - ปิดกั้น + บล็อก ลบ %1$s หรือไม่ @@ -4227,7 +4298,7 @@ เพิ่มไปยังสตอรี่ เพิ่มข้อความ เพิ่มการตอบกลับ - ส่งไปยัง + ส่งไปให้ ดูข้อความครั้งเดียว มีรายการที่ใหญ่เกินไปอย่างน้อยหนึ่งรายการ มีรายการที่ไม่ถูกต้องอย่างน้อยหนึ่งรายการ @@ -4350,7 +4421,7 @@ ยกเลิกการบริจาครายเดือนแล้ว โล่ Boost ของคุณหมดอายุแล้ว และจะไม่แสดงบนโปรไฟล์คุณอีกต่อไป - คุณสามารถเปิดใช้งานโล่ Boost ได้ใหม่เป็นเวลา 30 วันด้วยการบริจาคหนึ่งครั้ง + คุณสามารถเปิดใช้งานโล่บูสต์ได้ใหม่เป็นเวลา 30 วันด้วยการบริจาคหนึ่งครั้ง คุณสามารถใช้ Signal ต่อไปได้ แต่หากต้องการสนับสนุนเทคโนโลยีที่สร้างมาเพื่อคุณ ให้ลองพิจารณาการเป็นผู้สนับสนุนด้วยการบริจาครายเดือน เป็นผู้สนับสนุน @@ -4405,7 +4476,7 @@ ไม่สามารถส่งการบริจาคได้เนื่องจากข้อผิดพลาดของเครือข่าย ตรวจสอบการเชื่อมต่อแล้วลองอีกครั้ง - การบริจาคในนามของ %1$s + การบริจาคในนามของ %1$s %1$s บริจาคให้ Signal ในนามของคุณ @@ -4796,9 +4867,9 @@ เลือกว่าจะให้ใครเห็นสตอรี่ของคุณบ้าง การเปลี่ยนแปลงจะไม่ส่งผลต่อสตอรี่ที่ส่งไปแล้ว - การตอบกลับ & การตอบสนอง + การตอบกลับและการแสดงความรู้สึก - อนุญาตการตอบกลับ & การตอบสนอง + อนุญาตการตอบกลับและการแสดงความรู้สึก ให้คนที่ดูสตอรี่ของคุณแสดงความรู้สึกและตอบกลับได้ @@ -4975,7 +5046,7 @@ ยืนยันการบริจาค - ส่งไปยัง + ส่งไปให้ ผู้รับจะได้รับการแจ้งเตือนในข้อความส่วนตัวว่ามีการบริจาค เพิ่มข้อความของคุณที่ด้านล่างนี้ @@ -5476,5 +5547,15 @@ ลบชื่อผู้ใช้ + + + ชม. + + นาที + + ตั้ง + + เวลาขั้นต่ำก่อนที่การล็อกหน้าจอจะเริ่มทำงานคือ 1 นาที + diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 54ce6749c3..4ddc5463b3 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -14,6 +14,7 @@ + Oo Hindi @@ -97,8 +98,8 @@ Blocked users - I-add ang blocked user - Ang mga user na naka-block ay hindi magagawang tawagan ka o padalhan ka ng mga mensahe. + Mag-add ng blocked user + Hindi makakatawag o makakapag-send sa \'yo ng messages ang blocked users. Walang blocked users Gusto mo bang i-block ang user na ito? Hindi na makakatawag o makakapag-send ng messages si \"%1$s\" sa\'yo. @@ -147,16 +148,16 @@ Pwede niyo nang i-message at tawagan ang isa\'t isa at ang pangalan at photo mo ay ibabahagi rin sa kanya. Pwede niyo nang i-message ang isa\'t isa. - Hindi makakatawag o makakapag-send ng messages ang blocked users sa\'yo. - Hindi ka mapapadalhan ng messages ng mga taong binlock mo. + Hindi makakatawag o makakapag-send ng messages sa \'yo ang mga binlock mong tao. + Hindi makakapag-send ng messages sa \'yo ang mga binlock mong tao. I-block ang pag-receive ng updates at news mula sa Signal. I-resume ang pag-receive ng updates at news mula sa Signal. - I-unblock ang %1$s? + Gusto mo bang i-unblock ang %1$s? I-block I-block at Umalis - I-report ang spam at i-block + I-report bilang spam at i-block Ngayong araw @@ -366,7 +367,7 @@ Error sending media - Reported bilang spam at naka-block na. + Ni-report bilang spam at na-block na. Disabled sa ngayon ang SMS messaging. Pwede mong i-export ang iyong messages sa ibang app sa phone mo. @@ -488,29 +489,29 @@ Unread - Pin - Pin + I-pin + I-pin - Unpin - Unpin + I-unpin + I-unpin - Mute - Mute + I-mute + I-mute - Unmute - Unmute + I-unmute + I-unmute - Select + Piliin - Archive - Archive + I-archive + I-archive - Unarchive - Unarchive + I-unarchive + I-unarchive Burahin @@ -542,6 +543,15 @@ +%1$d + + I-relink ang devices mo + + Na-unlink ang devices na inilagay mo noong na-unregister ang iyong device. Pumunta sa Settings para i-relink ang anumang devices. + + Buksan ang settings + + Mamaya na lang + Piliin ang members @@ -935,7 +945,7 @@ I-notify ako sa Mentions - Gusto mo bang makatanggap ng notifications kapag ikaw ay na-mention sa muted chats? + Gusto mo bang makatanggap ng notifications kapag na-mention ka sa muted chats? I-notify ako palagi \'Wag akong i-notify @@ -953,6 +963,16 @@ Nagawa na ang username Nakopya na ang username + + Hindi mabura ang username. Subukan ulit mamaya. + + Nabura na ang username + + + + Nagkaroon ng problema sa username mo, hindi na ito naka-assign sa iyong account. Maaari mong subukang ilagay ito ulit o pumili ng bago. + + Ayusin @@ -1156,8 +1176,8 @@ Bagong grupo Imbitahan ang mga kaibigan Gamitin ang SMS - Hitsura - Mag-add ng photo + Chat colors + Mag-add ng profile photo Replies @@ -1472,10 +1492,10 @@ I-unblock Hayaang mag-message si %1$s sa\'yo at i-share sa kanya ang pangalan at photo mo? Hindi niya malalaman na nakita mo ang kanyang message hanggang sa i-accept mo ito. - Hayaang mag-message si %1$s sa\'yo at i-share sa kanya ang pangalan at photo mo? Hindi niya malalaman na nakita mo ang kanyang message hanggang sa i-unblock mo siya. + Hayaang mag-message si %1$s sa \'yo at i-share sa kanya ang pangalan at photo mo? Hindi ka makakatanggap ng anumang message hanggang sa i-unblock mo siya. - Hayaang mag-message si %1$s sa\'yo? Hindi niya malalaman na nakita mo ang kanyang message hanggang sa i-unblock mo siya. - Gusto mo bang makakuha ng updates at news mula kay %1$s? Hindi ka makakatanggap ng anumang updates hanggang sa i-unblock mo siya. + Hayaang mag-message si %1$s sa\'yo? Hindi ka makakatanggap ng anumang message hanggang sa i-unblock mo siya. + Gusto mo bang makakuha ng updates at news mula kay %1$s? Hindi ka makakatanggap ng anumang update hanggang sa i-unblock mo siya. Gusto mo bang ipagpatuloy ang iyong conversation sa group na ito at i-share sa members nito ang pangalan at photo mo? I-upgrade ang group na ito para ma-activate ang bagong features gaya ng @mentions at admins. Ang members na hindi nai-share ang kanilang pangalan at photo sa group na ito ay iimbitahang mag-join. Ang Legacy Group na ito\'y hindi na pwedeng gamitin dahil masyado na itong malaki. Ang maximum group size ay %1$d. @@ -1483,7 +1503,7 @@ Gusto mo bang mag-join sa group na ito at i-share sa members ang pangalan at photo mo? Hindi nila malalaman na nakita mo ang kanilang messages hanggang sa i-accept mo ito. Gusto mo bang mag-join sa group na ito at i-share sa members ang pangalan at photo mo? Hindi mo makikita ang kanilang messages hanggang sa i-accept mo ito. Gusto mo bang mag-join sa group na ito? Hindi nila malalaman na nakita mo ang kanilang messages hanggang sa i-accept mo ito. - Gusto mo bang i-unblock ang group na ito at i-share sa members ang pangalan at photo mo? Wala kang matatanggap na anumang mensahe hangga\'t hindi mo sila ina-unblock. + Gusto mo bang i-unblock ang group na ito at i-share sa members ang pangalan at photo mo? Hindi ka makakatanggap ng anumang message hanggang sa i-unblock mo sila. Tingnan Kasapi ng %1$s @@ -1584,9 +1604,20 @@ Gumawa ng bagong PIN + + I-send ang SMS code + + Signal Registration - Kailangan ng Tulong sa pag-register ulit ng PIN sa Android + + Ang iyong PIN ay isang %1$d+ digit code na ginawa mo at maaari itong numeric o alphanumeric.\n\nKung hindi mo maalala ang iyong PIN, maaari kang gumawa ng bago. + + Kung hindi mo maalala ang iyong PIN, maaari kang gumawa ng bago. + + Naubusan ka na ng PIN guesses, pero maaari mo pa ring ma-access ang iyong Signal account sa pamamagitan ng paggawa ng bagong PIN. + Babala - Kung idi-disable mo ang iyong PIN, mawawala ang lahat ng data kapag ika\'y nag-register ulit sa Signal maliban nalang kung manually kang nag-back up and restore. Hindi mo pwedeng i-turn on ang Registration Lock habang naka-disable ang PIN. + Kapag dinisable mo ang iyong PIN, mawawala ang lahat ng data kapag nag-register ka ulit sa Signal maliban na lang kung manually kang nag-back up at restore. Hindi mo pwedeng i-on ang Registration Lock habang naka-disable ang PIN. I-disable ang PIN @@ -1713,7 +1744,7 @@ I-unmute - I-Mute + I-mute Ring @@ -1726,12 +1757,12 @@ - Si %1$s ay naka-block + Naka-block si %1$s Mas marami pang Info Hindi mo mare-receive ang kanyang audio o video at hindi niya rin mare-receive ang audio o video na galing sa\'yo. Hindi ma-receive ang audio at video galing kay %1$s Hindi ma-receive ang audio at video galing kay %1$s - Maaaring ito ay dahil hindi niya na-verify ang iyong safety number change, may problema sa kanyang device, o naka-block ka sa kanya. + Maaaring ito ay dahil hindi niya na-verify ang iyong safety number change, may problema sa kanyang device, o binlock ka n\'ya. I-swipe para ma-view ang screen share @@ -1768,11 +1799,18 @@ Kinakailangan ng Signal ng pahintulot para ma-access ang contacts at media para maka-connect ka sa friends mo at makapag-send ng message. Ina-upload ang contacts mo gamit ang private contact discovery ng Signal, kung saan end-to-end encrypted ang mga ito at hindi kailanman makikita ng Signal service. Kinakailangan ng Signal ng pahintulot para ma-access ang contacts para maka-connect ka sa friends mo. Ina-upload ang contacts mo gamit ang private contact discovery ng Signal, kung saan end-to-end encrypted ang mga ito at hindi kailanman makikita ng Signal service. Nakagawa ka ng napakaraming pagtatangkang irehistro ang numerong ito. Pakiusap na subukang muli kinalaunan. + + Masyadong maraming beses mo nang sinubukang i-register ang number na ito. Subukan ulit pagkatapos ng %1$s. Hindi makakonekta sa serbisyo. Pakisuri ang koneksyon ng network at subukang muli. Non-standard number format Ang number na nilagay mo (%1$s) ay mukhang isang non-standard format.\n\nDid you mean %2$s? Molly Android - Phone Number Format + Call requested + + Nag-request ng SMS + + Nag-request ng verification code Ikaw ngayon ay %1$d layong-hakbang na lang sa pagsusumite ng debug log. %1$d hakbang ka pa para makapagsumite ng debug log. @@ -1792,6 +1830,16 @@ Tawag Verification Code I-resend ang Code + + Nagkakaproblema ka ba sa pag-register? + + • Siguraduhing may signal ang iyong phone para maka-receive ka ng verification code sa SMS o tawag\n • Kumpirmahing nakaka-receive ka ng tawag sa number mo\n • I-check kung tama ang phone number na inilagay mo. + + Para sa iba pang impormasyon, sundin ang sumusunod na troubleshooting steps o Kontakin ang Support + + ang sumusunod na troubleshooting steps + + Kontakin ang Support Paganahin ang Registration Lock? @@ -1953,11 +2001,15 @@ Nag-react ng %1$s sa story mo - Nag-react ng %1$s sa story nila + Nag-react ng %1$s sa story niya Payment Scheduled message + + Na-merge na ang iyong message history + + Kay %2$s ang %1$s Update sa Molly @@ -2030,7 +2082,7 @@ Ang MMS na mensahe ay naka-encrypt para sa hindi-umiiral na sesyon - Patahimikin ang mga notipikasyon + I-mute ang notifications Isinasagawa ang pag-i-import @@ -2089,11 +2141,13 @@ Kontak Nag-react ng %1$s sa: \"%2$s\". Nag-react ng %1$s sa iyong video. - Nag-react ng %1$s sa iyong imahe. - Reacted %1$s to your GIF. + Nag-react ng %1$s sa iyong image. + Nag-react ng %1$s sa iyong GIF. Nag-react ng %1$s sa iyong file. Nag-react ng %1$s sa iyong audio. - Reacted %1$s to your view-once media. + Nag-react ng %1$s sa iyong view-once media. + + Nag-react ng %1$s sa payment mo. Nag-react ng %1$s sa iyong sticker. Nabura na ang message na ito. @@ -2487,8 +2541,8 @@ Delivery Issue - Ang message, sticker, reaction, o read receipt mula kay %1$s ay hindi ma-deliver sa\'yo. Maaaring sinubukan niyang i-send ito sa\'yo directly o sa isang group. - Ang message, sticker, reaction, o read receipt mula kay %1$s ay hindi ma-deliver sa\'yo. + Hindi ma-deliver sa \'yo ang message, sticker, reaction, o read receipt mula kay %1$s. Maaaring sinubukan niyang i-send ito sa \'yo directly o sa isang group. + Hindi ma-deliver sa \'yo ang message, sticker, reaction, o read receipt mula kay %1$s. Pangalan (kailangan) @@ -2555,7 +2609,7 @@ Pending - Sent to + Sinend kay Sent from Delivered to Read by @@ -2635,10 +2689,10 @@ Gamitin ang default Gumamit ng custom - Patahimikin nang 1 oras - I-mute for 8 hours - Patahimikin nang 1 araw - Patahimikin nang 7 araw + I-mute sa loob ng 1 oras + I-mute sa loob ng 8 oras + I-mute sa loob ng 1 araw + I-mute sa loob ng 7 araw Always Default ng settings @@ -2675,7 +2729,7 @@ Use address book photos I-display ang contact photos mula sa address book mo kung available - Panatilihin sa Archive ang Muted Chats + Panatilihinng Naka-archive ang Muted Chats Mananatili sa archive ang muted chats kahit may bagong message na dumating. Mag-generate ng link previews @@ -2971,7 +3025,7 @@ Sent payment Received payment Payment completed%1$s - I-block ang number + Block number Transfer @@ -3128,10 +3182,10 @@ - Huwag nang patahimikin + I-unmute - Patahimikin ang mga notipikasyon + I-mute ang notifications Group settings @@ -3295,6 +3349,8 @@ Ilagay ang iyong PIN Ipasok ang PIN na iyong nilikha para sa iyong account. Ito ay iba sa iyong SMS verification code. + + Ilagay ang ginawa mong PIN para sa account na ito. Ilagay ang alphanumeric na PIN Ilagay ang numeric na PIN Hindi tama ang PIN. Subukang muli. @@ -3398,7 +3454,10 @@ Ang backup mo ay may lamang masyadong malaking file na hindi ma-back up. Mangyaring burahin ito at gumawa ng bagong backup. I-tap to manage backups. Maling number? + Tawagan ako (%1$02d:%2$02d) + + I-resend ang Code (%1$02d:%2$02d) Makipag-ugnayan sa Suporta ng Signal Pagpaparehistro sa Signal - Verification Code para sa Android Maling kodigo @@ -3406,6 +3465,18 @@ Hindi alam Ipakita ang aking phone number Find me by phone number + + Phone number + + Piliin kung sinong pwedeng makakita ng phone number mo at kung sinong pwedeng kumontak sa \'yo sa Molly gamit ito. + + Sinong makakakita ng number ko + + Walang makakakita ng phone number mo sa Molly + + Sinong makakahanap sa \'kin gamit ang number ko + + Makikita ng lahat ng tao at groups na mine-message mo ang iyong phone number. Makikita ka sa Molly bilang isang contact ng sinumang may phone number mo sa kanilang contacts. Everyone My contacts Nobody @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" has been blocked. - Failed to block \"%1$s\" - \"%1$s\" has been unblocked. + Binlock si \"%1$s\". + Hindi na-block si \"%1$s\" + In-unblock si \"%1$s\". I-review ang Members @@ -3667,7 +3738,7 @@ Lumipat sa data connection dahil unstable ang Wi-Fi connection. - Ang pag-delete ng iyong account ay magreresulta sa: + Ang pagbura ng iyong account ay magreresulta sa: Ilagay ang numero ng iyong telepono Burahin ang account Burahin ang iyong contact info at profile photo @@ -3677,7 +3748,7 @@ Walang specified na number Ang nilagay mong phone number ay hindi match sa number ng iyong account. Sigurado ka bang nais mong burahin ang iyong account? - Buburahin nito ang iyong Signal account at mare-reset ang app. Sasara ang app pagkatapos nito. + Buburahin nito ang iyong Signal account at ire-reset ang app. Magsasara ang app pagkatapos ng proseso. Hindi mabura ang local data. Pwede mo itong i-clear manually sa system application settings. I-launch ang App Settings @@ -3784,12 +3855,12 @@ I-deactivate ang Wallet Your balance - It\'s recommended that you transfer your funds to another wallet address bago mo i-deactivate ang payments. If you choose not to transfer your funds now, mananatili ito sa iyong wallet linked to Molly kapag nag-reactivate ka ng payments. + Inirerekomenda naming i-transfer mo muna ang iyong funds sa iba pang wallet address bago mo i-deactivate ang payments. Kapag pinili mong hindi i-transfer ang iyong funds ngayon, mananatili ito sa iyong wallet na naka-link sa Molly kapag ni-reactivate mo ang payments. I-transfer ang remaining balance Deactivate without transferring I-deactivate Gusto mo bang mag-deactivate without transferring? - Ang balance mo ay mananatili sa iyong wallet linked to Molly kapag nag-reactivate ka ng payments. + Mananatili ang balance mo sa iyong wallet na naka-link sa Molly kapag nag-reactivate ka ng payments. Error deactivating wallet. @@ -4008,7 +4079,7 @@ Messaging Mga naglalahong mensahe App security - I-block ang mga screenshot sa listahan ng mga bago at sa loob ng app + I-block ang screenshots sa recents list at sa loob ng app Signal messages and calls, always relay calls, and sealed sender Default timer para sa new chats Mag-set ng default disappearing message timer para sa lahat ng new chats na sinimulan mo. @@ -4165,7 +4236,7 @@ Tawag - I-Mute + I-mute Muted @@ -4187,8 +4258,8 @@ Link ng group I-add as a contact I-unmute - Conversation muted until %1$s - Conversation muted forever + Naka-mute ang chat hanggang %1$s + Forever na naka-mute ang chat Copied phone number to clipboard. Numero ng telepono Makakuha ng badges para sa profile mo sa pamamagitan ng pagsuporta sa Signal. I-tap ang isang badge para matuto pa tungkol dito. @@ -4204,7 +4275,7 @@ Sinong pwedeng mag-send ng messages? - Patahimikin ang mga notipikasyon + I-mute ang notifications Hindi naka-mute Mentions Always notify @@ -4453,7 +4524,7 @@ Kinancel ang Monthly Donation Expired na ang iyong Boost badge at hindi na ito makikita sa profile mo. - Pwede mong i-reactivate ang iyong Boost badge nang dagdag na 30 araw sa pamamagitan ng one-time contribution. + Pwede mong i-reactivate ang iyong Boost badge nang karagdagang 30 araw sa pamamagitan ng one-time contribution. Pwede mo pa ring gamitin ang Signal app pero para masuportahan ang technology na ginawa para sa\'yo, inaanyayahan ka naming magbigay ng monthly donation. Become a Sustainer @@ -4508,7 +4579,7 @@ Hindi maipadala ang donation mo dahil sa network error. I-check ang iyong connection at subukan ulit. - Donation para kay %1$s + Donation sa pangalan ni %1$s Nag-donate sa Signal si %1$s para sa iyo @@ -4907,9 +4978,9 @@ Replies & reactions - Allow replies & reactions + Payagan ang replies & reactions - Payagang mag-react at mag-reply ang mga taong nag-view ng story mo + Payagang mag-react at mag-reply ang mga taong pwedeng mag-view ng story mo Signal Connections @@ -5059,7 +5130,7 @@ Nag-react ka sa story ni %1$s - Nag-react sa iyong story + Nag-react sa story mo Nag-react sa isang story @@ -5601,5 +5672,15 @@ Burahin ang username + + + h + + m + + I-set + + Ang minimum time bago ma-apply ang screen lock ay 1 minute. + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 771f22bd5f..d8e2e3ebd6 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -14,6 +14,7 @@ + Evet Hayır @@ -97,10 +98,10 @@ Engellenen kullanıcılar - Engellenmiş kullanıcılara ekle - Engellenen kullanıcılar sizi arayamaz veya size ileti gönderemez. - Hiç engellenmiş kullanıcı yok - Kişiyi engelle? + Engellenecek kullanıcı ekle + Engellenen kullanıcılar seni arayamaz veya sana mesaj gönderemez. + Engellenen kullanıcı yok + Kullanıcıyı engelle? \"%1$s\" sizi arayamayacak veya size ileti gönderemeyecek. Engelle @@ -138,7 +139,7 @@ Devam Et - %1$s grubunu engelle ve ayrıl? + %1$s grubundan ayrıl ve engelle? %1$s engellensin mi? Bu grubun iletilerini veya güncellemelerini artık almayacaksınız, ve grup üyeleri sizi tekrardan gruba ekleyemeyecek. Grup üyeleri sizi bu gruba ekleyemeyecekler. @@ -147,16 +148,16 @@ Birbirinizi arayıp ileti gönderebileceksiniz ve adınızla fotoğrafınız onlarla paylaşılacak. Birbirinize mesaj atabileceksiniz. - Engellenen kişiler sizi arayamayacaklar veya ileti gönderemeyecekler. - Engellenen kişiler size iletiler gönderemeyecek. + Engelli kişiler seni arayamaz veya sana mesaj gönderemez. + Engelli kişiler sana mesaj gönderemez. - Signal\'den güncellemeler ve haberler almayı engelle. + Signal\'den güncelleme ve haber almayı engelle. Signal\'den güncellemeler ve haberler almayı yeniden başlat. %1$s engeli kaldırılsın mı? Engelle Engelle ve Ayrıl - Gereksiz olarak bildir ve engelle + İstenmeyen ileti olarak bildir ve engelle Bugün @@ -366,7 +367,7 @@ İçerik gönderilemedi - Gereksiz olarak bildirildi ve engellendi + İstenmeyen ileti olarak bildirildi ve engellendi. SMS ile mesajlaşma şu anda devre dışı. Mesajlarını telefonundaki başka bir uygulamaya aktarabilirsin. @@ -492,8 +493,8 @@ Sabitle - Kaldır - Kaldır + Sabitlenenlerden kaldır + Sabitlenenlerden kaldır Sessize al @@ -509,8 +510,8 @@ Arşivle - Çıkart - Çıkart + Arşivden çıkart + Arşivden çıkart Sil @@ -542,6 +543,15 @@ +%1$d + + Cihazlarını yeniden bağla + + Cihazının kaydı kaldırıldığında eklediğin cihazların bağlantısı kaldırıldı. Herhangi bir cihazı yeniden bağlamak için Ayarlar\'a git. + + Ayarları aç + + Sonra + Üyeleri seçin @@ -935,7 +945,7 @@ Bahsedilmeleri bana bildir - Sessize alınan konuşmalarda sizden bahsedildiğinde bildirim gönderilsin mi? + Sessize alınan konuşmalarda senden bahsedildiğinde bildirim gönderilsin mi? Her zaman bana bildir Bana bildirme @@ -953,6 +963,16 @@ Kullanıcı adı oluşturuldu Kullanıcı adı kopyalandı + + Kullanıcı adı silinemedi. Daha sonra tekrar dene. + + Kullanıcı adı silindi + + + + Kullanıcı adınla ilgili bir sorun oluştu, hesabına artık atanmamış olarak görünüyor. Tekrar deneyebilir ve ayarlayabilir veya yeni bir tane seçebilirsin. + + Şimdi düzelt @@ -1156,8 +1176,8 @@ Yeni grup Arkadaşlarını davet et SMS kullan - Görünüm - Fotoğraf ekleyin + Sohbet renkleri + Profil fotoğrafı ekle Yanıtlar @@ -1472,10 +1492,10 @@ Engeli kaldır %1$s kişisinin sizinle yazışmasına ve adınızla fotoğrafınızı görmesine izin vermek istiyor musunuz? Kabul edene kadar iletilerini gördüğünüzü bilmeyecekler. - %1$s kişisinin sizinle yazışmasına ve adınızla fotoğrafınızı görmesine izin vermek istiyor musunuz? Engeli kaldırana kadar hiçbir ileti almayacaksınız. + %1$s kişisinin seninle yazışmasına ve adınla fotoğrafını paylaşmasına izin vermek istiyor musun? Engelini kaldırana kadar hiçbir ileti almayacaksın. - %1$s size ileti gönderebilsin mi? Engeli kaldırana kadar hiçbir ileti almayacaksınız. - %1$s\'ten güncellemeler ve haberler alınsın mı? Engellemeyi kaldırana kadar herhangi bir güncelleme almayacaksınız. + %1$s sana mesaj gönderebilsin mi? Engelini kaldırana kadar hiçbir ileti almayacaksın. + %1$s ile ilgili güncelleme ve haber alınsın mı? Engelini kaldırana kadar herhangi bir güncelleme almayacaksın. Bu grupla konuşmaya devam etmek ve adınızı ve fotoğrafınızı üyeleriyle paylaşmak ister misiniz? Yöneticiler ve bahsedilmeler gibi yeni özellikleri etkinleştirmek için bu grubu yükseltin. Bu gruba resimlerini veya adlarını paylaşmayan kişilere gruba katılma daveti gönderilecektir. Bu Eski Grup çok büyük olduğu için artık kullanılamaz. Maksimum grup boyutu %1$d. @@ -1483,7 +1503,7 @@ Bu gruba katılmak ve adınızla fotoğrafınızı grup üyeleri ile paylaşmak istiyor musunuz? Kabul edene kadar iletilerini gördüğünüzü bilmeyecekler. Bu gruba katılıp adınızı ve fotoğrafınızı üyeleriyle paylaşmak ister misiniz? Kabul edene kadar mesajlarını görmezsiniz. Bu gruba katılınsın mı? Kabul edene kadar iletilerini gördüğünüzü bilmeyecekler. - Bu grubun engelini kaldırmak ve adınızla fotoğrafınızı grup üyeleri ile paylaşmak istiyor musunuz? Engeli kaldırana kadar hiçbir ileti almayacaksınız. + Bu grubun engelini kaldırmak ve adınla fotoğrafını grup üyeleri ile paylaşmak istiyor musun? Engeli kaldırana kadar hiçbir ileti almayacaksın. Görüntüle %1$s grubuna üye @@ -1584,9 +1604,20 @@ Yeni PIN oluştur + + SMS kodu gönder + + Signal Kaydı - Android için PIN\'i yeniden kaydetme konusunda yardıma ihtiyacın mı var + + PIN\'in, sayısal veya alfanumerik karakterleri kullanabileceğin %1$d+ haneli bir koddur.\n\nPIN\'ini hatırlayamıyorsan yeni bir tane oluşturabilirsin. + + PIN\'ini hatırlayamıyorsan yeni bir tane oluşturabilirsin. + + PIN tahminlerin tükendi ancak yine de yeni bir PIN oluşturarak Signal hesabına erişebilirsin. + Uyarı - PIN\'i devre dışı bırakırsanız, elle yedekleyip geri yüklemediğiniz sürece Signal\'i yeniden kaydettiğinizde tüm verilerinizi kaybedersiniz. PIN devre dışı iken Kayıt Kilidini açamazsınız. + PIN\'i devre dışı bırakırsan elle yedekleyip geri yüklemediğin sürece Signal\'i yeniden kaydettiğinde tüm verilerini kaybedersin. PIN devre dışı iken Kayıt Kilidini açamazsın. PIN\'i devre dışı bırak @@ -1731,7 +1762,7 @@ Birbirinizin sesini veya görüntüsünü alamayacaksınız. %1$s kişisinden ses ve görüntü alınamıyor %1$s kişisinden ses ve görüntü alınamıyor - Bunun sebebi, güvenlik numaranızın değişimini doğrulamamaları, cihazlarıyla ilgili bir sorun olması veya sizi engellemesi olabilir. + Bunun sebebi güvenlik numaranın değişimini doğrulamaması, cihazıyla ilgili bir sorun olması veya seni engellemesi olabilir. Ekran paylaşımını görmek için kaydırın @@ -1768,11 +1799,18 @@ Signal, arkadaşlarına bağlanmana yardımcı olmak ve mesaj gönderebilmek için kişilere ve medyaya erişim iznine ihtiyaç duyar. Kişi listen Signal\'in gizli kişi bulma özelliğiyle yüklenir; yani uçtan uca şifrelenir ve Signal hizmeti tarafından hiç görülmez. Signal, arkadaşlarına bağlanmana yardımcı olabilmek için kişilere erişim iznine ihtiyaç duyuyor. Kişilerin, Signal\'in gizli kişi bulma özelliğiyle yüklenir; yani uçtan uca şifrelenir ve Signal hizmeti tarafından hiç görülmez. Bu numarayı kaydettirmek için çok fazla deneme yaptınız. Lütfen daha sonra tekrar deneyiniz. + + Bu numarayı kaydetmek için çok fazla girişimde bulundun. Lütfen %1$s içinde tekrar dene. Hizmete bağlanılamadı. Lütfen ağ bağlantınızı kontrol edip tekrar deneyin. Standart olmayan numara formatı Girdiğin numara (%1$s) standart olmayan bir formatta gibi görünüyor.\n\n%2$s mi demek istedin? Molly Android - Telefon Numarası Formatı + Arama istendi + + SMS istendi + + Doğrulama kodu istendi Artık bir hata ayıklama günlüğü göndermekten %1$d adım uzaktasınız. Artık bir hata ayıklama günlüğü göndermekten %1$d adım uzaktasınız. @@ -1792,6 +1830,16 @@ Ara Doğrulama Kodu Kodu Yeniden Gönder + + Kaydolurken sorun mu yaşıyorsun? + + • Telefonunun SMS\'ini almak veya aramak için hücresel bir sinyali olduğundan emin ol \n • Numaraya bir telefon çağrısı alabileceğini onayla \n • Telefon numaranı doğru girdiğinden emin ol. + + Daha fazla bilgi için lütfen bu sorun giderme adımlarını izle veya Destekle İletişime Geç + + bu sorun giderme adımları + + Destekle İletişime Geç Kayıt Kilidi Açılsın mı? @@ -1951,13 +1999,17 @@ Bir rozet kullandın - Hikayene %1$s tepki verdi + Hikayene %1$s tepkisi verdi - Hikayesine %1$s tepki verdi + Hikayesine %1$s tepkisi verdi Ödeme Planlanmış mesaj + + Mesaj geçmişin birleştirildi + + %1$s numarası %2$s adlı kişiye aittir Molly güncellemesi @@ -2087,14 +2139,16 @@ Şifresiz SMS %1$s %2$s Kişi - \"%2$s\" iletisine karşılık verdi: %1$s - Videonuza karşılık verdi: %1$s - Görüntünüze karşılık verdi: %1$s - GIF\'inize karşılık verdi: %1$s - Dosyanıza karşılık verdi: %1$s - Sesinize karşılık verdi: %1$s - Tek görümlük içeriğinize karşılık verdi: %1$s - Çıkartmanıza karşılık verdi: %1$s + \"%2$s\" iletisine %1$s tepkisi verdi. + Videona %1$s tepkisi verdi. + Resmine %1$s tepkisi verdi. + GIF\'ine %1$s tepkisi verdi. + Dosyana %1$s tepkisi verdi. + Sesine %1$s tepkisi verdi. + Bir kez görüntülenen içeriğine %1$s tepkisi verdi. + + Ödemene %1$s tepkisi verdi. + Çıkartmana %1$s tepkisi verdi. Bu ileti silindi. Kişi katıldı Signal bildirimlerini kapatmak mı istiyorsunuz? Signal > Ayarlar > Bildirimler kısmında tekrar açabilirsiniz. @@ -2487,8 +2541,8 @@ Teslim Sıkıntısı - %1$s tarafından gönderilen bir ileti, çıkartma, tepki veya okundu bilgisi size teslim edilemedi. Size ayrı olarak veya grup içerisinden göndermeye çalışmış olabilirler. - %1$s tarafından gönderilen bir ileti, çıkartma, tepki veya okundu bilgisi size teslim edilemedi. + %1$s tarafından gönderilen bir ileti, çıkartma, tepki veya okundu bilgisi sana teslim edilemedi. Sana doğrudan veya grup içerisinden göndermeye çalışmış olabilir. + %1$s tarafından gönderilen bir ileti, çıkartma, tepki veya okundu bilgisi sana teslim edilemedi. Ad (gerekli) @@ -2555,7 +2609,7 @@ Beklemede - Gönderilen + Gönder: Gönderen İletilen Okuyan @@ -2635,10 +2689,10 @@ Varsayılanı kullan Özelleştirilmiş kullan - 1 saatliğine sustur - 8 saat sustur - 1 günlüğüne sustur - 7 günlüğüne sustur + 1 saatliğine sessize al + 8 saatliğine sessize al + 1 günlüğüne sessize al + 7 günlüğüne sessize al Her zaman Varsayılan ayarlar @@ -3295,6 +3349,8 @@ PIN\'inizi girin Bu hesap için oluşturduğunuz PIN kodunu girin. Bu kod, SMS doğrulama kodunuzdan farklıdır. + + Hesabın için oluşturduğun PIN\'i gir. Alfanumerik PIN\'i Girin Sayısal PIN\'i Girin Yanlış PIN. Tekrar deneyin. @@ -3398,7 +3454,10 @@ Yedekleme işlemi, çok büyük bir dosya içeriyor ve yedeklenemiyor. Lütfen bu dosyayı sil ve yeni bir yedekleme oluştur. Yedekleri yönetmek için dokunun. Yanlış numara? + Beni ara (%1$02d:%2$02d) + + Kodu Yeniden Gönder (%1$02d:%2$02d) Signal Destek Ekibiyle İletişime Geç Signal Kaydı - Android için Doğrulama Kodu Geçersiz kod @@ -3406,6 +3465,18 @@ Bilinmiyor Telefon numaramı görebilir Telefon numaramla beni bulabilir + + Telefon numarası + + Telefon numaranı kimlerin görebileceğini ve seninle kimlerin Molly üzerinden iletişim kurabileceğini seç. + + Numaramı kimler görebilir? + + Hiç kimse telefon numaranı Molly\'de göremeyecek + + Kimler beni numaram ile bulabilir? + + Telefon numaran, ileti gönderdiğin kişiler ve gruplar tarafından görülür. Numaranı kişilerine ekleyen insanlar da seni Molly\'de görebilir. Herkes Kişilerim Hiç kimse @@ -3667,11 +3738,11 @@ Zayıf Wi-Fi. Hücresel veriye geçildi. - Hesabınızı silmek: + Hesabını sildiğinde: Telefon numaranızı girin Hesabı sil - Hesap bilgilerini ve profil resmini silecektir - Tüm iletilerini sil + Hesap bilgilerin ve profil resmin silinir + Tüm iletilerin silinir Ödemeler hesabından %1$s silinir Ülke kodu belirtilmedi Telefon numarası belirtilmedi @@ -3784,12 +3855,12 @@ Cüzdanı Devre Dışı Bırak Bakiyeniz - Ödemeleri devre dışı bırakmadan önce mevcut bakiyenizi başka bir cüzdana aktarmanızı öneriyoruz. Eğer paranızı şimdi aktarmamayı seçerseniz, ödemeleri devre dışı bıraktığınızda Molly\'e bağlı cüzdanınızda kalacaktır. + Ödemeleri devre dışı bırakmadan önce mevcut bakiyeni başka bir cüzdana aktarmanı öneriyoruz. Eğer paranı şimdi aktarmamayı seçersen ödemeleri tekrar etkinleştirmeyi seçersen Molly\'e bağlı cüzdanında kalacaktır. Kalan bakiyeyi aktar Aktarmadan devre dışı bırak Devre dışı bırak Aktarmadan devre dışı bırakılsın mı? - Ödemeleri tekrar etkinleştirmeyi seçerseniz, bakiyeniz Molly\'e bağlı cüzdanınızda kalacaktır. + Ödemeleri tekrar etkinleştirmeyi seçersen bakiyen Molly\'e bağlı cüzdanında kalacaktır. Cüzdanı devre dışı bırakırken bir hata oldu. @@ -4008,7 +4079,7 @@ Yazışma Kaybolan iletiler Uygulama güvenliği - Son uygulamalar görünümünde ve uygulama içinde ekran görüntüsü alınmasını engelle + Son kullanılanlar listesinde ve uygulama içinde ekran görüntüsü alınmasını engelle Signal iletileri ve aramaları, arama aktarma ve gizli gönderici Yeni konuşmalar için varsayılan zamanlayıcı Tarafınızca başlatılan tüm yeni iletiler için varsayılan kaybolan ileti zamanlayıcısı ayarlayın. @@ -4187,8 +4258,8 @@ Grup bağlantısı Kişi olarak ekle Sesi aç - Konuşma %1$s tarihine kadar sessiz yapıldı - Konuşma sonsuza kadar sessiz yapıldı + Konuşma %1$s tarihine kadar sessize alındı + Konuşma sonsuza kadar sessize alındı Telefon numarası panoya kopyalandı Telefon numarası Signal\'i destekleyerek profilin için rozet kazan. Daha fazlasını öğrenmek için bir rozete dokun. @@ -4205,7 +4276,7 @@ Bildirimleri sessize al - Sessizleştirilmedi + Sessize alınmadı Bahsedilmeler Her zaman bildir Bildirme @@ -4330,7 +4401,7 @@ Hikayeye ekle İleti ekle Yanıt ekle - Gönderilecek + Gönder: Tek görümlük ileti Bir veya daha fazla öğe çok büyük Bir veya daha fazla öğe çok geçersiz @@ -4508,7 +4579,7 @@ Bir bağlantı hatası nedeniyle bağışın gönderilemedi. Bağlantını kontrol edip tekrar dene. - %1$s için bağış + %1$s adına bağış %1$s senin adına Signal\'e bağış yaptı @@ -4905,9 +4976,9 @@ Hikayeni kimlerin görüntüleyebileceğini seç. Yaptığın değişiklikler, daha önce gönderdiğin hikayeleri etkilemez. - Yanıtlar & tepkiler + Yanıtlar ve tepkiler - Yanıtlar & tepkilere izin ver + Yanıtlar ve tepkilere izin ver Hikayeni görebilecek kişilerin tepki ve yanıt vermesine izin ver @@ -5059,9 +5130,9 @@ %1$s adlı kişinin hikayesine tepki verdin - Hikayene tepki verildi + Hikayene tepki verdi - Bir hikayeye tepki verildi + Bir hikayeye tepki verdi @@ -5087,7 +5158,7 @@ Bağışı onayla - Gönderilecek + Gönder: Alıcı, bağıştan mesajla haberdar edilecektir. Kendi mesajını aşağıya ekle. @@ -5601,5 +5672,15 @@ Kullanıcı adını sil + + + s + + dk + + Ayarla + + Ekran kilidi uygulanmadan önceki minimum süre 1 dakikadır. + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 422de1a217..4577d35c7b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -14,6 +14,7 @@ + Так Ні @@ -138,7 +139,7 @@ Продовжити - Заблокувати і покинути %1$s? + Заблокувати і покинути «%1$s»? Заблокувати %1$s? Ви більше не отримуватимете повідомлень чи оновлень з цієї групи, та її учасники не зможуть додати вас до цієї групи знов. Учасники групи більше не зможуть додавати вас до цієї групи. @@ -147,16 +148,16 @@ Ви зможете обмінюватись повідомленнями та дзвінками, а також вони зможуть бачити ваше ім\'я та фото. Ви зможете надсилати повідомлення один одному. - Заблоковані люди не зможуть подзвонити чи відправити Вам повідомлення. - Заблоковані особи не зможуть відправити вам повідомлення. + Заблоковані люди не зможуть вам телефонувати чи надсилати повідомлення. + Заблоковані люди не зможуть надсилати вам повідомлення. - Вимкнути отримання оновлень та новин Signal. + Вимкнути отримання оновлень і новин Signal. Увімкнути отримання оновлень та новин Signal. Розблокувати %1$s? Заблокувати Заблокувати і покинути - Повідомити про спам та заблокувати + Повідомити про спам і заблокувати Сьогодні @@ -366,7 +367,7 @@ Помилка відправлення медіа - Помічено як спам та заблоковано. + Позначено як спам і заблоковано. SMS-повідомлення наразі не підтримуються. Ви можете експортувати свої повідомлення в інший застосунок на телефоні. @@ -501,15 +502,15 @@ Прочитане - Прочитане - Прочитане - Прочитане + Прочитані + Прочитані + Прочитані Непрочитане - Непрочитане - Непрочитане - Непрочитане + Непрочитані + Непрочитані + Непрочитані Закріпити @@ -554,7 +555,7 @@ Видалити Видалити - Обрати все + Вибрати все %1$d обрано %1$d обрано @@ -582,6 +583,15 @@ +%1$d + + Прив\'яжіть пристрої повторно + + Додані пристрої було відв\'язано, коли була скасована реєстрація вашого пристрою. Перейдіть у налаштування, щоб повторно прив’язати пристрої. + + До налаштувань + + Пізніше + Виберіть учасників @@ -1011,7 +1021,7 @@ Сповіщати мене про згадування - Отримувати сповіщення, якщо вас згадали у чатах з вимкнутими сповіщеннями? + Отримувати сповіщення, якщо вас згадали в чатах із вимкнутими сповіщеннями? Завжди сповіщати Не сповіщати @@ -1029,6 +1039,16 @@ Ім’я користувача створено Ім’я користувача скопійовано + + Не вдалося видалити ім\'я користувача. Спробуйте знову пізніше. + + Ім\'я користувача було видалено + + + + Із вашим іменем користувача сталася помилка. Ім\'я більше не присвоєно вашому акаунту. Ви можете спробувати встановити це ім\'я повторно або вибрати нове. + + Виправити @@ -1252,8 +1272,8 @@ Нова група Запросити друзів Використовувати SMS - Вигляд - Додати Фото + Кольори чату + Фото профілю Відповіді @@ -1588,10 +1608,10 @@ Розблокувати Дозволити %1$s писати вам та ділитись з ним своїм ім\'ям та фото? Користувач не знатиме, що ви бачили його повідомлення, допоки ви не схвалите запит. - Дозволити %1$s надсилати вам повідомлення та ділитись ім\'ям та фото з цим користувачем? Ви не отримаєте повідомлень, допоки не розблокуєте цього користувача. + Дозволити користувачу %1$s надсилати вам повідомлення та ділитись ім\'ям і фото з цим користувачем? Ви не отримаєте повідомлень, поки не розблокуєте цього користувача. - Дозволити %1$s відправити вам повідомлення? Ви не отримаєте жодного повідомлення, допоки не розблокуєте його. - Отримуєте оновлення та новини від %1$s? Ви не отримуватимете жодних оновлень допоки не увімкнете їх. + Дозволити користувачу %1$s надсилати вам повідомлення? Ви не отримаєте повідомлень, поки не розблокуєте цього користувача. + Отримувати оновлення та новини від %1$s? Ви не отримуватимете оновлень, поки не розблокуєте користувача. Продовжувати спілкуватися з цією групою та ділитися своїм ім’ям та фото з її учасниками? Оновіть цю групу, щоб отримати доступ до нових можливостей як-до @згадування та адміністратори. Учасники, які не поділились своїми іменами чи фотографіями у цій групі, будуть запрошені приєднатись. Ця Застаріла Група більше не може використовуватись, тому що вона занадто велика. Максимальний розмір групи це - %1$d. @@ -1599,7 +1619,7 @@ Приєднатися до цієї групи та поділитися своїм ім’ям та фото з її учасниками? Вони не знатимуть, що ви бачили їхні повідомлення, поки ви не приймете. Приєднатися до цієї групи та поділитися своїм ім’ям і фото з її учасниками? Ви не будете бачити повідомлення групи, доки не погодитесь. Приєднатись до групи? Вони не дізнаються, що ви бачили їх повідомлення допоки ви не приймете запит. - Розблокувати цю групу та поділитися своїм ім’ям та фото з її учасниками? Ви не отримаєте жодного повідомлення, поки не розблокуєте їх. + Розблокувати цю групу та ділитися своїм ім’ям і фото з її учасниками? Ви не отримаєте повідомлень, поки не розблокуєте групу. Детальніше Полягає в %1$s @@ -1712,9 +1732,20 @@ Створити новий PIN-код + + Надіслати SMS-код + + Реєстрація в Signal — Потрібна допомога з реєстрацією PIN-коду для Android + + Ваш PIN-код — це створений вами цифровий або буквено-цифровий код, який містить мінімум %1$d символи.\n\nЯкщо ви не пам\'ятаєте свій PIN-код, ви можете створити новий. + + Якщо ви не пам\'ятаєте свій PIN-код, ви можете створити новий. + + Спроби вгадати PIN-код закінчилися, але ви можете зайти у свій акаунт Signal, створивши новий PIN-код. + Попередження - Якщо ви вимкнете PIN-код, то при перевстановленні Signal втратите всі дані, якщо ви заздалегідь  вручну  не створите резервну копію. При відключенному PIN-коді неможна буде включити блокування реєстрації. + Якщо ви вимкнете PIN-код, заздалегідь не створивши резервну копію вручну, то при перевстановленні Signal усі дані буде втрачено. При відключеному PIN-коді неможливо включити блокування реєстрації. Вимкнути PIN @@ -1849,9 +1880,9 @@ Камера - Сповіщати + Увімкнути звук - Без сповіщень + Вимкнути звук Телефонувати @@ -1866,12 +1897,12 @@ - %1$s заблокований (-а) + Користувача %1$s заблоковано Детальніше Ви не отримаєте аудіо або відео від цього користувача, а користувач не отримає ваші. Не вдається отримати аудіо та підсилювач; відео з %1$s Неможливо отримати аудіо та відео від %1$s - Це могло статись через те, що користувач не підтвердив вашу зміну коду безпеки, виникла проблема з пристроєм користувача, або цей користувач вас заблокував. + Це могло статися через те, що користувач не підтвердив вашу зміну коду безпеки, зіткнувся з проблемою з пристроєм або заблокував вас. Потягніть вгору, щоб побачити поширення екрану @@ -1908,11 +1939,18 @@ Signal потребує дозволів на перегляд контактів та медіа, щоб допомогти вам зв’язуватися з друзями та надсилати повідомлення. Ваші контакти завантажуються за допомогою функції виявлення приватних контактів Signal, що означає, що вони зашифровані наскрізним методом і є невидимі для служби Signal. Signal потребує дозволу для перегляду контактів, щоб допомогти вам зв’язатися з друзями. Ваші контакти завантажуються за допомогою функції виявлення приватних контактів Signal, що означає, що вони наскрізне зашифровані і ніколи не відображаються службою Signal. Ви зробили надто багато спроб зареєструвати цей номер. Будь ласка, спробуйте ще раз пізніше. + + Ви зробили забагато спроб зареєструвати цей номер. Будь ласка, повторіть через %1$s. Немає з\'єднання із сервером. Перевірте чи наявність доступу до Інтернет та спробуйте знову. Нестандартний формат номеру Введене число (%1$s) виглядає нестандартним.\n\nВи мали на увазі %2$s? Molly Android — Формат телефонних номерів + Запит на дзвінок надіслано + + SMS запитано + + Код підтвердження запитано Ви в %1$d кроці від відправки журналу налагодження Ви в %1$d кроках від відправки журналу налагодження @@ -1934,6 +1972,16 @@ Виклик Код підтвердження Надіслати код знову + + Виникли проблеми з реєстрацією? + + • Перевірте сигнал стільникової мережі на вашому телефоні для отримання SMS чи виклику\n • Переконайтеся, що ви можете отримувати телефонні виклики на цей номер\n • Перевірте правильність введеного номеру телефону. + + Щоб отримати додаткову інформацію, виконайте ці кроки з вирішення проблем або зверніться до служби підтримки + + ці кроки з вирішення проблем + + Звернутись у службу підтримки Включити блокування реєстрації? @@ -2093,13 +2141,17 @@ Ви активували значок - Реакція %1$s на вашу сторі + Реакція на вашу сторі: %1$s - Реакція %1$s на сторі + Реакція на сторі: %1$s Платіж Заплановане повідомлення + + Вашу історію повідомлень було об’єднано + + %2$s володіє номером %1$s Оновлення Molly @@ -2174,7 +2226,7 @@ MMS повідомлення зашифроване для неіснуючої сесії - Вимкнути звук сповіщень + Вимкнути сповіщення Імпортування @@ -2231,14 +2283,16 @@ Незахищене SMS %1$s %2$s Контакт - Відреагував (-ла) %1$sна: \"%2$s\". - Відреагував (-ла) %1$s на ваше відео. - Відреагував (-ла) %1$s на ваше зображення. - Відреагував %1$s на вашу GIF. - Відреагував (-ла) %1$s на ваш файл. - Відреагував (-ла) %1$s на ваше аудіо. - Відреагував (-ла) %1$s на ваше одноразове медіа - Відреагував (-ла) %1$s на ваш стікер. + Реакція %1$s на «%2$s». + Реакція на ваше відео: %1$s. + Реакція на ваше зображення: %1$s. + Реакція на ваш GIF: %1$s. + Реакція на ваш файл: %1$s. + Реакція на ваше аудіо: %1$s. + Реакція на ваше одноразове медіа: %1$s. + + Реакція на ваш платіж: %1$s. + Реакція на ваш стікер: %1$s. Це повідомлення було видалено. Вимкнути сповіщення \"Контакт приєднався до Signal\"? Ви можете заново включити їх в Signal > Налаштування > Сповіщення. @@ -2653,8 +2707,8 @@ Проблема з доставленням - Не вдалось доставити повідомлення, стікер, реакцію або повідомлення про прочитання від %1$sдо вас. Ця людина могла відправити вищевказане безпосередньо вам або в яку-небудь групу. - Не вдалося доставити повідомлення, стікер, реакцію або повідомлення про прочитання від %1$s вам. + Не вдалося доставити повідомлення, стікер, реакцію або повідомлення про прочитання від користувача %1$s до вас. Можливо, користувач надіслав це безпосередньо вам або в групу. + Не вдалося доставити повідомлення, стікер, реакцію або повідомлення про прочитання від користувача %1$s до вас. Ім\'я (обов\'язково) @@ -2721,7 +2775,7 @@ Очікування - Надіслано до + Надіслано Надіслано з Доставлено Прочитано @@ -2801,10 +2855,10 @@ Використовувати типово Використовувати власні - Вимкнути звук на 1 годину - Вимкнути звук на 8 годин - Вимкнути звук на 1 день - Вимкнути звук на 7 днів + Не сповіщати протягом години + Не сповіщати протягом 8 годин + Не сповіщати протягом дня + Не сповіщати протягом 7 днів Завжди Типові налаштування @@ -2925,7 +2979,7 @@ Розширені налаштування PIN-коду Безкоштовні приватні повідомлення та виклики для користувачів Signal Надіслати журнал відладки - Видалити обліковий запис + Видалити акаунт Режим сумісності \'WiFi Calling\' Увімкніть, якщо ваш пристрій використовує доставку SMS/MMS через WiFi (вмикайте лише якщо «WiFi Calling» увімкнен на вашому пристрої) Клавіатура в режимі «інкогніто» @@ -3139,7 +3193,7 @@ Надісланий платіж Отриманий платіж Платіж завершено %1$s - Номер блока + Заблокувати номер Переказ @@ -3298,10 +3352,10 @@ - Увімкнути звук + Сповіщати - Вимкнути звук сповіщень + Вимкнути сповіщення Налаштування групи @@ -3471,6 +3525,8 @@ Введіть свій PIN-код Введіть PIN-код, який ви створили для свого облікового запису. Це не те ж саме. що код перевірки з SMS. + + Введіть PIN-код, який ви створили для свого акаунту. Введіть буквено-цифровий PIN-код Введіть цифровий PIN-код Неправильний PIN-код. Будь ласка спробуйте ще раз. @@ -3541,7 +3597,7 @@ Папка Я записав цю фразу-пароль. Без неї я не зможу відновити резервну копію. Відновити резервну копію - Перенесення або відновлення облікового запису + Перенесення або відновлення акаунту Перенести обліковий запис Пропустити Резервні копії чату @@ -3584,7 +3640,10 @@ Резервна копія містить великий файл, який неможливо зберегти. Будь ласка, видаліть його і створіть нову резервну копію. Натисніть, щоб керувати резервним копіюванням. Невірний номер? + Подзвоніть мені (%1$02d:%2$02d) + + Надіслати код повторно (%1$02d:%2$02d) Зверніться в службу підтримки Signal Реєстрація в Signal - код підтвердження для Android Невірний код @@ -3592,6 +3651,18 @@ Невідомо Бачити мій номер телефону Шукати мене за номером телефону + + Номер телефону + + Виберіть, хто може бачити ваш номер телефону і хто може зв\'язатися з вами в Molly через номер. + + Хто може бачити мій номер + + Ніхто не бачитиме ваш номер телефону в Molly + + Хто може знайти мене за номером телефону + + Ваш номер телефону бачитимуть користувачі, з якими ви спілкуєтесь, та учасники груп, у яких ви спілкуєтесь. Люди, що зберегли ваш номер телефону до контактів, теж бачитимуть його в Molly. Всі Мої контакти Ніхто @@ -3625,7 +3696,7 @@ Невідомо - Перенесення або выдновлення аккаунта + Перенесення або відновлення акаунту Якщо Ви вже зареєстрували обліковий запис Signal, ви можете перенести або відновити свій обліковий запис і повідомлення Перенести з пристрою Android Перенести обліковий запис і повідомлення з вашого старого Android-пристрою. У вас повинен бути доступ до свого старого пристрою. @@ -3801,9 +3872,9 @@ %1$s/%2$s - Заблоковано «%1$s». + «%1$s» було заблоковано. Не вдалося заблокувати «%1$s» - Розблоковано «%1$s». + «%1$s» було розблоковано. Перегляд учасників @@ -3857,17 +3928,17 @@ Слабкий сигнал Wi-Fi. Увімкнено стільникову мережу. - Видалення вашого облікового запису призведе до: + Видалення акаунту: Введіть ваш номер телефону - Видалити обліковий запис - Видалення даних вашого облікового запису та зображення профілю - Видалити всі ваші повідомлення зараз + Видалити акаунт + Видалить дані вашого акаунту і фото профілю + Видалить усі ваші повідомлення Видаліть %1$s зі свого платіжного рахунку Код країни не вказано Номер не вказано Наданий номер телефону не належить до вашого облікового запису. Ви дійсно хочете видалити обліковий запис? - Ця дія видалить ваш обліковий запис Signal та скине додаток. Після завершення цього процесу додаток закриється. + Ця дія видалить ваш акаунт Signal і відновить початковий стан застосунку. Після завершення застосунок закриється. Не вдалося видалити локальні дані. Ви можете видалити їх вручну в системних налаштуваннях програми. Відкрийте Налаштування програми @@ -3976,12 +4047,12 @@ Вимкнути гаманець Ваш баланс - Перш ніж відключати платежі, ми рекомендуємо перевести кошти на інший гаманець. Якщо ви вирішите не передавати свої кошти зараз, вони залишаться у вашому Гаманці, прив\'язаного з Molly, якщо ви знову активуєте платежі. + Перш ніж відключити платежі, ми рекомендуємо перевести кошти в інший гаманець. Інакше кошти залишаться у вашому гаманці, прив\'язаному до Molly, якщо ви знову активуєте платежі. Перевести кошти, що залишились на балансі Вимкнути без переводу коштів Вимкнути Вимкнути без переводу коштів? - Баланс залишиться на рахунку, який додано до Molly, якщо ви вирішите знов увімкнути платежі. + Баланс залишиться в гаманці, прив\'язаному до Molly, якщо ви вирішите знов увімкнути платежі. Помилка при вимкненні гаманця. @@ -4182,7 +4253,7 @@ Повідомлення Виклики Сповіщати коли… - Контакт приєднвся до Signal + Контакт приєданвся до Signal Профілі сповіщень @@ -4202,7 +4273,7 @@ Листування Зникаючі повідомлення Безпека програми - Блокувати знімки екрану в списку недавніх та в програмі + Блокувати знімки екрану в списку недавніх і в застосунку Signal повідомлення та дзвінки, завжди ретранслювати дзвінки та прихований відправник Таймер за умовчанням для нових чатів Встановіть таймер зникнення повідомлень за замовчуванням для усіх нових чатів, які ви створите. @@ -4363,7 +4434,7 @@ Виклик - Без сповіщень + Не сповіщати Сповіщення вимкнені @@ -4374,7 +4445,7 @@ Деталі контакту Перевірити номер безпеки Заблокувати - Блокувати групу + Заблокувати групу Розблокувати Розблокувати групу Додати до групи @@ -4385,8 +4456,8 @@ Групове посилання Додати як контакт Сповіщати - Звук для розмови вимкнено до поки %1$s - Звук для розмови вимкнено назавжди + Сповіщення розмови вимкнено до %1$s + Сповіщення розмови вимкнено назавжди Номер телефону скопійовано в буфер обміну. Номер телефону Отримайте значки для свого профілю, підтримуючи Signal. Торкніться значка, щоб дізнатися більше. @@ -4402,7 +4473,7 @@ Хто може надсилати повідомлення? - Вимкнути звук сповіщень + Вимкнути сповіщення Сповіщення увімкнені Згадати Завжди повідомляти @@ -4671,7 +4742,7 @@ Регулярний щомісячний донат було скасовано, тому що ми не змогли обробити ваш платіж. Ваш значок більше не відображається в профілі. Регулярний щомісячний донат було скасовано. %1$s Ваш значок %2$s більше не відображається в профілі. - Ви можете продовжувати використовувати Signal, але щоб підтримати застосунок і повторно активувати свій значок, ви можете поновити передплату. + Ви можете продовжувати використовувати Signal. Якщо ви бажаєте підтримати застосунок і повторно активувати свій значок, ви можете поновити передплату. Поновити передплату До Google Pay @@ -4714,7 +4785,7 @@ Не вдалося надіслати донат через помилку мережі. Перевірте з\'єднання та спробуйте ще. - Донат для користувача %1$s + Донат від імені користувача %1$s %1$s підтримує Signal донатом від вашого імені @@ -5123,11 +5194,11 @@ Виберіть, хто може переглядати ваші сторіз. Зміни не впливатимуть на вже надіслані сторіз. - Відповіді & реакції + Відповіді і реакції - Дозволити відповіді & реакції + Дозволити відповіді і реакції - Дозвольте тим, хто бачить вашу сторі, реагувати та відповідати на неї + Дозвольте глядачам своїх сторіз реагувати і відповідати на них Контакти в Signal @@ -5279,9 +5350,9 @@ Ви відреагували на сторі користувача %1$s - Реакції на вашу сторі + Реакція на вашу сторі - Реакції на сторі + Реакція на сторі @@ -5564,7 +5635,7 @@ Експорт SMS-повідомлень… - Це може зайняти певний час + Це може тривати певний час Експорт %1$d з %2$d… @@ -5851,5 +5922,15 @@ Видалити ім\'я користувача + + + год + + хв + + Задати + + Мінімальний час до застосування блокування екрана становить 1 хвилину. + diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 04dc3b853a..e1dfdad3bc 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -14,13 +14,14 @@ + جی ہاں جی نہیں حذف کریں برائے مہربانی انتظار کریں۔۔۔ محفوظ کریں - اپنے لئے نوٹ کریں + خود کے لیے نوٹ @@ -96,13 +97,13 @@ پیغامات کی جانچ ہو رہی ہے… - مسدود صارفین - مسدود صارف کو شامل کریں - بلاکڈ صارفین آپ کو پیغام یا کال نہیں کر پائیں گے۔ - کوئی مسدود صارف نہیں - صارف کو مسدود کریں؟ + بلاک کردہ یوزرز + بلاک کردہ یوزر کو شامل کریں + بلاک کردہ یوزرز آپ کو کال کرنے یا آپ کو میسجز بھیجنے کے قابل نہیں ہوں گے۔ + کوئی بلاک کردہ یوزرز نہیں ہیں + یوزر کو بلاک کریں؟ \"%1$s\" آپ کو کال کرنے یا پیغامات بھیجنے کے قابل نہیں ہوگا۔ - بلاک + بلاک کریں @@ -138,8 +139,8 @@ جاری رکھیں - %1$s کو مسدود کریں اور چھوڑیں؟ - %1$sمسدود کریں؟ + %1$s کو بلاک کریں اور چھوڑ دیں؟ + %1$s کو بلاک کریں؟ آپ کو اب اس گروپ کی طرف سے پیغامات یا اپ ڈیٹس نہیں آئیں گے اور ممبران آپ کو دوبارہ اس گروپ میں شامل نہیں کرسکیں گے۔ گروپ ممبران آپ کو دوبارہ اس گروپ میں شامل نہیں کرسکیں گے۔ گروپ ممبران آپ کو دوبارہ اس گروپ میں شامل کرسکیں گے۔ @@ -147,16 +148,16 @@ آپ ایک دوسرے کو پیغام دینے اور کال کرنے کے قابل ہو جائیں گے اور آپ کے نام اور تصویر ان کے ساتھ شیئر کی جائے گی۔ آپ ایک دوسرے کو پیغام بھیجنے کے قابل ہوں گے۔ - مسدود افراد آپ کو کال کرنے یا پیغامات بھیجنے کے اہل نہیں ہوں گے۔ - بلاک کیے گئے افراد آپ کو پیغامات نہیں بھیج سکیں گے۔ + بلاک کردہ افراد آپ کو کال کرنے یا آپ کو میسجز بھیجنے کے قابل نہیں ہوں گے۔ + بلاک کردہ افراد آپ کو میسجز بھیجنے کے قابل نہیں ہوں گے۔ Signal اپ ڈیٹس اور خبریں حاصل کرنا بلاک کریں۔ Signal اپ ڈیٹس اور خبریں حاصل کرنا بحال کریں۔ - %1$s کو غیر مسدود کریں؟ - بلاک - مسدود اور چھوڑو - بطور اسپام نشان لگائیں ، اور اسے مسدود کردیں + %1$s کو اَن بلاک کرنا ہے؟ + بلاک کریں + بلاک کریں اور چھوڑ دیں + اسپام رپورٹ کریں اور بلاک کریں آج @@ -318,7 +319,7 @@ Signal پیغام چلیں Molly کو چلاتے ہیں%1$s براۓ مہربانی رابطہ منتخب کریں - ان بلاک + اَن بلاک کریں جو پیغام آپ بھیج رہے ہیں منسلک ہونے کیلئے اس کا حجم حد سے زیادہ ہے۔ آڈیو ریکارڈ کرنے کے قابل نہیں ہے! ؐ آپ اس گروپ کو پیغامات نہیں بھیج سکتے کیونکہ آپ اب ممبر نہیں رہیں۔ @@ -366,7 +367,7 @@ میڈیا بھیجنے میں خامی - اسپام کے بطور اطلاع دی گئی اور مسدود ہے۔ + اسپام کے طور پر رپورٹ کیا گیا اور بلاک کر دیا گیا۔ SMS میسجنگ ابھی غیر فعال ہے۔ آپ اپنے فون کی کسی دوسری ایپ پر اپنے میسجز برآمد کر سکتے ہیں۔ @@ -455,7 +456,7 @@ منسوخ کریں - بلاک ہوگیا + بلاک کر دیا گیا فلٹر کو صاف کریں @@ -542,6 +543,15 @@ +%1$d + + اپنی ڈیوائسز کو دوبارہ لنک کریں + + جب آپ کی ڈیوائس غیر رجسٹر شدہ تھی اس وقت آپ کی جانب سے شامل کردہ ڈیوائسز غیر لنک کردہ ہو گئی تھیں۔ کوئی بھی ڈیوائسز دوبارہ لنک کرنے کے لیے سیٹنگز میں جائیں۔ + + سیٹنگز کھولیں + + بعد میں + ممبران کو منتخب کریں @@ -935,7 +945,7 @@ جب مجھے کوئی ذکر کرتا ہے تو مجھے مطلع کریں - خاموش چیٹس میں آپ کا ذکر ہونے پر اطلاعات موصول کریں؟ + خاموش کردہ چیٹس میں خود کو مینشن کیے جانے پر اطلاعات موصول کریں؟ مجھے ہمیشہ مطلع کریں مجھے مطلع نہ کریں @@ -953,6 +963,16 @@ صارفی نام تخلیق کیا گیا صارفی نام کاپی کیا گیا + + یوزر نیم حذف نہیں کر سکے۔ بعد میں دوبارہ کوشش کریں۔ + + یوزر نیم حذف کر دیا گیا + + + + آپ کے یوزر نیم کے ساتھ کچھ غلط ہو گیا، یہ مزید آپ کے اکاؤنٹ کے لیے تفویض کردہ نہیں ہے۔ آپ کوشش کر سکتے ہیں اور اسے دوبارہ سیٹ کر سکتے ہیں یا ایک نیا منتخب کر سکتے ہیں۔ + + ابھی ٹھیک کریں @@ -1156,8 +1176,8 @@ نیا گروپ دوستوں کو مدعو کریں ایس ایم ایس کا استعمال کریں - ظہور ہونا - تصویر لگاو + چیٹ کے رنگ + پروفائل فوٹو شامل کریں جوابات @@ -1468,14 +1488,14 @@ قبول کریں جاری رکھیں حذف کریں - بلاک - ان بلاک + بلاک کریں + اَن بلاک کریں %1$s کو آپ کو میسج کرنے اور ان کے ساتھ اپنا نام اور تصویر شئیر کرنے دیں؟ جب تک آپ ان کو قبول نہیں کرتے انھیں معلوم نہیں ہوگا کہ آپ نے ان کا پیغام دیکھا ہے۔ - %1$s کو آپ کو میسج کرنے اور ان کے ساتھ اپنا نام اور تصویر شئیر کرنے دیں؟ جب تک آپ ان کو غیر مسدود نہیں کرتے, آپ کو کوئی پیغام نہیں ملے گا. + %1$s کو خود کو میسج بھیجنے اور ان کے ساتھ اپنا نام اور تصویر شیئر کرنے کی اجازت دیں؟ جب تک آپ ان کو اَن بلاک نہیں کرتے تب تک آپ کو کوئی میسجز موصول نہیں ہوں گے۔ - %1$s کوپیغام بھیجنے کی اجازت دیں؟ آپ انہیں ان بلاک کرنے تک کوئی پیغامات حاصل نہیں کریں گے۔ - %1$s سے اپ ڈیٹس اور خبریں حاصل کریں؟ آپ انہیں ان بلاک کرنے تک کوئی اپ ڈیٹس حاصل نہیں کریں گے۔ + %1$s کو خود کو میسج بھیجنے کی اجازت دیں؟ جب تک آپ ان کو اَن بلاک نہیں کرتے تب تک آپ کو کوئی میسجز موصول نہیں ہوں گے۔ + %1$s سے اپ ڈیٹس اور خبریں حاصل کریں؟ جب تک آپ ان کو اَن بلاک نہیں کرتے تب تک آپ کو کوئی اپ ڈیٹس موصول نہیں ہوں گی۔ اس گروپ کے ساتھ اپنی گفتگو جاری رکھیں اور اس کے ممبروں کے ساتھ اپنا نام اور تصویر شیئر کریں؟ اس گروپ کو اعلی درجے کی خصوصیات جیسے @ ذکر اور منتظمین کو چالو کرنے کے لئے۔ جن ممبروں نے اس گروپ میں اپنا نام یا تصویر شیئر نہیں کی ہے ان میں شرکت کی دعوت دی جائے گی۔ اس لیگیسی گروپ کو اب استعمال نہیں کیا جاسکتا ہے کیونکہ یہ بہت بڑا ہے۔ زیادہ سے زیادہ گروپ سائز %1$d ہے۔ @@ -1483,7 +1503,7 @@ اس گروپ میں شامل ہوں اور اس کے ممبروں کے ساتھ اپنا نام اور تصویر بانٹیں؟ جب تک آپ ان کو قبول نہیں کرتے ان کو معلوم نہیں ہوگا کہ آپ نے ان کے پیغامات دیکھے ہیں۔ اس گروپ میں شامل ہوں اور اس کے اراکین کے ساتھ اپنا نام اور تصویر شیئر کریں؟ آپ قبول کرنے تک ان کے پیغامات نہیں دیکھ پائیں گے۔ اس گروپ میں شامل ہوں؟ جب تک آپ ان کو قبول نہ کریں تب تک وہ نہیں جانیں گے کہ آپ نے ان کے پیغامات دیکھے ہیں۔ - اس گروپ کو غیر مسدود کریں اور اس کے ممبروں کے ساتھ اپنا نام اور تصویر شئیر کریں؟ آپ کو کوئی پیغامات موصول نہیں ہوگا جب تک کہ آپ ان کو بلاک نہیں کردیں گے۔ + اس گروپ کو اَن بلاک کریں اور اس کے ممبرز کے ساتھ اپنا نام اور تصویر شیئر کریں؟ جب تک آپ ان کو اَن بلاک نہیں کرتے تب تک آپ کو کوئی میسجز موصول نہیں ہوں گے۔ دیکھیں کا رکن%1$s @@ -1584,9 +1604,20 @@ نیا PIN بنائیں + + SMS کوڈ بھیجیں + + Signal رجسٹریشن - Android کے لیے پِن کو دوبارہ رجسٹر کرنے میں مدد درکار ہے + + آپ کا پِن آپ کی طرف سے تخلیق کردہ %1$d+ اعدادی کوڈ ہے جو کہ عددی یا حرفی و عددی ہو سکتا ہے۔\n\nاگر آپ اپنا پِن یاد کرنے سے قاصر ہیں، تو آپ ایک نیا تخلیق کر سکتے ہیں۔ + + اگر آپ اپنا پِن یاد کرنے سے قاصر ہیں، تو آپ ایک نیا تخلیق کر سکتے ہیں۔ + + آپ کے پِن کے اندازے ختم ہو چکے ہیں، تاہم آپ اب بھی ایک نیا پِن تخلیق کر کے اپنے Signal اکاؤنٹ تک رسائی حاصل کر سکتے ہیں۔ + انتباہ - اگر آپ PIN کو غیر فعال کردیتے ہیں تو ، جب تک آپ دستی طور پر بیک اپ اور بحال نہیں کرتے ہیں تو Signal کو دوبارہ رجسٹر کرواتے وقت آپ تمام ڈیٹا ضائع کردیں گے۔ جب PIN غیر فعال ہو تو آپ رجسٹریشن لاک آن نہیں کرسکتے ہیں۔ + اگر آپ پِن کو غیر فعال کرتے ہیں، تو Signal کو دوبارہ رجسٹر کرنے پر آپ تمام ڈیٹا سے محروم ہو جائیں گے تاوقتیکہ آپ دستی طور پر بیک اپ بنائیں اور بحال کریں۔ پِن کے غیر فعال ہوتے ہوئے آپ رجسٹریشن لاک آن نہیں کر سکتے۔ PIN کو غیر فعال کریں @@ -1616,8 +1647,8 @@ میری سٹوری - بلاک - ان بلاک + بلاک کریں + اَن بلاک کریں @@ -1711,9 +1742,9 @@ کیمرہ - غیر خاموش + غیر خاموش کریں - خاموش + خاموش کریں کال کریں @@ -1726,12 +1757,12 @@ - %1$s مسدود ہے + %1$s بلاک ہے مزید معلومات آپ کو ان کا آڈیو یا ویڈیو موصول نہیں ہوگا اور وہ آپ کو وصول نہیں کریں گے۔ آڈیو وصول نہیں کر سکتے ہیں۔ %1$s سے ویڈیو %1$s سے آڈیو اور ویڈیو موصول نہیں ہوسکتی ہے - اس کی وجہ یہ ہوسکتی ہے کہ انہوں نے آپ کے حفاظتی نمبر میں تبدیلی کی توثیق نہیں کی ہے ، ان کے آلے میں کوئی مسئلہ ہے یا انہوں نے آپ کو مسدود کردیا ہے۔ + ایسا اس لیے ہو سکتا ہے کیونکہ انہوں نے آپ کے حفاظتی نمبر کی تبدیلی کی تصدیق نہیں کی ہے، ان کی ڈیوائس کے ساتھ کوئی مسئلہ ہے، یا وہ آپ کو بلاک کر چکے ہیں۔ اسکرین شیئر دیکھنے کیلئے سوائپ کریں @@ -1768,11 +1799,18 @@ سگنل کو آپ کے دوستوں سے رابطہ قائم کرنے اور پیغامات بھیجنے کے لیے رابطوں اور میڈیا کی اجازت کی ضرورت ہوتی ہے۔ آپ کے رابطے سگنل کی نجی رابطہ دریافت کا استعمال کرتے ہوئے اپ لوڈ کیے جاتے ہیں، جس کا مطلب ہے کہ یہ شروع سے آخر تک خفیہ ہوتے ہیں اور سگنل سروس کو کبھی نظر نہیں آتے۔ سگنل کو آپ کے دوستوں سے رابطہ قائم کرنے اور پیغامات بھیجنے کے لیے رابطوں کی اجازت کی ضرورت ہوتی ہے۔ آپ کے رابطے سگنل کی نجی رابطہ دریافت کا استعمال کرتے ہوئے اپ لوڈ کیے جاتے ہیں، جس کا مطلب ہے کہ یہ شروع سے آخر تک خفیہ ہوتے ہیں اور سگنل سروس کو کبھی نظر نہیں آتے۔ آپ نے اس نمبر کو رجسٹر کرنے کے لئے بہت زیادہ کوششیں کی ہیں۔ براہ کرم کچھ دیر بعد کوشش کریں. + + آپ اس نمبر کو رجسٹر کرنے کے لیے بہت مرتبہ کوششیں کر چکے ہیں۔ براہ کرم %1$s میں دوبارہ کوشش کریں۔ سروس سے رابطے کے قابل نہیں۔ براہ کرم نیٹ ورک کنکشن چیک کریں اور دوبارہ کوشش کریں۔ غیر معیاری ہندسوں کی ترتیب آپ کا درج کردہ نمبر (%1$s) غیر معیاری فارمیٹ لگ رہا ہے۔\n\nکیا آپ کا مطلب %2$s ہے؟ Molly Android - فون نمبر کا فارمیٹ + کال کی درخواست کی گئی + + SMS کی درخواست کر دی گئی + + تصدیقی کوڈ کی درخواست کر دی گئی کسی ڈی بَگ لاگ کو شامل کرنے سے آپ %1$d مرحلے دور ہیں۔ کسی ڈی بَگ لاگ کو پیش کرنے کیلئے اب آپ %1$d مرحلے دور ہیں۔ @@ -1792,6 +1830,16 @@ کال تصدیقی کوڈ کوڈ دوبارہ بھیجیں + + رجسٹر کرنے میں دشواری کا سامنا ہے؟ + + • یقینی بنائیں کہ SMS یا کال موصول کرنے کے لیے آپ کے فون میں سیلولر سگنل موجود ہیں\n • تصدیق کریں کہ آپ نمبر پر فون کال موصول کر سکتے ہیں\n •چیک کریں کہ آپ نے اپنا فون نمبر درست طریقے سے درج کیا ہے۔ + + مزید معلومات کے لیے، ٹربل شوٹنگ کے ان اقدامات پر عمل کریں یا سپورٹ سے رابطہ کریں + + ٹربل شوٹنگ کے یہ اقدامات + + سپورٹ سے رابطہ کریں رجسٹریشن لاک آن کریں؟ @@ -1951,13 +1999,17 @@ آپ نے ایک بیج ریڈیم کیا ہے - آپ کی سٹوری پر %1$s ردعمل دیا + آپ کی سٹوری پر %1$s ری ایکٹ کیا - ان کی سٹوری پر %1$s ردعمل دیا + ان کی سٹوری پر %1$s ری ایکٹ کیا پیمنٹ شیڈول کردہ میسج + + آپ کی میسج ہسٹری ضم کر دی گئی ہے + + %1$s کا تعلق %2$s سے ہے Molly اپ ڈیٹ @@ -2030,7 +2082,7 @@ غیر موجود سیشن کیلئے خفیہ کردہ ایم ایم ایس پیغام - خاموش اطلاع + اطلاعات کو خاموش کریں درآمد بہتری میں ہے @@ -2087,14 +2139,16 @@ غیر محفوظ ایس ایم ایس %1$s%2$s رابطہ کریں - اس پر رد عمل%1$s ظاہر کیا: \"%2$s\"۔ - آپ کی ویڈیو پر رد عمل %1$s ظاہر کیا - آپ کی تصویر پر رد عمل%1$s ظاہر کیا - آپ کے GIF پر %1$s ردعمل دیا۔ - آپ کی فائل پر رد عمل%1$s ظاہر کیا - آپ کی آڈیو پر رد عمل %1$s ظاہر کیا - ایک بار آپ کے ملاحظہ کرنے والے میڈیا پر ردعمل %1$s کا اظہار کیا۔ - آپ کی اسٹیکر پر رد عمل %1$s ظاہر کیا + %1$s ری ایکٹ کیا: \"%2$s\" پر۔ + آپ کی ویڈیو پر %1$s ری ایکٹ کیا۔ + آپ کی تصویر پر %1$s ری ایکٹ کیا۔ + آپ کے GIF پر %1$s ری ایکٹ کیا۔ + آپ کی فائل پر %1$s ری ایکٹ کیا۔ + آپ کی آڈیو پر %1$s ری ایکٹ کیا۔ + آپ کے ایک وقتی دیکھے جانے والے میڈیا پر %1$s ری ایکٹ کیا۔ + + Reacted %1$s to your payment. + آپ کے اسٹیکر پر %1$s ری ایکٹ کیا۔ یہ پیغام حذف کردیا گیا تھا۔ رابطے میں شامل Signal اطلاعات بند کردیں؟ آپ انہیں Signal & gt میں دوبارہ فعال کرسکتے ہیں۔ ترتیبات & gt؛ اطلاعات۔ @@ -2487,8 +2541,8 @@ فراہمی کا مسئلہ - کوئی پیغام ، اسٹیکر ، رد عمل ، یا پڑھی ہوئی رسید آپ کو %1$s سے نہیں پہنچا ہے۔ ہوسکتا ہے کہ انہوں نے یہ آپ کو براہ راست یا کسی گروپ میں بھیجنے کی کوشش کی ہو۔ - کوئی پیغام ، اسٹیکر ، رد عمل ، یا پڑھی ہوئی رسید آپ کو %1$s سے نہیں پہنچا سکتی ہے۔ + %1$s کی طرف سے کوئی میسج، اسٹیکر، ری ایکشن، یا میسج پڑھنے کا ثبوت آپ کو ڈیلیور نہیں کیا جا سکا۔ انہوں نے یہ آپ کو براہ راست، یا کسی گروپ میں بھیجنے کی کوشش کی ہو گی۔ + %1$s کی طرف سے کوئی میسج، اسٹیکر، ری ایکشن، یا میسج پڑھنے کا ثبوت آپ کو ڈیلیور نہیں کیا جا سکا۔ پہلا نام (ضروری) @@ -2635,10 +2689,10 @@ پہلے سے طے شدہ استعمال کریں اپنی مرضی سے استعمال کریں - 1 گھنٹے کیلئے خاموش - 8 گھنٹے خاموش رہیں - 1 دن کیلئے خاموش - 7 دنوں کیلئے خاموش + 1 گھنٹے کے لیے خاموش کریں + 8 گھنٹوں کے لیے خاموش کریں + 1 دن کے لیے خاموش کریں + 7 دنوں کے لیے خاموش کریں ہمیشہ پہلے سے موجود ترتیبات @@ -2675,9 +2729,9 @@ ایڈریس بک فوٹو استعمال کریں اگر دستیاب ہو تو اپنی ایڈریس بک سے رابطے کی تصاویر دکھائیں - خاموش کردہ چیٹس کو آرکائیو رکھیں + خاموش کردہ چیٹس کو آرکائیو کردہ رکھیں - آرکائیو کردہ خاموش چیٹس نئے پیغام کے آنے پر بھی آرکائیو کردہ رہیں گی۔ + نیا میسج آنے پر آرکائیو کی گئی خاموش کردہ چیٹس آرکائیو کردہ رہیں گی۔ لنک پیش نظارہ تیار کریں اپنے بھیجے ہوئے پیغامات کیلئے براہ راست ویب سائٹس سے لنک پیش نظارہ بازیافت کریں۔ پاسفریز تبدیل کریں @@ -2971,7 +3025,7 @@ ادائیگی بھیجی گئی ادائیگی موصول ہوئی ادائیگی مکمل ہوگئی%1$s - بلاک نمبر + نمبر بلاک کریں منتقلی @@ -3056,7 +3110,7 @@ کو نیا پیغام۔۔۔ - مسدود صارف + یوزر کو بلاک کریں گروپ میں شامل کریں @@ -3128,10 +3182,10 @@ - بے آواز + غیر خاموش کریں - خاموش اطلاعات + اطلاعات کو خاموش کریں گروپ کی ترتیبات @@ -3295,6 +3349,8 @@ اپنا پن داخل کریں اپنے اکاؤنٹ کیلئے جو پن بنایا ہے داخل کریں۔ یہ آپ کے تصدیقی ایس ایم ایس سے مختلف ہے۔ + + وہ پِن درج کریں جو آپ نے اپنے اکاؤنٹ کے لیے تخلیق کیا تھا۔ Alphanumeric کا پن درج کریں عددی پن درج کریں غلط پن. دوبارہ کوشش کریں. @@ -3398,7 +3454,10 @@ آپ کے بیک اپ میں ایک بہت بڑی فائل شامل ہے جسے بیک اپ نہیں کیا جا سکتا۔ براہ کرم اسے حذف کریں اور ایک نیا بیک اپ تخلیق کریں۔ بیک اپ کا انتظام کرنے کے لئے ٹیپ کریں۔ غلط نمبر؟ + مجھے کال کریں (%1$02d:%2$02d) + + کوڈ دوبارہ بھیجیں (%1$02d:%2$02d) Signal رابطہ سپورٹ کریں Signal رجسٹریشن- Android کے لیے تصدیقی کوڈ درج کریں غلط کوڈ @@ -3406,6 +3465,18 @@ نامعلوم میرا فون نمبر دیکھیں مجھے فون نمبر کے ذریعے تلاش کریں + + فون نمبر + + منتخب کریں کہ کون آپ کا فون نمبر دیکھ سکتا ہے اور کون اس کے ذریعے Molly پر آپ سے رابطہ کر سکتا ہے۔ + + میرا نمبر کون دیکھ سکتا ہے + + Molly پر کوئی بھی آپ کا فون نمبر نہیں دیکھ سکے گا + + کون مجھے میرے نمبر کے ذریعے تلاش کر سکتا ہے + + آپ کا فون نمبر ان لوگوں اور گروپس کو نظر آئے گا جنہیں آپ میسج کرتے ہیں۔ جن لوگوں کے فون رابطوں میں آپ کا نمبر محفوظ ہے وہ بھی اسے Molly پر دیکھیں گے۔ ہر ایک میرے رابطے کوئی نہیں @@ -3562,8 +3633,8 @@ - بلاک - ان بلاک + بلاک کریں + اَن بلاک کریں رابطوں میں شامل کریں روابط کھولنے کے قابل ایپ تلاش نہیں کر سکتے۔ @@ -3615,9 +3686,9 @@ %1$s/%2$s - \"%1$s\" کو مسدود کردیا گیا ہے۔ - \"%1$s\" مسدود میں ناکام - \"%1$s\" غیر مسدود کردیا گیا ہے۔ + \"%1$s\" کو بلاک کر دیا گیا ہے۔ + \"%1$s\' کو بلاک کرنے میں ناکام + \"%1$s\" کو اَن بلاک کر دیا گیا ہے۔ ممبران کا جائزہ لیں @@ -3644,7 +3715,7 @@ آپ کا رابطہ گروپ سے ہٹائیں رابطہ اپ ڈیٹ کریں - بلاک + بلاک کریں حذف کریں حال ہی میں اپنے پروفائل کا نام %1$s سے تبدیل کرکے %2$s کردیا @@ -3667,17 +3738,17 @@ Wi-Fi کمزور ہے۔ سیلولر پر سوئچ کریں۔ - آپ کا اکاؤنٹ حذف کرنے سے یہ ہوگا: + اپنا اکاؤنٹ حذف کرنے سے یہ ہو گا: اپنا فون نمبر درج کریں اکاؤنٹ حذف کریں - اپنے اکاؤنٹ کی معلومات اور پروفائل فوٹو حذف کریں - اپنے تمام پیغامات کو حذف کریں + اپنے اکاؤنٹ کی معلومات اور پروفائل کی تصویر حذف کریں + اپنے تمام میسجز حذف کریں اپنے ادائیگیوں کے اکاؤنٹ میں %1$s کو حذف کریں ملک کا کوئی کوڈ بتایا نہیں ہے کوئی نمبر نہیں بتایا گیا آپ نے جو فون نمبر داخل کیا ہے وہ آپ کے اکاؤنٹ کے مماثل نہیں ہے۔ کیا آپ کو یقین ہے کہ آپ اپنا اکاؤنٹ ختم کرنا چاہتے ہیں؟ - یہ آپ کا Signal اکاؤنٹ حذف کردے گا اور درخواست کو دوبارہ ترتیب دے گا۔ عمل مکمل ہونے کے بعد ایپ بند ہوجائے گی۔ + اس سے آپ کا Signal اکاؤنٹ حذف ہو جائے گا اور ایپلیکیشن کو دوبارہ ری سیٹ کرے گا۔ کارروائی مکمل ہونے کے بعد ایپ بند ہو جائے گی۔ مقامی ڈیٹا کو حذف کرنے میں ناکام۔ آپ اسے سسٹم ایپلیکیشن کی ترتیبات میں دستی طور پر صاف کرسکتے ہیں۔ ایپ کی ترتیبات لانچ کریں @@ -3784,12 +3855,12 @@ Wallet کو غیر فعال کریں آپ کا بیلنس - یہ تجویز کی جاتی ہے کہ ادائیگی غیر فعال کرنے سے پہلے آپ اپنے فنڈز کو دوسرے بٹوے کے پتے پر منتقل کریں۔ اگر آپ ابھی اپنے فنڈز کی منتقلی نہ کرنا چاہتے ہیں تو ، اگر آپ ادائیگیوں کو دوبارہ متحرک کرتے ہیں تو وہ Molly سے منسلک آپ کے پرس میں رہیں گے۔ + یہ تجویز پیش کی جاتی ہے کہ پیمنٹس کو غیر فعال کرنے سے قبل آپ اپنے فنڈز کو کسی دوسرے والیٹ ایڈریس پر ٹرانسفر کریں۔ اگر آپ فی الحال اپنے فنڈز کو ٹرانسفر کرنے کا انتخاب نہیں کرنا چاہتے، تو آپ کی جانب سے پیمنٹس کو دوبارہ فعال کرنے کی صورت میں یہ Molly سے لنک کردہ آپ کے والیٹ میں موجود رہیں گے۔ باقی بیلنس منتقل کریں منتقلی کے بغیر غیر فعال کریں غیر فعال کریں منتقلی کے بغیر غیر فعال کریں؟ - اگر آپ ادائیگیوں کو دوبارہ فعال کرنے کا انتخاب کرتے ہیں تو آپ کا بیلنس Molly سے منسلک آپ کے Wallet میں رہے گا۔ + اگر آپ پیمنٹس کو دوبارہ فعال کرنے کا انتخاب کرتے ہیں تو آپ کا بیلنس Molly سے لنک کردہ آپ کے والیٹ میں موجود رہے گا۔ Wallet کو غیر فعال کرنے میں خرابی۔ @@ -4003,12 +4074,12 @@ پروفائل تخلیق کریں - بلاک ہوگیا + بلاک کر دیا گیا %1$dرابطے پیغام رسانی پیغامات غائب ہو رہے ہیں ایپ سیکیورٹی - حالیہ فہرست میں سکرین شاٹ بلاک کریں اور ایپ کے اندر + حالیہ فہرست میں اور ایپ کے اندر اسکرین شاٹس کو بلاک کریں Signal میسجز اور کالز ، ہمیشہ ریلے کالز اور مہر بند مرسل کو نئی چیٹس کیلئے ڈیفالٹ ٹائمر آپ کے ذریعہ شروع کردہ تمام نئی چیٹس کے لئے ڈیفالٹ غائب میسیج ٹائمر مرتب کریں۔ @@ -4106,7 +4177,7 @@ ابھی نہیں - رد عمل کو اپنی مرضی کے مطابق بنائیں + ری ایکشنز کو حسبِ ضرورت بنائیں ایموجی کو تبدیل کرنے کے لئے ٹیپ کریں دوبارہ بحال کریں محفوظ کریں @@ -4165,9 +4236,9 @@ کال - خاموش + خاموش کریں - خاموش + خاموش کر دیا گیا تلاش کریں پیغامات غائب ہو رہے ہیں @@ -4175,10 +4246,10 @@ رابطہ کی تفصیلات حفاظتی نمبر دیکھیں - بلاک - مسدود گروپ - ان بلاک - غیر مسدود گروپ + بلاک کریں + گروپ کو بلاک کریں + اَن بلاک کریں + گروپ کو اَن بلاک کریں کسی گروپ میں شامل کریں تمام دیکھیں ممبران شامل کریں @@ -4186,9 +4257,9 @@ درخواستیں & amp؛ دعوت دیتا ہے گروپ لنک بطور رابطہ شامل کریں - غیر خاموش - گفتگو %1$s تک خاموش رہی - گفتگو ہمیشہ کیلئے خاموش کردی گئی + غیر خاموش کریں + گفتگو %1$s تک خاموش کردہ ہے + گفتگو ہمیشہ کے لیے خاموش کردہ ہے کلپ بورڈ پر فون نمبر کاپی کیا گیا۔ فون نمبر Signal کی سپورٹ کے ذریعے اپنی پروفائل کے لیے بیجز حاصل کریں۔ مزید جاننے کے لیے بیج پر ٹیپ کریں۔ @@ -4204,8 +4275,8 @@ پیغامات کون بھیج سکتا ہے؟ - خاموش اطلاعات - غیر خاموش + اطلاعات کو خاموش کریں + خاموش کردہ نہیں ہیں ذکر ہمیشہ مطلع کریں مطلع نہ کریں @@ -4234,7 +4305,7 @@ ہٹا دیں - بلاک + بلاک کریں %1$s ہٹائیں؟ @@ -4330,7 +4401,7 @@ سٹوری میں شامل کریں پیغام شامل کریں جواب شامل کریں - بھیجیں بطرف + بھیجیں بنام پیغام ایک بار دیکھیں ایک یا زائد آئٹمز بہت بڑی تھیں ایک یا زائد آئٹمز غلط تھیں @@ -4453,7 +4524,7 @@ ماہانہ عطیہ منسوخ ہو گیا آپ کا بوسٹ بیج زائد المیعاد ہو گیا ہے اور آپ کی پروفائل پر مزید دکھائی نہیں دیتا۔ - آپ ایک بار کے تعاون کے ساتھ آئندہ 30 دنوں کے لیے اپنا بوسٹ بیج دوبارہ فعال کر سکتے ہیں۔ + آپ ایک وقتی تعاون کے ساتھ آئندہ 30 دنوں کے لیے اپنا بوسٹ بیج دوبارہ فعال کر سکتے ہیں۔ آپ Signal استعمال کرنا جاری رکھ سکتے ہیں لیکن اپنے لیے بنائی گئی ٹیکنالوجی کی سپورٹ کے لیے، ماہانہ عطیہ دینے کے ذریعے عطیہ دینے والا بننے کو زیر غور لائیں۔ عطیہ گزار بنیں @@ -4465,7 +4536,7 @@ آپ کا ماہانہ متواتر عطیہ منسوخ ہو گیا تھا کیونکہ ہم آپ کی پیمنٹ پر عمل کاری نہیں کر سکے۔ آپ کا بیج آپ کی پروفائل پر مزید دکھائی نہیں دیتا۔ آپ کا متواتر ماہانہ عطیہ منسوخ کر دیا گیا تھا۔ %1$s آپ کا %2$s بیج آپ کی پروفائل پر مزید دکھائی نہیں دیتا۔ - آپ Signal استعمال کرنا جاری رکھ سکتے ہیں لیکن ایپ کی سپورٹ اور اپنے بیج کو دوبارہ فعال کرنے کے لیے، ابھی تجدید کریں۔ + آپ Signal استعمال کرنا جاری رکھ سکتے ہیں مگر صرف ایپ کو سپورٹ کرنے اور اپنے بیج کو دوبارہ فعال کرنے کے لیے، ابھی تجدید کریں۔ سبسکرپشن کی تجدید کریں Google Pay پر جائیں @@ -4508,7 +4579,7 @@ نیٹ ورک کی خرابی کے باعث آپ کا عطیہ بھیجا نہیں جا سکا۔ اپنا کنکشن چیک کریں اور دوبارہ کوشش کریں۔ - %1$s کو عطیہ + %1$s کی جانب سے عطیہ %1$s نے آپ کی جانب سے Signal کو عطیہ کیا @@ -4853,13 +4924,13 @@ آپ اس سٹوری کا جواب نہیں دے سکتے کیونکہ آپ اس گروپ کے ممبر نہیں رہے۔ - سٹوری پر ردعمل دیا + سٹوری پر ری ایکٹ کیا ویوز جوابات - اس سٹوری پر ردعمل دیں + اس سٹوری پر ری ایکٹ کریں %1$s کو نجی طور پر جواب دینا @@ -4905,11 +4976,11 @@ انتخاب کریں کہ کون آپ کی سٹوری دیکھ سکتا ہے۔ تبدیلیاں ان سٹوریز کو متاثر نہیں کریں گی جو آپ پہلے ہی بھیج چکے ہیں۔ - جوابات & ردعمل + جوابات اور ری ایکشنز - جوابات &ردعمل کو اجازت دیں + جوابات اور ری ایکشنز کی اجازت دیں - اپنی سٹوری دیکھنے والے لوگوں کو ردعمل اور جواب دینے کی اجازت دیں + جو لوگ آپ کی سٹوری دیکھ سکتے ہیں انہیں اس پر ری ایکٹ کرنے اور جواب دینے کی اجازت دیں Signal کنکشنز @@ -5057,11 +5128,11 @@ - آپ نے %1$s کی سٹوری پر ردعمل دیا + آپ نے %1$s کی سٹوری پر ری ایکٹ کیا - آپ کی سٹوری پر ردعمل دیا + آپ کی سٹوری پر ری ایکٹ کیا - سٹوری پر ردعمل دیا + سٹوری پر ری ایکٹ کیا @@ -5087,7 +5158,7 @@ عطیہ کی تصدیق کریں - بھیجیں بطرف + بھیجیں بنام وصول کنندہ کو عطیہ کے حوالے سے ایک 1 پر 1 پیغام ارسال کر کے مطلع کیا جائے گا۔ ذیل میں اپنا ذاتی پیغام شامل کریں۔ @@ -5601,5 +5672,15 @@ یوزر نیم حذف کریں + + + گھنٹہ + + منٹ + + سیٹ کریں + + اسکرین لاک کا اطلاق ہونے سے پہلے کم از کم وقت 1 منٹ ہے۔ + diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml index 8bc4f750e5..5ee5980cd5 100644 --- a/app/src/main/res/values-v21/themes.xml +++ b/app/src/main/res/values-v21/themes.xml @@ -43,6 +43,7 @@ + + @@ -331,4 +338,13 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ef27f4552e..f8a7c6de2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ https://support.signal.org/hc/articles/360007321171 https://signal.me/#u/%1$s signal.me/#u/%1$s + https://support.signal.org/hc/articles/5389476324250 Yes No @@ -542,6 +543,15 @@ +%1$d + + Re-link your devices + + The devices you added were unlinked when your device was unregistered. Go to Settings to re-link any devices. + + Open settings + + Later + Select members @@ -953,6 +963,16 @@ Username created Username copied + + Couldn\'t delete username. Try again later. + + Username deleted + + + + Something went wrong with your username, it\'s no longer assigned to your account. You can try and set it again or choose a new one. + + Fix now @@ -1156,8 +1176,8 @@ New group Invite friends Use SMS - Appearance - Add photo + Chat colors + Add a profile photo Replies @@ -1585,9 +1605,20 @@ Create new PIN https://support.signal.org/hc/articles/360007059792 + + Send SMS code + + Signal Registration - Need Help with reregister PIN for Android + + Your PIN is a %1$d+ digit code you created that can be numeric or alphanumeric.\n\nIf you can’t remember your PIN, you can create a new one. + + If you can’t remember your PIN, you can create a new one. + + You\'ve run out of PIN guesses, but you can still access your Signal account by creating a new PIN. + Warning - If you disable the PIN, you will lose all data when you re-register Signal unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled. + If you disable the PIN, you will lose all data when you re-register Signal unless you manually back up and restore. You cannot turn on Registration Lock while the PIN is disabled. Disable PIN @@ -1771,11 +1802,18 @@ Signal needs the contacts and media permissions to help you connect with friends and send messages. Your contacts are uploaded using Signal\'s private contact discovery, which means they are end-to-end encrypted and never visible to the Signal service. Signal needs the contacts permission to help you connect with friends. Your contacts are uploaded using Signal\'s private contact discovery, which means they are end-to-end encrypted and never visible to the Signal service. You\'ve made too many attempts to register this number. Please try again later. + + You\'ve made too many attempts to register this number. Please try again in %s. Unable to connect to service. Please check network connection and try again. Non-standard number format The number you entered (%1$s) appears to be a non-standard format.\n\nDid you mean %2$s? Molly Android - Phone Number Format + Call requested + + SMS requested + + Verification code requested You are now %d step away from submitting a debug log. You are now %d steps away from submitting a debug log. @@ -1795,6 +1833,16 @@ Call Verification Code Resend Code + + Having trouble registering? + + • Make sure your phone has a cellular signal to receive your SMS or call\n • Confirm you can receive a phone call to the number\n • Check that you have entered your phone number correctly. + + For more information, please follow these troubleshooting steps or Contact Support + + these troubleshooting steps + + Contact Support Turn on Registration Lock? @@ -1962,6 +2010,10 @@ Payment Scheduled message + + Your message history has been merged + + %1$s belongs to %2$s Molly update @@ -2098,6 +2150,8 @@ Reacted %1$s to your file. Reacted %1$s to your audio. Reacted %1$s to your view-once media. + + Reacted %1$s to your payment. Reacted %1$s to your sticker. This message was deleted. @@ -3296,6 +3350,8 @@ Enter your PIN Enter the PIN you created for your account. This is different from your SMS verification code. + + Enter the PIN you created for your account. Enter alphanumeric PIN Enter numeric PIN Incorrect PIN. Try again. @@ -3399,7 +3455,10 @@ Your backup contains a very large file that cannot be backed up. Please delete it and create a new backup. Tap to manage backups. Wrong number? + Call me (%1$02d:%2$02d) + + Resend Code (%1$02d:%2$02d) Contact Signal Support Signal Registration - Verification Code for Android Incorrect code @@ -3407,6 +3466,18 @@ Unknown See my phone number Find me by phone number + + Phone number + + Choose who can see your phone number and who can contact you on Molly with it. + + Who can see my number + + Nobody will see your phone number on Molly + + Who can find me by number + + Your phone number will be visible to people and groups you message. People who have your number in their phone contacts will also see it on Molly. Everyone My contacts Nobody @@ -4509,7 +4580,7 @@ Your donation could not be sent because of a network error. Check your connection and try again. - Donation to %1$s + Donation on behalf of %1$s %1$s donated to Signal on your behalf @@ -5323,7 +5394,7 @@ Exporting SMS messages - This may take awhile + This may take a while Exporting %1$d of %2$d… @@ -5602,5 +5673,15 @@ Delete username + + + h + + m + + Set + + Minimum time before screen lock applies is 1 minute. + diff --git a/app/src/main/res/values/strings2.xml b/app/src/main/res/values/strings2.xml index 545f4f45ff..4a0a333113 100644 --- a/app/src/main/res/values/strings2.xml +++ b/app/src/main/res/values/strings2.xml @@ -92,4 +92,5 @@ Molly can periodically check for new releases and ask you to install them. You will be notified when updates are available %1$s is not registered with Signal + s diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 68f7a48984..7f8d51ec34 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -465,13 +465,6 @@ 0dp - - @@ -501,26 +494,14 @@ - - - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index d059128fe0..693722f6e6 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -47,6 +47,7 @@ diff --git a/app/src/spinner/AndroidManifest.xml b/app/src/spinner/AndroidManifest.xml index 3b9ab3344f..40ebd5ba92 100644 --- a/app/src/spinner/AndroidManifest.xml +++ b/app/src/spinner/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/app/src/test/java/org/thoughtcrime/securesms/mediasend/MediaRepositoryTest.kt b/app/src/test/java/org/thoughtcrime/securesms/mediasend/MediaRepositoryTest.kt index 70e59177e0..f6d7dd3a7f 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/mediasend/MediaRepositoryTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/mediasend/MediaRepositoryTest.kt @@ -28,7 +28,8 @@ import java.util.Optional class MediaRepositoryTest { @Rule - @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() + @JvmField + val mockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var staticMediaUtilMock: MockedStatic @@ -129,7 +130,7 @@ class MediaRepositoryTest { videoGif, bucketId, caption, - transformProperties, + transformProperties ) } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt b/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt index 897659c83f..ebb9725d46 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/megaphone/RemoteMegaphoneRepositoryTest.kt @@ -199,7 +199,7 @@ class RemoteMegaphoneRepositoryTest { primaryActionData, secondaryActionData, snoozedAt, - seenCount, + seenCount ) } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/BaseRecipientTest.kt b/app/src/test/java/org/thoughtcrime/securesms/recipients/BaseRecipientTest.kt index 586e5ebfb1..e4a54deab5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/recipients/BaseRecipientTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/recipients/BaseRecipientTest.kt @@ -23,7 +23,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore abstract class BaseRecipientTest { @Rule - @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() + @JvmField + val mockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var applicationDependenciesStaticMock: MockedStatic diff --git a/app/src/test/java/org/thoughtcrime/securesms/registration/PushChallengeRequestTest.java b/app/src/test/java/org/thoughtcrime/securesms/registration/PushChallengeRequestTest.java index 05443cd7d7..78ff7f1a2e 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/registration/PushChallengeRequestTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/registration/PushChallengeRequestTest.java @@ -34,7 +34,7 @@ public final class PushChallengeRequestTest { public void getPushChallengeBlocking_returns_absent_if_times_out() { SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class); - Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 50L); + Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, "session ID", Optional.of("token"), 50L); assertFalse(challenge.isPresent()); } @@ -44,7 +44,7 @@ public void getPushChallengeBlocking_waits_for_specified_period() { SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class); long startTime = System.currentTimeMillis(); - PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 250L); + PushChallengeRequest.getPushChallengeBlocking(signal, "session ID", Optional.of("token"), 250L); long duration = System.currentTimeMillis() - startTime; assertThat(duration, greaterThanOrEqualTo(250L)); @@ -56,14 +56,14 @@ public void getPushChallengeBlocking_completes_fast_if_posted_to_event_bus() thr doAnswer(invocation -> { AsyncTask.execute(() -> PushChallengeRequest.postChallengeResponse("CHALLENGE")); return null; - }).when(signal).requestRegistrationPushChallenge("token", "+123456"); + }).when(signal).requestRegistrationPushChallenge("session ID", "token"); long startTime = System.currentTimeMillis(); - Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 500L); + Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, "session ID", Optional.of("token"), 500L); long duration = System.currentTimeMillis() - startTime; assertThat(duration, lessThan(500L)); - verify(signal).requestRegistrationPushChallenge("token", "+123456"); + verify(signal).requestRegistrationPushChallenge("session ID", "token"); verifyNoMoreInteractions(signal); assertTrue(challenge.isPresent()); @@ -75,7 +75,7 @@ public void getPushChallengeBlocking_returns_fast_if_no_fcm_token_supplied() { SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class); long startTime = System.currentTimeMillis(); - PushChallengeRequest.getPushChallengeBlocking(signal, Optional.empty(), "+123456", 500L); + PushChallengeRequest.getPushChallengeBlocking(signal, "session ID", Optional.empty(), 500L); long duration = System.currentTimeMillis() - startTime; assertThat(duration, lessThan(500L)); @@ -85,7 +85,7 @@ public void getPushChallengeBlocking_returns_fast_if_no_fcm_token_supplied() { public void getPushChallengeBlocking_returns_absent_if_no_fcm_token_supplied() { SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class); - Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.empty(), "+123456", 500L); + Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, "session ID", Optional.empty(), 500L); verifyNoInteractions(signal); assertFalse(challenge.isPresent()); @@ -97,7 +97,7 @@ public void getPushChallengeBlocking_returns_absent_if_any_IOException_is_thrown doThrow(new IOException()).when(signal).requestRegistrationPushChallenge(any(), any()); - Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 500L); + Optional challenge = PushChallengeRequest.getPushChallengeBlocking(signal, "session ID", Optional.of("token"), 500L); assertFalse(challenge.isPresent()); } diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt index 9f69880382..8cf104b635 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/ContactRecordProcessorTest.kt @@ -188,6 +188,108 @@ class ContactRecordProcessorTest { assertFalse(result) } + @Test + fun `isInvalid, valid E164, true`() { + // GIVEN + val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) + + val record = buildRecord { + setServiceId(ACI_B.toString()) + setServiceE164(E164_B) + } + + // WHEN + val result = subject.isInvalid(record) + + // THEN + assertFalse(result) + } + + @Test + fun `isInvalid, invalid E164 (missing +), true`() { + // GIVEN + val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) + + val record = buildRecord { + setServiceId(ACI_B.toString()) + setServiceE164("15551234567") + } + + // WHEN + val result = subject.isInvalid(record) + + // THEN + assertTrue(result) + } + + @Test + fun `isInvalid, invalid E164 (contains letters), true`() { + // GIVEN + val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) + + val record = buildRecord { + setServiceId(ACI_B.toString()) + setServiceE164("+1555ABC4567") + } + + // WHEN + val result = subject.isInvalid(record) + + // THEN + assertTrue(result) + } + + @Test + fun `isInvalid, invalid E164 (no numbers), true`() { + // GIVEN + val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) + + val record = buildRecord { + setServiceId(ACI_B.toString()) + setServiceE164("+") + } + + // WHEN + val result = subject.isInvalid(record) + + // THEN + assertTrue(result) + } + + @Test + fun `isInvalid, invalid E164 (too many numbers), true`() { + // GIVEN + val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) + + val record = buildRecord { + setServiceId(ACI_B.toString()) + setServiceE164("+12345678901234567890") + } + + // WHEN + val result = subject.isInvalid(record) + + // THEN + assertTrue(result) + } + + @Test + fun `isInvalid, invalid E164 (starts with zero), true`() { + // GIVEN + val subject = ContactRecordProcessor(ACI_A, PNI_A, E164_A, recipientTable) + + val record = buildRecord { + setServiceId(ACI_B.toString()) + setServiceE164("+05551234567") + } + + // WHEN + val result = subject.isInvalid(record) + + // THEN + assertTrue(result) + } + @Test fun `merge, e164MatchesButPnisDont pnpEnabled, keepLocal`() { // GIVEN @@ -311,9 +413,9 @@ class ContactRecordProcessorTest { } companion object { - val STORAGE_ID_A: StorageId = StorageId.forStoryDistributionList(byteArrayOf(1, 2, 3, 4)) - val STORAGE_ID_B: StorageId = StorageId.forStoryDistributionList(byteArrayOf(5, 6, 7, 8)) - val STORAGE_ID_C: StorageId = StorageId.forStoryDistributionList(byteArrayOf(5, 6, 7, 8)) + val STORAGE_ID_A: StorageId = StorageId.forContact(byteArrayOf(1, 2, 3, 4)) + val STORAGE_ID_B: StorageId = StorageId.forContact(byteArrayOf(5, 6, 7, 8)) + val STORAGE_ID_C: StorageId = StorageId.forContact(byteArrayOf(9, 10, 11, 12)) val ACI_A = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e")) val ACI_B = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed")) diff --git a/app/src/test/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenuTest.kt b/app/src/test/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenuTest.kt index 627f7bdefe..4a6cbd587a 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenuTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenuTest.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.dialogs import android.app.Application import android.content.Context import android.content.Intent +import android.net.Uri import androidx.fragment.app.Fragment import androidx.test.core.app.ApplicationProvider import org.junit.Assert.assertEquals @@ -15,6 +16,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +import org.signal.core.util.getParcelableExtraCompat import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.database.FakeMessageRecords import org.thoughtcrime.securesms.database.model.StoryType @@ -61,8 +63,8 @@ class StoryContextMenuTest { // THEN verify(fragment).startActivity(intentCaptor.capture()) val chooserIntent: Intent = intentCaptor.firstValue - val targetIntent: Intent = chooserIntent.getParcelableExtra(Intent.EXTRA_INTENT)!! - assertEquals(PartAuthority.getAttachmentPublicUri(PartAuthority.getAttachmentDataUri(attachmentId)), targetIntent.getParcelableExtra(Intent.EXTRA_STREAM)) + val targetIntent: Intent = chooserIntent.getParcelableExtraCompat(Intent.EXTRA_INTENT, Intent::class.java)!! + assertEquals(PartAuthority.getAttachmentPublicUri(PartAuthority.getAttachmentDataUri(attachmentId)), targetIntent.getParcelableExtraCompat(Intent.EXTRA_STREAM, Uri::class.java)) assertEquals(MediaUtil.IMAGE_JPEG, targetIntent.type) assertTrue(Intent.FLAG_GRANT_READ_URI_PERMISSION and chooserIntent.flags == Intent.FLAG_GRANT_READ_URI_PERMISSION) } @@ -82,7 +84,7 @@ class StoryContextMenuTest { // THEN verify(fragment).startActivity(intentCaptor.capture()) val chooserIntent: Intent = intentCaptor.firstValue - val targetIntent: Intent = chooserIntent.getParcelableExtra(Intent.EXTRA_INTENT)!! + val targetIntent: Intent = chooserIntent.getParcelableExtraCompat(Intent.EXTRA_INTENT, Intent::class.java)!! assertEquals(expected, targetIntent.getStringExtra(Intent.EXTRA_TEXT)) } @@ -102,7 +104,7 @@ class StoryContextMenuTest { // THEN verify(fragment).startActivity(intentCaptor.capture()) val chooserIntent: Intent = intentCaptor.firstValue - val targetIntent: Intent = chooserIntent.getParcelableExtra(Intent.EXTRA_INTENT)!! + val targetIntent: Intent = chooserIntent.getParcelableExtraCompat(Intent.EXTRA_INTENT, Intent::class.java)!! assertEquals(expected, targetIntent.getStringExtra(Intent.EXTRA_TEXT)) } @@ -124,7 +126,7 @@ class StoryContextMenuTest { // THEN verify(fragment).startActivity(intentCaptor.capture()) val chooserIntent: Intent = intentCaptor.firstValue - val targetIntent: Intent = chooserIntent.getParcelableExtra(Intent.EXTRA_INTENT)!! + val targetIntent: Intent = chooserIntent.getParcelableExtraCompat(Intent.EXTRA_INTENT, Intent::class.java)!! assertEquals(expected, targetIntent.getStringExtra(Intent.EXTRA_TEXT)) } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt index 3748cb1af2..f7e0825fc1 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt @@ -104,7 +104,7 @@ class StoryViewerViewModelTest { val testSubject = StoryViewerViewModel( StoryViewerArgs( recipientId = startStory, - isInHiddenStoryMode = false, + isInHiddenStoryMode = false ), repository ) @@ -131,7 +131,7 @@ class StoryViewerViewModelTest { val testSubject = StoryViewerViewModel( StoryViewerArgs( recipientId = startStory, - isInHiddenStoryMode = false, + isInHiddenStoryMode = false ), repository ) @@ -158,7 +158,7 @@ class StoryViewerViewModelTest { val testSubject = StoryViewerViewModel( StoryViewerArgs( recipientId = startStory, - isInHiddenStoryMode = false, + isInHiddenStoryMode = false ), repository ) @@ -185,7 +185,7 @@ class StoryViewerViewModelTest { val testSubject = StoryViewerViewModel( StoryViewerArgs( recipientId = startStory, - isInHiddenStoryMode = false, + isInHiddenStoryMode = false ), repository ) @@ -212,7 +212,7 @@ class StoryViewerViewModelTest { val testSubject = StoryViewerViewModel( StoryViewerArgs( recipientId = startStory, - isInHiddenStoryMode = false, + isInHiddenStoryMode = false ), repository ) diff --git a/app/src/test/java/org/thoughtcrime/securesms/testing/ProxySQLCipherOpenHelper.kt b/app/src/test/java/org/thoughtcrime/securesms/testing/ProxySQLCipherOpenHelper.kt index 566f861f59..bc87d76bad 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/testing/ProxySQLCipherOpenHelper.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/testing/ProxySQLCipherOpenHelper.kt @@ -14,7 +14,7 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase as SQLCipherSQLiteDatabase class ProxySQLCipherOpenHelper( context: Application, val readableDatabase: AndroidSQLiteDatabase, - val writableDatabase: AndroidSQLiteDatabase, + val writableDatabase: AndroidSQLiteDatabase ) : SignalDatabase(context, DatabaseSecret(ByteArray(32).apply { SecureRandom().nextBytes(this) }), AttachmentSecret()) { constructor(context: Application, testOpenHelper: TestSQLiteOpenHelper) : this(context, testOpenHelper.readableDatabase, testOpenHelper.writableDatabase) diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/NameUtil_getAbbreviation.kt b/app/src/test/java/org/thoughtcrime/securesms/util/NameUtil_getAbbreviation.kt index 8f0c13becd..3de13abc34 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/util/NameUtil_getAbbreviation.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/util/NameUtil_getAbbreviation.kt @@ -33,7 +33,7 @@ class NameUtil_getAbbreviation( arrayOf("љabc ђ123", "љђ"), // Works on device, but for whatever reason doesn't work in robolectric // arrayOf("Bob \uD83C\uDDE8\uD83C\uDDFF", "B\uD83C\uDDE8\uD83C\uDDFF"), - arrayOf("", null), + arrayOf("", null) ) } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.java b/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.java index 8925473f6d..00f0cce462 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.java @@ -11,12 +11,12 @@ public class UsernameUtilTest { public void checkUsername_tooShort() { assertEquals(UsernameUtil.InvalidReason.TOO_SHORT, UsernameUtil.checkUsername(null).get()); assertEquals(UsernameUtil.InvalidReason.TOO_SHORT, UsernameUtil.checkUsername("").get()); - assertEquals(UsernameUtil.InvalidReason.TOO_SHORT, UsernameUtil.checkUsername("abc").get()); + assertEquals(UsernameUtil.InvalidReason.TOO_SHORT, UsernameUtil.checkUsername("ab").get()); } @Test public void checkUsername_tooLong() { - assertEquals(UsernameUtil.InvalidReason.TOO_LONG, UsernameUtil.checkUsername("abcdefghijklmnopqrstuvwxyz1").get()); + assertEquals(UsernameUtil.InvalidReason.TOO_LONG, UsernameUtil.checkUsername("abcdefghijklmnopqrstuvwxyz1234567").get()); } @Test diff --git a/build-logic/plugins/build.gradle.kts b/build-logic/plugins/build.gradle.kts new file mode 100644 index 0000000000..bd92562e9b --- /dev/null +++ b/build-logic/plugins/build.gradle.kts @@ -0,0 +1,26 @@ + + +plugins { + `kotlin-dsl` + id("groovy-gradle-plugin") +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +kotlinDslPluginOptions { + jvmTarget.set("11") +} + +dependencies { + implementation(libs.kotlin.gradle.plugin) + implementation(libs.android.library) + implementation(libs.android.application) + implementation(project(":tools")) + + // These allow us to reference the dependency catalog inside of our compiled plugins + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(testLibs.javaClass.superclass.protectionDomain.codeSource.location)) +} diff --git a/build-logic/plugins/src/main/java/android-constants.gradle.kts b/build-logic/plugins/src/main/java/android-constants.gradle.kts new file mode 100644 index 0000000000..fc6043d6bc --- /dev/null +++ b/build-logic/plugins/src/main/java/android-constants.gradle.kts @@ -0,0 +1,6 @@ +// MOLLY: Edit Dockerfile to download the same SDK packages +val signalBuildToolsVersion by extra("32.0.0") +val signalCompileSdkVersion by extra("android-33") +val signalTargetSdkVersion by extra(31) +val signalMinSdkVersion by extra(23) +val signalJavaVersion by extra(JavaVersion.VERSION_11) diff --git a/build-logic/plugins/src/main/java/signal-library.gradle.kts b/build-logic/plugins/src/main/java/signal-library.gradle.kts new file mode 100644 index 0000000000..051fc2f31d --- /dev/null +++ b/build-logic/plugins/src/main/java/signal-library.gradle.kts @@ -0,0 +1,69 @@ +@file:Suppress("UnstableApiUsage") + +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.accessors.dm.LibrariesForTestLibs +import org.gradle.api.JavaVersion +import org.gradle.kotlin.dsl.extra + +val libs = the() +val testLibs = the() + +val signalBuildToolsVersion: String by extra +val signalCompileSdkVersion: String by extra +val signalTargetSdkVersion: Int by extra +val signalMinSdkVersion: Int by extra +val signalJavaVersion: JavaVersion by extra + +plugins { + id("com.android.library") + id("kotlin-android") + id("android-constants") +} + +android { + buildToolsVersion = signalBuildToolsVersion + compileSdkVersion = signalCompileSdkVersion + + defaultConfig { + minSdk = signalMinSdkVersion + targetSdk = signalTargetSdkVersion + multiDexEnabled = true + } + + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = signalJavaVersion + targetCompatibility = signalJavaVersion + } + + kotlinOptions { + jvmTarget = "11" + } + + lint { + disable += "InvalidVectorPath" + } +} + +dependencies { + lintChecks(project(":lintchecks")) + + coreLibraryDesugaring(libs.android.tools.desugar) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.fragment.ktx) + implementation(libs.androidx.annotation) + implementation(libs.androidx.appcompat) + implementation(libs.rxjava3.rxandroid) + implementation(libs.rxjava3.rxjava) + implementation(libs.rxjava3.rxkotlin) + implementation(libs.androidx.multidex) + + testImplementation(testLibs.junit.junit) + testImplementation(testLibs.mockito.core) + testImplementation(testLibs.mockito.android) + testImplementation(testLibs.mockito.kotlin) + testImplementation(testLibs.robolectric.robolectric) + testImplementation(testLibs.androidx.test.core) + testImplementation(testLibs.androidx.test.core.ktx) +} diff --git a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts new file mode 100644 index 0000000000..609380dd5d --- /dev/null +++ b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts @@ -0,0 +1,74 @@ +@file:Suppress("UnstableApiUsage") + +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.accessors.dm.LibrariesForTestLibs +import org.gradle.api.JavaVersion +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.the + +val libs = the() +val testLibs = the() + +val signalBuildToolsVersion: String by extra +val signalCompileSdkVersion: String by extra +val signalTargetSdkVersion: Int by extra +val signalMinSdkVersion: Int by extra +val signalJavaVersion: JavaVersion by extra + +plugins { + id("com.android.application") + id("kotlin-android") + id("android-constants") +} + +android { + buildToolsVersion = signalBuildToolsVersion + compileSdkVersion = signalCompileSdkVersion + + defaultConfig { + versionCode = 1 + versionName = "1.0" + + minSdk = signalMinSdkVersion + targetSdk = signalTargetSdkVersion + multiDexEnabled = true + } + + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = signalJavaVersion + targetCompatibility = signalJavaVersion + } + + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + coreLibraryDesugaring(libs.android.tools.desugar) + + implementation(project(":core-util")) + + coreLibraryDesugaring(libs.android.tools.desugar) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.fragment.ktx) + implementation(libs.androidx.annotation) + implementation(libs.androidx.appcompat) + implementation(libs.rxjava3.rxandroid) + implementation(libs.rxjava3.rxjava) + implementation(libs.rxjava3.rxkotlin) + implementation(libs.androidx.multidex) + implementation(libs.material.material) + implementation(libs.androidx.constraintlayout) + + testImplementation(testLibs.junit.junit) + testImplementation(testLibs.mockito.core) + testImplementation(testLibs.mockito.android) + testImplementation(testLibs.mockito.kotlin) + testImplementation(testLibs.robolectric.robolectric) + testImplementation(testLibs.androidx.test.core) + testImplementation(testLibs.androidx.test.core.ktx) +} diff --git a/app/translations.gradle b/build-logic/plugins/src/main/java/translations.gradle similarity index 89% rename from app/translations.gradle rename to build-logic/plugins/src/main/java/translations.gradle index 08f7d2b13e..e7e69ed4a4 100644 --- a/app/translations.gradle +++ b/build-logic/plugins/src/main/java/translations.gradle @@ -1,7 +1,7 @@ import groovy.io.FileType import groovy.transform.stc.ClosureParams import groovy.transform.stc.SimpleType -import org.signal.StaticIpResolver +import org.signal.buildtools.StaticIpResolver ext { autoResConfig = this.&autoResConfig @@ -124,15 +124,16 @@ task postTranslateIpFetch { group 'Translate' description 'Fetches static IPs for core hosts and writes them to static-ips.gradle' doLast { + def staticIpResolver = new StaticIpResolver() new File(projectDir, "static-ips.gradle").text = """ - ext.service_ips='${StaticIpResolver.resolveToBuildConfig("chat.signal.org")}' - ext.storage_ips='${StaticIpResolver.resolveToBuildConfig("storage.signal.org")}' - ext.cdn_ips='${StaticIpResolver.resolveToBuildConfig("cdn.signal.org")}' - ext.cdn2_ips='${StaticIpResolver.resolveToBuildConfig("cdn2.signal.org")}' - ext.cds_ips='${StaticIpResolver.resolveToBuildConfig("api.directory.signal.org")}' - ext.kbs_ips='${StaticIpResolver.resolveToBuildConfig("api.backup.signal.org")}' - ext.sfu_ips='${StaticIpResolver.resolveToBuildConfig("sfu.voip.signal.org")}' - ext.content_proxy_ips='${StaticIpResolver.resolveToBuildConfig("contentproxy.signal.org")}' + ext.service_ips='${staticIpResolver.resolveToBuildConfig("chat.signal.org")}' + ext.storage_ips='${staticIpResolver.resolveToBuildConfig("storage.signal.org")}' + ext.cdn_ips='${staticIpResolver.resolveToBuildConfig("cdn.signal.org")}' + ext.cdn2_ips='${staticIpResolver.resolveToBuildConfig("cdn2.signal.org")}' + ext.cds_ips='${staticIpResolver.resolveToBuildConfig("api.directory.signal.org")}' + ext.kbs_ips='${staticIpResolver.resolveToBuildConfig("api.backup.signal.org")}' + ext.sfu_ips='${staticIpResolver.resolveToBuildConfig("sfu.voip.signal.org")}' + ext.content_proxy_ips='${staticIpResolver.resolveToBuildConfig("contentproxy.signal.org")}' """.stripIndent().trim() } } diff --git a/build-logic/settings.gradle b/build-logic/settings.gradle new file mode 100644 index 0000000000..dc0507652e --- /dev/null +++ b/build-logic/settings.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = "build-logic" + +include ':plugins' +include ':tools' + +apply from: '../dependencies.gradle' diff --git a/build-logic/tools/build.gradle.kts b/build-logic/tools/build.gradle.kts new file mode 100644 index 0000000000..b4efd4a4c5 --- /dev/null +++ b/build-logic/tools/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("org.jetbrains.kotlin.jvm") version "1.6.21" + id("java-library") +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +dependencies { + implementation(libs.dnsjava) + testImplementation(testLibs.junit.junit) + testImplementation(testLibs.mockk) +} diff --git a/build-logic/tools/src/main/java/org/signal/buildtools/StaticIpResolver.kt b/build-logic/tools/src/main/java/org/signal/buildtools/StaticIpResolver.kt new file mode 100644 index 0000000000..bcc260a63e --- /dev/null +++ b/build-logic/tools/src/main/java/org/signal/buildtools/StaticIpResolver.kt @@ -0,0 +1,97 @@ +package org.signal.buildtools + +import org.xbill.DNS.ARecord +import org.xbill.DNS.Lookup +import org.xbill.DNS.Record +import org.xbill.DNS.SimpleResolver +import org.xbill.DNS.Type +import java.net.UnknownHostException +import kotlin.streams.toList + +/** + * A tool to resolve hostname to static IPs. + * Feeds into our custom DNS resolver to provide a static IP fallback for our services. + */ +class StaticIpResolver @JvmOverloads constructor( + private val recordFetcher: RecordFetcher = RealRecordFetcher +) { + + /** + * Resolves a hostname to a list of IPs, represented as a Java array declaration. e.g. + * + * ```java + * new String[]{"192.168.1.1", "192.168.1.2"} + * ``` + * + * This is intended to be injected as a BuildConfig. + */ + fun resolveToBuildConfig(hostName: String): String { + val ips: List = resolve(hostName) + val builder = StringBuilder() + + builder.append("new String[]{") + + ips.forEachIndexed { i, ip -> + builder.append("\"").append(ip).append("\"") + + if (i < ips.size - 1) { + builder.append(",") + } + } + + return builder.append("}").toString() + } + + private fun resolve(hostname: String): List { + val ips: MutableSet = mutableSetOf() + + // Run several resolves to mitigate DNS round robin + for (i in 1..10) { + ips.addAll(resolveOnce(hostname)) + } + + return ips.stream().sorted().toList() + } + + private fun resolveOnce(hostName: String): List { + try { + val records = recordFetcher.fetchRecords(hostName) + if (records != null) { + return records + .filter { it.type == Type.A } + .map { it as ARecord } + .map { it.address } + .map { it.hostAddress } + .filterNotNull() + } else { + throw IllegalStateException("Failed to resolve host! Lookup did not return any records.. $hostName") + } + } catch (e: UnknownHostException) { + throw IllegalStateException("Failed to resolve host! $hostName", e) + } + } + + interface RecordFetcher { + fun fetchRecords(hostName: String): Array? + } + + private object RealRecordFetcher : RecordFetcher { + override fun fetchRecords(hostName: String): Array? { + val resolver = SimpleResolver("1.1.1.1") + val lookup: Lookup = doLookup(hostName) + + lookup.setResolver(resolver) + + return lookup.run() + } + + @Throws(UnknownHostException::class) + private fun doLookup(hostname: String): Lookup { + try { + return Lookup(hostname) + } catch (e: Throwable) { + throw UnknownHostException() + } + } + } +} diff --git a/build-logic/tools/src/test/java/org/signal/buildtools/StaticIpResolverTest.kt b/build-logic/tools/src/test/java/org/signal/buildtools/StaticIpResolverTest.kt new file mode 100644 index 0000000000..f52d9df389 --- /dev/null +++ b/build-logic/tools/src/test/java/org/signal/buildtools/StaticIpResolverTest.kt @@ -0,0 +1,59 @@ +package org.signal.buildtools + +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Test +import org.xbill.DNS.ARecord +import org.xbill.DNS.DClass +import org.xbill.DNS.Name +import org.xbill.DNS.Record +import java.net.Inet4Address + +class StaticIpResolverTest { + + companion object { + const val SIGNAL_DOT_ORG = "www.signal.org" + val SIGNAL_IP = byteArrayOf(123, 45, 67, 89) + val STRINGIFIED_IP = SIGNAL_IP.joinToString(".") + } + + @Test + fun `Given a hostname with records, when I resolveToBuildConfig, then I expect a matching IP`() { + val staticIpResolver = StaticIpResolver( + FakeRecordFetcher( + mapOf( + SIGNAL_DOT_ORG to arrayOf( + ARecord( + Name.fromString("www."), + DClass.ANY, + 0L, + mockk { + every { address } returns SIGNAL_IP + every { hostAddress } returns STRINGIFIED_IP + } + ) + ) + ) + ) + ) + val actual = staticIpResolver.resolveToBuildConfig(SIGNAL_DOT_ORG) + val expected = """ + new String[]{"$STRINGIFIED_IP"} + """.trimIndent() + + assertEquals(expected, actual) + } + + @Test(expected = IllegalStateException::class) + fun `Given a hostname without records, when I resolveToBuildConfig, then I expect`() { + val staticIpResolver = StaticIpResolver(FakeRecordFetcher(emptyMap())) + staticIpResolver.resolveToBuildConfig(SIGNAL_DOT_ORG) + } + + private class FakeRecordFetcher(private val recordMap: Map?>) : StaticIpResolver.RecordFetcher { + override fun fetchRecords(hostName: String): Array? { + return recordMap[hostName] + } + } +} diff --git a/build.gradle b/build.gradle index 746f3340d9..42c2f39d67 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.gradle.api.services.BuildService + buildscript { ext.kotlin_version = '1.7.20' repositories { @@ -8,20 +10,13 @@ buildscript { includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824' } } - maven { - url "https://plugins.gradle.org/m2/" - content { - includeModule 'org.jlleitschuh.gradle', 'ktlint-gradle' - } - } } dependencies { classpath 'com.android.tools:r8:3.3.75' - classpath 'com.android.tools.build:gradle:7.2.2' - classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2' + classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1" classpath 'app.cash.exhaustive:exhaustive-gradle:0.1.1' classpath ('com.squareup.wire:wire-gradle-plugin:4.4.3') { exclude group: 'com.squareup.wire', module: 'wire-swift-generator' @@ -33,28 +28,10 @@ buildscript { } } -ext { - BUILD_TOOL_VERSION = '32.0.0' - // MOLLY: Edit Dockerfile to download the same SDK packages - COMPILE_SDK = 33 - TARGET_SDK = 31 - MINIMUM_SDK = 23 - - JAVA_VERSION = JavaVersion.VERSION_1_8 -} - wrapper { distributionType = Wrapper.DistributionType.ALL } -allprojects { - repositories { - google() - mavenCentral() - mavenLocal() - } -} - subprojects { ext.lib_signal_service_version_number = "2.15.3" ext.lib_signal_service_group_info = "org.whispersystems" @@ -72,17 +49,40 @@ subprojects { preserveFileTimestamps = false reproducibleFileOrder = true } + + // MOLLY: Prevent memory starvation in parallel execution + def limiterService = gradle.sharedServices.registerIfAbsent("concurrencyConstraint", BuildService.class) { + it.maxParallelUsages.set(2) + } + + //noinspection UnnecessaryQualifiedReference + def expensiveTaskClasses = [ + com.android.build.gradle.internal.lint.AndroidLintAnalysisTask, + com.android.build.gradle.internal.tasks.R8Task, + org.jetbrains.kotlin.gradle.tasks.KotlinCompile, + ] + + tasks.configureEach { task -> + if (expensiveTaskClasses.any { it.isInstance(task) }) { + usesService(limiterService) + } + } +} + +task buildQa { + group 'Verification' + description 'Quality Assurance for build logic.' + dependsOn gradle.includedBuild('build-logic').task(':tools:test') } task qa { group 'Verification' description 'Quality Assurance. Run before pushing.' dependsOn = [ + 'buildQa', ':libsignal-service:test', - ':libsignal-service:ktlintCheck', ':app:testProdGmsWebsiteReleaseUnitTest', ':app:lintProdGmsWebsiteRelease', - ':app:ktlintCheck', ':app:assembleProdFossWebsiteRelease', ] } @@ -90,3 +90,18 @@ task qa { task clean(type: Delete) { delete rootProject.buildDir } + +task format { + group 'Formatting' + description 'Runs the ktlint formatter on all sources in this project and included builds' + + def dependencyList = subprojects.collect { + tasks.findByPath(":${it.name}:ktlintFormat") + } + + dependencyList.removeIf { it == null} + dependencyList.add(0, gradle.includedBuild('build-logic').task(':plugins:ktlintFormat')) + dependencyList.add(0, gradle.includedBuild('build-logic').task(':tools:ktlintFormat')) + + dependsOn dependencyList +} \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index fe26224fae..0000000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -apply plugin: 'java-gradle-plugin' - -repositories { - google() - mavenCentral() -} - -dependencies { - implementation libs.dnsjava -} \ No newline at end of file diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle deleted file mode 100644 index 4e79e50209..0000000000 --- a/buildSrc/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -enableFeaturePreview('VERSION_CATALOGS') -apply from: '../dependencies.gradle' diff --git a/buildSrc/src/main/java/org/signal/StaticIpResolver.java b/buildSrc/src/main/java/org/signal/StaticIpResolver.java deleted file mode 100644 index d483a59336..0000000000 --- a/buildSrc/src/main/java/org/signal/StaticIpResolver.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.signal; - -import org.gradle.internal.impldep.org.eclipse.jgit.annotations.NonNull; -import org.xbill.DNS.ARecord; -import org.xbill.DNS.Lookup; -import org.xbill.DNS.Record; -import org.xbill.DNS.Resolver; -import org.xbill.DNS.SimpleResolver; -import org.xbill.DNS.Type; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -public final class StaticIpResolver { - - private StaticIpResolver() {} - - public static String resolveToBuildConfig(String hostName) { - String[] ips = resolve(hostName); - StringBuilder builder = new StringBuilder(); - builder.append("new String[]{"); - for (int i = 0; i < ips.length; i++) { - builder.append("\"").append(ips[i]).append("\""); - if (i < ips.length - 1) { - builder.append(","); - } - } - return builder.append("}").toString(); - } - - private static String[] resolve(String hostname) { - Set ips = new HashSet<>(); - - // Run several resolves to mitigate DNS round robin - for (int i = 0; i < 10; i++) { - ips.addAll(resolveOnce(hostname)); - } - - return ips.stream().sorted().toArray(String[]::new); - } - - private static List resolveOnce(String hostName) { - try { - Resolver resolver = new SimpleResolver("1.1.1.1"); - Lookup lookup = doLookup(hostName); - - lookup.setResolver(resolver); - - Record[] records = lookup.run(); - - if (records != null) { - return Arrays.stream(records) - .filter(r -> r.getType() == Type.A) - .map(r -> (ARecord) r) - .map(ARecord::getAddress) - .map(InetAddress::getHostAddress) - .collect(Collectors.toList()); - } else { - throw new IllegalStateException("Failed to resolve host! " + hostName); - } - } catch (UnknownHostException e) { - throw new IllegalStateException("Failed to resolve host! " + hostName); - } - } - - private static @NonNull Lookup doLookup(@NonNull String hostname) throws UnknownHostException { - try { - return new Lookup(hostname); - } catch (Throwable e) { - throw new UnknownHostException(); - } - } -} diff --git a/contacts/app/build.gradle b/contacts/app/build.gradle index 2548e9fdc6..82b9d2f202 100644 --- a/contacts/app/build.gradle +++ b/contacts/app/build.gradle @@ -1,49 +1,15 @@ plugins { - id 'com.android.application' - id 'kotlin-android' - id 'org.jlleitschuh.gradle.ktlint' + id 'signal-sample-app' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'org.signal.contactstest' defaultConfig { applicationId "org.signal.contactstest" - versionCode 1 - versionName "1.0" - - minSdkVersion 21 - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - kotlinOptions { - jvmTarget = '1.8' - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION } } -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" -} - dependencies { - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.activity.ktx - implementation libs.androidx.appcompat - implementation libs.material.material - implementation libs.androidx.constraintlayout - - testImplementation testLibs.junit.junit - implementation project(':contacts') - implementation project(':core-util') } \ No newline at end of file diff --git a/contacts/app/src/main/AndroidManifest.xml b/contacts/app/src/main/AndroidManifest.xml index f4ea47e829..3299863661 100644 --- a/contacts/app/src/main/AndroidManifest.xml +++ b/contacts/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - - \ No newline at end of file + \ No newline at end of file diff --git a/contacts/app/src/main/res/layout/parent_item.xml b/contacts/app/src/main/res/layout/parent_item.xml index e6552ceedc..f735f901d0 100644 --- a/contacts/app/src/main/res/layout/parent_item.xml +++ b/contacts/app/src/main/res/layout/parent_item.xml @@ -9,7 +9,7 @@ android:clipToPadding="false" android:clipChildren="false"> - - + \ No newline at end of file diff --git a/contacts/lib/build.gradle b/contacts/lib/build.gradle index 49840ac7ef..cede9f34dd 100644 --- a/contacts/lib/build.gradle +++ b/contacts/lib/build.gradle @@ -1,49 +1,11 @@ plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jlleitschuh.gradle.ktlint' + id 'signal-library' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - - kotlinOptions { - jvmTarget = '1.8' - } - - lintOptions { - disable 'InvalidVectorPath' - } -} - -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" + namespace 'org.signal.contacts' } dependencies { - lintChecks project(':lintchecks') - implementation project(':core-util') - - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.core.ktx - implementation libs.androidx.annotation - implementation libs.androidx.appcompat - - api libs.rxjava3.rxjava -} +} \ No newline at end of file diff --git a/contacts/lib/src/main/AndroidManifest.xml b/contacts/lib/src/main/AndroidManifest.xml index 75b64c9fbb..039401e2ff 100644 --- a/contacts/lib/src/main/AndroidManifest.xml +++ b/contacts/lib/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/contacts/lib/src/main/java/org/signal/contacts/SystemContactsRepository.kt b/contacts/lib/src/main/java/org/signal/contacts/SystemContactsRepository.kt index 423a6eda55..bb91ba1fbe 100644 --- a/contacts/lib/src/main/java/org/signal/contacts/SystemContactsRepository.kt +++ b/contacts/lib/src/main/java/org/signal/contacts/SystemContactsRepository.kt @@ -596,7 +596,7 @@ object SystemContactsRepository { ContactsContract.PhoneLookup.NUMBER, ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME, - ContactsContract.PhoneLookup.TYPE, + ContactsContract.PhoneLookup.TYPE ) context.contentResolver.query(uri, projection, null, null, null)?.use { contactCursor -> @@ -727,7 +727,7 @@ object SystemContactsRepository { photoUri = cursor.requireString(ContactsContract.CommonDataKinds.Phone.PHOTO_URI), number = e164Formatter(displayNumber), type = cursor.requireInt(ContactsContract.CommonDataKinds.Phone.TYPE), - label = cursor.requireString(ContactsContract.CommonDataKinds.Phone.LABEL), + label = cursor.requireString(ContactsContract.CommonDataKinds.Phone.LABEL) ) } else { Log.w(TAG, "Skipping phone entry with invalid number!") diff --git a/core-ui/build.gradle b/core-ui/build.gradle index cfc5538446..427f8f473f 100644 --- a/core-ui/build.gradle +++ b/core-ui/build.gradle @@ -1,6 +1,10 @@ -apply from: "$rootProject.projectDir/signalModule.gradle" +plugins { + id 'signal-library' +} android { + namespace 'org.signal.core.ui' + buildFeatures { compose true } diff --git a/core-ui/src/main/AndroidManifest.xml b/core-ui/src/main/AndroidManifest.xml index 49f27e02e6..7277dc362a 100644 --- a/core-ui/src/main/AndroidManifest.xml +++ b/core-ui/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/core-ui/src/main/java/org/signal/core/ui/Buttons.kt b/core-ui/src/main/java/org/signal/core/ui/Buttons.kt new file mode 100644 index 0000000000..c55947ccc5 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Buttons.kt @@ -0,0 +1,291 @@ +package org.signal.core.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ButtonElevation +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.signal.core.ui.theme.SignalTheme + +object Buttons { + + private val largeButtonContentPadding = PaddingValues( + horizontal = 24.dp, + vertical = 12.dp + ) + + private val mediumButtonContentPadding = PaddingValues( + horizontal = 24.dp, + vertical = 10.dp + ) + + private val smallButtonContentPadding = PaddingValues( + horizontal = 16.dp, + vertical = 8.dp + ) + + @Composable + fun LargePrimary( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: Shape = ButtonDefaults.shape, + colors: ButtonColors = ButtonDefaults.buttonColors(), + elevation: ButtonElevation? = ButtonDefaults.buttonElevation(), + border: BorderStroke? = null, + contentPadding: PaddingValues = largeButtonContentPadding, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable RowScope.() -> Unit + ) { + Button( + onClick = onClick, + modifier = modifier, + enabled = enabled, + shape = shape, + colors = colors, + elevation = elevation, + border = border, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content + ) + } + + @Composable + fun LargeTonal( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: Shape = ButtonDefaults.filledTonalShape, + colors: ButtonColors = ButtonDefaults.filledTonalButtonColors(), + elevation: ButtonElevation? = ButtonDefaults.filledTonalButtonElevation(), + border: BorderStroke? = null, + contentPadding: PaddingValues = largeButtonContentPadding, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable RowScope.() -> Unit + ) { + FilledTonalButton( + onClick = onClick, + modifier = modifier, + enabled = enabled, + shape = shape, + colors = colors, + elevation = elevation, + border = border, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content + ) + } + + @Composable + fun MediumTonal( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: Shape = ButtonDefaults.filledTonalShape, + colors: ButtonColors = ButtonDefaults.filledTonalButtonColors(), + elevation: ButtonElevation? = ButtonDefaults.filledTonalButtonElevation(), + border: BorderStroke? = null, + contentPadding: PaddingValues = mediumButtonContentPadding, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable RowScope.() -> Unit + ) { + FilledTonalButton( + onClick = onClick, + modifier = modifier, + enabled = enabled, + shape = shape, + colors = colors, + elevation = elevation, + border = border, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = content + ) + } + + @Composable + fun Small( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: Shape = ButtonDefaults.shape, + colors: ButtonColors = ButtonDefaults.buttonColors( + containerColor = SignalTheme.colors.colorSurface2, + contentColor = MaterialTheme.colorScheme.onSurface + ), + elevation: ButtonElevation? = ButtonDefaults.buttonElevation(), + border: BorderStroke? = null, + contentPadding: PaddingValues = smallButtonContentPadding, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable RowScope.() -> Unit + ) { + Button( + onClick = onClick, + modifier = modifier.heightIn(min = 32.dp), + enabled = enabled, + shape = shape, + colors = colors, + elevation = elevation, + border = border, + contentPadding = contentPadding, + interactionSource = interactionSource, + content = { + ProvideTextStyle(value = MaterialTheme.typography.labelMedium) { + content() + } + } + ) + } +} + +@Composable +private fun LargePrimaryButtonPreview( + darkMode: Boolean, + enabled: Boolean +) { + SignalTheme(isDarkMode = darkMode) { + Buttons.LargePrimary( + onClick = {}, + enabled = enabled + ) { + Text("Button") + } + } +} + +@Composable +private fun LargeTonalButtonPreview( + darkMode: Boolean, + enabled: Boolean +) { + SignalTheme(isDarkMode = darkMode) { + Buttons.LargeTonal( + onClick = {}, + enabled = enabled + ) { + Text("Button") + } + } +} + +@Composable +private fun MediumTonalButtonPreview( + darkMode: Boolean, + enabled: Boolean +) { + SignalTheme(isDarkMode = darkMode) { + Buttons.MediumTonal( + onClick = {}, + enabled = enabled + ) { + Text("Button") + } + } +} + +@Composable +private fun SmallButtonPreview( + darkMode: Boolean, + enabled: Boolean +) { + SignalTheme(isDarkMode = darkMode) { + Buttons.Small( + onClick = {}, + enabled = enabled + ) { + Text("Button") + } + } +} + +@Preview +@Composable +private fun LargePrimaryButtonPreview() { + Column { + Row { + LargePrimaryButtonPreview(darkMode = false, enabled = true) + Spacer(modifier = Modifier.width(10.dp)) + LargePrimaryButtonPreview(darkMode = true, enabled = true) + } + + Row { + LargePrimaryButtonPreview(darkMode = false, enabled = false) + Spacer(modifier = Modifier.width(10.dp)) + LargePrimaryButtonPreview(darkMode = true, enabled = false) + } + } +} + +@Preview +@Composable +private fun LargeTonalButtonPreview() { + Column { + Row { + LargeTonalButtonPreview(darkMode = false, enabled = true) + Spacer(modifier = Modifier.width(10.dp)) + LargeTonalButtonPreview(darkMode = true, enabled = true) + } + + Row { + LargeTonalButtonPreview(darkMode = false, enabled = false) + Spacer(modifier = Modifier.width(10.dp)) + LargeTonalButtonPreview(darkMode = true, enabled = false) + } + } +} + +@Preview +@Composable +private fun MediumTonalButtonPreview() { + Column { + Row { + MediumTonalButtonPreview(darkMode = false, enabled = true) + Spacer(modifier = Modifier.width(10.dp)) + MediumTonalButtonPreview(darkMode = true, enabled = true) + } + + Row { + MediumTonalButtonPreview(darkMode = false, enabled = false) + Spacer(modifier = Modifier.width(10.dp)) + MediumTonalButtonPreview(darkMode = true, enabled = false) + } + } +} + +@Preview +@Composable +private fun SmallButtonPreview() { + Column { + Row { + SmallButtonPreview(darkMode = false, enabled = true) + Spacer(modifier = Modifier.width(10.dp)) + SmallButtonPreview(darkMode = true, enabled = true) + } + + Row { + SmallButtonPreview(darkMode = false, enabled = false) + Spacer(modifier = Modifier.width(10.dp)) + SmallButtonPreview(darkMode = true, enabled = false) + } + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Dividers.kt b/core-ui/src/main/java/org/signal/core/ui/Dividers.kt new file mode 100644 index 0000000000..a64df564d9 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Dividers.kt @@ -0,0 +1,32 @@ +package org.signal.core.ui + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.signal.core.ui.theme.SignalTheme + +/** + * Thin divider lines for separating content. + */ +object Dividers { + @Composable + fun Default(modifier: Modifier = Modifier) { + Divider( + thickness = 1.5.dp, + color = MaterialTheme.colorScheme.surfaceVariant, + modifier = modifier.padding(vertical = 16.25.dp) + ) + } +} + +@Preview +@Composable +private fun DefaultPreview() { + SignalTheme(isDarkMode = false) { + Dividers.Default() + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Rows.kt b/core-ui/src/main/java/org/signal/core/ui/Rows.kt new file mode 100644 index 0000000000..0c09231de8 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Rows.kt @@ -0,0 +1,70 @@ +package org.signal.core.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.signal.core.ui.theme.SignalTheme + +object Rows { + /** + * A row consisting of a radio button and text, which takes up the full + * width of the screen. + */ + @Composable + fun RadioRow( + selected: Boolean, + text: String, + modifier: Modifier = Modifier + ) { + Row( + modifier = modifier + .fillMaxWidth() + .padding( + horizontal = dimensionResource(id = R.dimen.core_ui__gutter), + vertical = 16.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selected, + onClick = null, + modifier = Modifier.padding(end = 24.dp) + ) + + Text( + text = text, + style = MaterialTheme.typography.bodyLarge + ) + } + } +} + +@Preview +@Composable +private fun RadioRowPreview() { + SignalTheme(isDarkMode = false) { + var selected by remember { mutableStateOf(true) } + + Rows.RadioRow( + selected, + "RadioRow", + modifier = Modifier.clickable { + selected = !selected + } + ) + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt b/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt new file mode 100644 index 0000000000..ebc113dc9b --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt @@ -0,0 +1,80 @@ +package org.signal.core.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +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.graphics.painter.ColorPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.signal.core.ui.theme.SignalTheme + +@OptIn(ExperimentalMaterial3Api::class) +object Scaffolds { + @Composable + fun Settings( + title: String, + onNavigationClick: () -> Unit, + navigationIconPainter: Painter, + modifier: Modifier = Modifier, + navigationContentDescription: String? = null, + content: @Composable (PaddingValues) -> Unit + ) { + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = title, + style = MaterialTheme.typography.titleLarge + ) + }, + navigationIcon = { + IconButton( + onClick = onNavigationClick, + Modifier.padding(end = 16.dp) + ) { + Icon( + painter = navigationIconPainter, + contentDescription = navigationContentDescription + ) + } + } + ) + }, + modifier = modifier, + content = content + ) + } +} + +@Preview +@Composable +private fun SettingsScaffoldPreview() { + SignalTheme(isDarkMode = false) { + Scaffolds.Settings( + "Settings Scaffold", + onNavigationClick = {}, + navigationIconPainter = ColorPainter(Color.Black) + ) { paddingValues -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.padding(paddingValues).fillMaxSize() + ) { + Text("Content") + } + } + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Texts.kt b/core-ui/src/main/java/org/signal/core/ui/Texts.kt new file mode 100644 index 0000000000..662abea6f2 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/Texts.kt @@ -0,0 +1,40 @@ +package org.signal.core.ui + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.signal.core.ui.theme.SignalTheme + +object Texts { + /** + * Header row for settings pages. + */ + @Composable + fun SectionHeader( + text: String, + modifier: Modifier = Modifier + ) { + Text( + text = text, + style = MaterialTheme.typography.titleSmall, + modifier = modifier + .padding( + horizontal = dimensionResource(id = R.dimen.core_ui__gutter) + ) + .padding(top = 16.dp, bottom = 12.dp) + ) + } +} + +@Preview +@Composable +private fun SectionHeaderPreview() { + SignalTheme(isDarkMode = false) { + Texts.SectionHeader("Header") + } +} diff --git a/core-ui/src/main/java/org/signal/core/ui/theme/ExtendedColors.kt b/core-ui/src/main/java/org/signal/core/ui/theme/ExtendedColors.kt new file mode 100644 index 0000000000..7c7a8db8aa --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/theme/ExtendedColors.kt @@ -0,0 +1,58 @@ +package org.signal.core.ui.theme + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color + +@Immutable +data class ExtendedColors( + val neutralSurface: Color, + val colorOnCustom: Color, + val colorOnCustomVariant: Color, + val colorSurface1: Color, + val colorSurface2: Color, + val colorSurface3: Color, + val colorSurface4: Color, + val colorSurface5: Color, + val colorTransparent1: Color, + val colorTransparent2: Color, + val colorTransparent3: Color, + val colorTransparent4: Color, + val colorTransparent5: Color, + val colorNeutral: Color, + val colorNeutralVariant: Color, + val colorTransparentInverse1: Color, + val colorTransparentInverse2: Color, + val colorTransparentInverse3: Color, + val colorTransparentInverse4: Color, + val colorTransparentInverse5: Color, + val colorNeutralInverse: Color, + val colorNeutralVariantInverse: Color +) + +val LocalExtendedColors = staticCompositionLocalOf { + ExtendedColors( + neutralSurface = Color.Unspecified, + colorOnCustom = Color.Unspecified, + colorOnCustomVariant = Color.Unspecified, + colorSurface1 = Color.Unspecified, + colorSurface2 = Color.Unspecified, + colorSurface3 = Color.Unspecified, + colorSurface4 = Color.Unspecified, + colorSurface5 = Color.Unspecified, + colorTransparent1 = Color.Unspecified, + colorTransparent2 = Color.Unspecified, + colorTransparent3 = Color.Unspecified, + colorTransparent4 = Color.Unspecified, + colorTransparent5 = Color.Unspecified, + colorNeutral = Color.Unspecified, + colorNeutralVariant = Color.Unspecified, + colorTransparentInverse1 = Color.Unspecified, + colorTransparentInverse2 = Color.Unspecified, + colorTransparentInverse3 = Color.Unspecified, + colorTransparentInverse4 = Color.Unspecified, + colorTransparentInverse5 = Color.Unspecified, + colorNeutralInverse = Color.Unspecified, + colorNeutralVariantInverse = Color.Unspecified + ) +} diff --git a/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt b/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt index 3bb9fa8c7f..0c0539cd18 100644 --- a/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt +++ b/core-ui/src/main/java/org/signal/core/ui/theme/SignalTheme.kt @@ -5,12 +5,13 @@ import androidx.compose.material3.Typography import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.sp -private val typography = Typography().apply { +private val typography = Typography().run { copy( headlineLarge = headlineLarge.copy( lineHeight = 40.sp, @@ -85,6 +86,56 @@ private val lightColorScheme = lightColorScheme( outline = Color(0xFF808389) ) +private val lightExtendedColors = ExtendedColors( + neutralSurface = Color(0x99FFFFFF), + colorOnCustom = Color(0xFFFFFFFF), + colorOnCustomVariant = Color(0xB3FFFFFF), + colorSurface1 = Color(0xFFF2F5F9), + colorSurface2 = Color(0xFFEDF0F6), + colorSurface3 = Color(0xFFE8ECF4), + colorSurface4 = Color(0xFFE6EAF3), + colorSurface5 = Color(0xFFE3E7F1), + colorTransparent1 = Color(0x14FFFFFF), + colorTransparent2 = Color(0x29FFFFFF), + colorTransparent3 = Color(0x8FFFFFFF), + colorTransparent4 = Color(0xB8FFFFFF), + colorTransparent5 = Color(0xF5FFFFFF), + colorNeutral = Color(0xFFFFFFFF), + colorNeutralVariant = Color(0xB8FFFFFF), + colorTransparentInverse1 = Color(0x0A000000), + colorTransparentInverse2 = Color(0x14000000), + colorTransparentInverse3 = Color(0x66000000), + colorTransparentInverse4 = Color(0xB8000000), + colorTransparentInverse5 = Color(0xE0000000), + colorNeutralInverse = Color(0xFF121212), + colorNeutralVariantInverse = Color(0xFF5C5C5C) +) + +private val darkExtendedColors = ExtendedColors( + neutralSurface = Color(0x14FFFFFF), + colorOnCustom = Color(0xFFFFFFFF), + colorOnCustomVariant = Color(0xB3FFFFFF), + colorSurface1 = Color(0xFF23242A), + colorSurface2 = Color(0xFF272A31), + colorSurface3 = Color(0xFF2C2F37), + colorSurface4 = Color(0xFF2E3039), + colorSurface5 = Color(0xFF31343E), + colorTransparent1 = Color(0x0AFFFFFF), + colorTransparent2 = Color(0x1FFFFFFF), + colorTransparent3 = Color(0x29FFFFFF), + colorTransparent4 = Color(0x7AFFFFFF), + colorTransparent5 = Color(0xB8FFFFFF), + colorNeutral = Color(0xFF121212), + colorNeutralVariant = Color(0xFF5C5C5C), + colorTransparentInverse1 = Color(0x0A000000), + colorTransparentInverse2 = Color(0x14000000), + colorTransparentInverse3 = Color(0x29000000), + colorTransparentInverse4 = Color(0xB8000000), + colorTransparentInverse5 = Color(0xF5000000), + colorNeutralInverse = Color(0xE0FFFFFF), + colorNeutralVariantInverse = Color(0xA3FFFFFF) +) + private val darkColorScheme = darkColorScheme( primary = Color(0xFFB6C5FA), primaryContainer = Color(0xFF464B5C), @@ -110,9 +161,19 @@ fun SignalTheme( isDarkMode: Boolean, content: @Composable () -> Unit ) { - MaterialTheme( - colorScheme = if (isDarkMode) darkColorScheme else lightColorScheme, - typography = typography, - content = content - ) + val extendedColors = if (isDarkMode) darkExtendedColors else lightExtendedColors + + CompositionLocalProvider(LocalExtendedColors provides extendedColors) { + MaterialTheme( + colorScheme = if (isDarkMode) darkColorScheme else lightColorScheme, + typography = typography, + content = content + ) + } +} + +object SignalTheme { + val colors: ExtendedColors + @Composable + get() = LocalExtendedColors.current } diff --git a/core-ui/src/main/res/values-sw360dp/dimens.xml b/core-ui/src/main/res/values-sw360dp/dimens.xml new file mode 100644 index 0000000000..88ea03c837 --- /dev/null +++ b/core-ui/src/main/res/values-sw360dp/dimens.xml @@ -0,0 +1,4 @@ + + + 24dp + \ No newline at end of file diff --git a/core-ui/src/main/res/values/dimens.xml b/core-ui/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..0411eef7bb --- /dev/null +++ b/core-ui/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 16dp + \ No newline at end of file diff --git a/core-util/build.gradle b/core-util/build.gradle index a3672b04c7..c53ac1aad0 100644 --- a/core-util/build.gradle +++ b/core-util/build.gradle @@ -1,28 +1,11 @@ plugins { - id 'com.android.library' + id 'signal-library' id 'com.google.protobuf' - id 'kotlin-android' id 'kotlin-kapt' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - kotlinOptions { - jvmTarget = '1.8' - } + namespace 'org.signal.core.util' } protobuf { @@ -41,17 +24,8 @@ protobuf { } dependencies { - lintChecks project(':lintchecks') - - coreLibraryDesugaring libs.android.tools.desugar - - api libs.androidx.annotation - - implementation libs.androidx.core.ktx - implementation libs.androidx.lifecycle.common.java8 implementation libs.google.protobuf.javalite implementation libs.androidx.sqlite - implementation libs.rxjava3.rxjava testImplementation testLibs.junit.junit testImplementation testLibs.mockito.core diff --git a/core-util/src/main/AndroidManifest.xml b/core-util/src/main/AndroidManifest.xml index 801fdbe4e8..7277dc362a 100644 --- a/core-util/src/main/AndroidManifest.xml +++ b/core-util/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/core-util/src/main/java/org/signal/core/util/AsciiArt.kt b/core-util/src/main/java/org/signal/core/util/AsciiArt.kt index a8741f9854..c309d9583b 100644 --- a/core-util/src/main/java/org/signal/core/util/AsciiArt.kt +++ b/core-util/src/main/java/org/signal/core/util/AsciiArt.kt @@ -5,7 +5,7 @@ import kotlin.math.max class AsciiArt { - private class Table ( + private class Table( private val columns: List, private val rows: List> ) { diff --git a/core-util/src/main/java/org/signal/core/util/BundleExtensions.kt b/core-util/src/main/java/org/signal/core/util/BundleExtensions.kt new file mode 100644 index 0000000000..564888cd33 --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/BundleExtensions.kt @@ -0,0 +1,23 @@ +package org.signal.core.util + +import android.os.Build +import android.os.Bundle +import android.os.Parcelable + +fun Bundle.getParcelableCompat(key: String, clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= 33) { + this.getParcelable(key, clazz) + } else { + @Suppress("DEPRECATION") + this.getParcelable(key) + } +} + +fun Bundle.getParcelableArrayListCompat(key: String, clazz: Class): ArrayList? { + return if (Build.VERSION.SDK_INT >= 33) { + this.getParcelableArrayList(key, clazz) + } else { + @Suppress("DEPRECATION") + this.getParcelableArrayList(key) + } +} diff --git a/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt b/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt index 0a5b290c7d..f00b91f009 100644 --- a/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/CursorExtensions.kt @@ -113,6 +113,16 @@ fun Cursor.readToSingleInt(defaultValue: Int = 0): Int { } } +fun Cursor.readToSingleBoolean(defaultValue: Boolean = false): Boolean { + return use { + if (it.moveToFirst()) { + it.getInt(0) != 0 + } else { + defaultValue + } + } +} + @JvmOverloads inline fun Cursor.readToList(predicate: (T) -> Boolean = { true }, mapper: (Cursor) -> T): List { val list = mutableListOf() diff --git a/core-util/src/main/java/org/signal/core/util/DimensionUnitExtensions.kt b/core-util/src/main/java/org/signal/core/util/DimensionUnitExtensions.kt index 71828ad6d1..a6fa0d76e7 100644 --- a/core-util/src/main/java/org/signal/core/util/DimensionUnitExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/DimensionUnitExtensions.kt @@ -24,4 +24,4 @@ val Float.sp: Float get() = DimensionUnit.SP.toPixels(this) * Converts the given Int SP value into Pixels */ @get:Px -val Int.sp: Int get() = this.toFloat().sp.toInt() \ No newline at end of file +val Int.sp: Int get() = this.toFloat().sp.toInt() diff --git a/core-util/src/main/java/org/signal/core/util/FontUtil.kt b/core-util/src/main/java/org/signal/core/util/FontUtil.kt index 1cc9492b96..8ddf1727c9 100644 --- a/core-util/src/main/java/org/signal/core/util/FontUtil.kt +++ b/core-util/src/main/java/org/signal/core/util/FontUtil.kt @@ -7,7 +7,6 @@ import android.graphics.Paint import android.graphics.PorterDuff import kotlin.math.abs - object FontUtil { private const val SAMPLE_EMOJI = "\uD83C\uDF0D" // 🌍 @@ -37,4 +36,4 @@ object FontUtil { return bitmap.getPixel(0, 0) != 0 } -} \ No newline at end of file +} diff --git a/core-util/src/main/java/org/signal/core/util/IntentExtensions.kt b/core-util/src/main/java/org/signal/core/util/IntentExtensions.kt new file mode 100644 index 0000000000..8ee2295d05 --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/IntentExtensions.kt @@ -0,0 +1,23 @@ +package org.signal.core.util + +import android.content.Intent +import android.os.Build +import android.os.Parcelable + +fun Intent.getParcelableExtraCompat(key: String, clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= 33) { + this.getParcelableExtra(key, clazz) + } else { + @Suppress("DEPRECATION") + this.getParcelableExtra(key) + } +} + +fun Intent.getParcelableArrayListExtraCompat(key: String, clazz: Class): ArrayList? { + return if (Build.VERSION.SDK_INT >= 33) { + this.getParcelableArrayListExtra(key, clazz) + } else { + @Suppress("DEPRECATION") + this.getParcelableArrayListExtra(key) + } +} diff --git a/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt b/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt index 4f4feadba7..a3bf7e530d 100644 --- a/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt @@ -16,4 +16,4 @@ fun Optional.isAbsent(): Boolean { fun E?.toOptional(): Optional { return Optional.ofNullable(this) -} \ No newline at end of file +} diff --git a/core-util/src/main/java/org/signal/core/util/ParcelExtensions.kt b/core-util/src/main/java/org/signal/core/util/ParcelExtensions.kt new file mode 100644 index 0000000000..1fd2d4f3ec --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/ParcelExtensions.kt @@ -0,0 +1,23 @@ +package org.signal.core.util + +import android.os.Build +import android.os.Parcel +import android.os.Parcelable + +fun Parcel.readParcelableCompat(clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= 33) { + this.readParcelable(clazz.classLoader, clazz) + } else { + @Suppress("DEPRECATION") + this.readParcelable(clazz.classLoader) + } +} + +fun Parcel.readSerializableCompat(clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= 33) { + this.readSerializable(clazz.classLoader, clazz) + } else { + @Suppress("DEPRECATION", "UNCHECKED_CAST") + this.readSerializable() as T + } +} diff --git a/core-util/src/main/java/org/signal/core/util/Serializer.kt b/core-util/src/main/java/org/signal/core/util/Serializer.kt index 5521d1d7f2..5bbddb2f15 100644 --- a/core-util/src/main/java/org/signal/core/util/Serializer.kt +++ b/core-util/src/main/java/org/signal/core/util/Serializer.kt @@ -25,5 +25,4 @@ object StringStringSerializer : StringSerializer { override fun deserialize(data: String): String { return data } - -} \ No newline at end of file +} diff --git a/core-util/src/main/java/org/signal/core/util/SqlUtil.kt b/core-util/src/main/java/org/signal/core/util/SqlUtil.kt index 757d45d4aa..a100cbcddf 100644 --- a/core-util/src/main/java/org/signal/core/util/SqlUtil.kt +++ b/core-util/src/main/java/org/signal/core/util/SqlUtil.kt @@ -82,7 +82,7 @@ object SqlUtil { @JvmStatic fun getForeignKeyDependencies(db: SupportSQLiteDatabase, table: String): Set { return db.query("PRAGMA foreign_key_list($table)") - .readToSet{ cursor -> + .readToSet { cursor -> cursor.requireNonNullString("table") } } @@ -393,4 +393,4 @@ object SqlUtil { } class Query(val where: String, val whereArgs: Array) -} \ No newline at end of file +} diff --git a/core-util/src/main/java/org/signal/core/util/StringExtensions.kt b/core-util/src/main/java/org/signal/core/util/StringExtensions.kt index c7103d24cd..7cc0a2cf7f 100644 --- a/core-util/src/main/java/org/signal/core/util/StringExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/StringExtensions.kt @@ -37,4 +37,4 @@ fun String.asListContains(item: String): Boolean { */ fun String.toSingleLine(): String { return this.trimIndent().split("\n").joinToString(separator = " ") -} \ No newline at end of file +} diff --git a/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt b/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt index 0de0e6432c..a80ee8ace9 100644 --- a/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt +++ b/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt @@ -21,13 +21,13 @@ class DeadlockDetector(private val handler: Handler, private val pollingInterval var lastThreadDumpTime: Long = -1 fun start() { - Log.d(TAG, "Beginning deadlock monitoring."); + Log.d(TAG, "Beginning deadlock monitoring.") running = true handler.postDelayed(this::poll, pollingInterval) } fun stop() { - Log.d(TAG, "Ending deadlock monitoring."); + Log.d(TAG, "Ending deadlock monitoring.") running = false handler.removeCallbacksAndMessages(null) } @@ -122,7 +122,6 @@ class DeadlockDetector(private val handler: Handler, private val pollingInterval for (entry in blocked) { stringBuilder.append("-- [${entry.key.id}] ${entry.key.name} | ${entry.key.state}\n") - val callerThrowable: Throwable? = TracedThreads.callerStackTraces[entry.key.id] val stackTrace: Array = if (callerThrowable != null) { ExceptionUtil.joinStackTrace(entry.value, callerThrowable.stackTrace) diff --git a/core-util/src/main/java/org/signal/core/util/concurrent/TracingExecutorService.kt b/core-util/src/main/java/org/signal/core/util/concurrent/TracingExecutorService.kt index b4ed4e9f03..f603b3f702 100644 --- a/core-util/src/main/java/org/signal/core/util/concurrent/TracingExecutorService.kt +++ b/core-util/src/main/java/org/signal/core/util/concurrent/TracingExecutorService.kt @@ -31,25 +31,28 @@ internal class TracingExecutorService(val wrapped: ExecutorService) : ExecutorSe val queue: Queue get() { - return if (wrapped is ThreadPoolExecutor) + return if (wrapped is ThreadPoolExecutor) { wrapped.queue - else + } else { LinkedBlockingQueue() + } } val activeCount: Int get() { - return if (wrapped is ThreadPoolExecutor) + return if (wrapped is ThreadPoolExecutor) { wrapped.activeCount - else + } else { 0 + } } val maximumPoolSize: Int get() { - return if (wrapped is ThreadPoolExecutor) + return if (wrapped is ThreadPoolExecutor) { wrapped.maximumPoolSize - else + } else { 0 + } } } diff --git a/core-util/src/main/java/org/signal/core/util/concurrent/TracingUncaughtExceptionHandler.kt b/core-util/src/main/java/org/signal/core/util/concurrent/TracingUncaughtExceptionHandler.kt index 8ce330b9ac..9648fb10ff 100644 --- a/core-util/src/main/java/org/signal/core/util/concurrent/TracingUncaughtExceptionHandler.kt +++ b/core-util/src/main/java/org/signal/core/util/concurrent/TracingUncaughtExceptionHandler.kt @@ -5,12 +5,13 @@ import org.signal.core.util.ExceptionUtil /** * An uncaught exception handler that will combine a caller stack trace with the exception to print a more useful stack trace. */ -internal class TracingUncaughtExceptionHandler ( - val originalHandler: Thread.UncaughtExceptionHandler?, - private val callerStackTrace: Throwable) : Thread.UncaughtExceptionHandler { +internal class TracingUncaughtExceptionHandler( + val originalHandler: Thread.UncaughtExceptionHandler?, + private val callerStackTrace: Throwable +) : Thread.UncaughtExceptionHandler { override fun uncaughtException(thread: Thread, exception: Throwable) { val updated = ExceptionUtil.joinStackTrace(exception, callerStackTrace) originalHandler?.uncaughtException(thread, updated) } -} \ No newline at end of file +} diff --git a/core-util/src/main/java/org/signal/core/util/money/PlatformCurrencyUtil.kt b/core-util/src/main/java/org/signal/core/util/money/PlatformCurrencyUtil.kt index ab6c4ed459..2835218f11 100644 --- a/core-util/src/main/java/org/signal/core/util/money/PlatformCurrencyUtil.kt +++ b/core-util/src/main/java/org/signal/core/util/money/PlatformCurrencyUtil.kt @@ -21,4 +21,4 @@ object PlatformCurrencyUtil { fun getAvailableCurrencyCodes(): Set { return Currency.getAvailableCurrencies().map { it.currencyCode }.toSet() } -} \ No newline at end of file +} diff --git a/core-util/src/test/java/org/signal/core/util/StringExtensions_asListContains.kt b/core-util/src/test/java/org/signal/core/util/StringExtensions_asListContains.kt index 432d9bc5c4..1f476d740e 100644 --- a/core-util/src/test/java/org/signal/core/util/StringExtensions_asListContains.kt +++ b/core-util/src/test/java/org/signal/core/util/StringExtensions_asListContains.kt @@ -32,7 +32,7 @@ class StringExtensions_asListContains( arrayOf("a", "b", false), arrayOf("a", "abc", false), - arrayOf("b", "a*", false), + arrayOf("b", "a*", false) ).toList() } } diff --git a/core-util/src/test/java/org/signal/core/util/StringUtilTest_endsWith.kt b/core-util/src/test/java/org/signal/core/util/StringUtilTest_endsWith.kt index 324f0c1341..f5a4d69b8e 100644 --- a/core-util/src/test/java/org/signal/core/util/StringUtilTest_endsWith.kt +++ b/core-util/src/test/java/org/signal/core/util/StringUtilTest_endsWith.kt @@ -51,4 +51,4 @@ class StringUtilTest_endsWith { assertEquals(expected, result) } -} \ No newline at end of file +} diff --git a/core-util/src/test/java/org/signal/core/util/StringUtilTest_replace.kt b/core-util/src/test/java/org/signal/core/util/StringUtilTest_replace.kt index 6f99c2d993..dfa992b3bd 100644 --- a/core-util/src/test/java/org/signal/core/util/StringUtilTest_replace.kt +++ b/core-util/src/test/java/org/signal/core/util/StringUtilTest_replace.kt @@ -48,4 +48,4 @@ class StringUtilTest_replace { assertEquals(expected.toString(), result.toString()) } -} \ No newline at end of file +} diff --git a/core-util/src/test/java/org/signal/core/util/StringUtilTest_startsWith.kt b/core-util/src/test/java/org/signal/core/util/StringUtilTest_startsWith.kt index 3e5ca2a9ff..83daa460ef 100644 --- a/core-util/src/test/java/org/signal/core/util/StringUtilTest_startsWith.kt +++ b/core-util/src/test/java/org/signal/core/util/StringUtilTest_startsWith.kt @@ -51,4 +51,4 @@ class StringUtilTest_startsWith { assertEquals(expected, result) } -} \ No newline at end of file +} diff --git a/dependencies.gradle b/dependencies.gradle index caf15fd577..b518a0a760 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -4,19 +4,25 @@ dependencyResolutionManagement { versionCatalogs { libs { - version('androidx-camera', '1.2.0') - version('androidx-fragment', '1.5.2') + version('androidx-appcompat', '1.6.1') + version('androidx-camera', '1.2.1') + version('androidx-fragment', '1.5.5') version('androidx-lifecycle', '2.5.1') - version('androidx-navigation', '2.5.2') + version('androidx-navigation', '2.5.3') version('androidx-window', '1.0.0') version('exoplayer', '2.18.1') version('glide', '4.13.2') version('kotlin', '1.6.21') - version('libsignal-client', '0.21.1') + version('libsignal-client', '0.22.0') version('mp4parser', '1.9.39') + version('android-gradle-plugin', '7.4.2') + + // Android Plugins + alias('android-library').to('com.android.library', 'com.android.library.gradle.plugin').versionRef('android-gradle-plugin') + alias('android-application').to('com.android.application', 'com.android.application.gradle.plugin').versionRef('android-gradle-plugin') // Compose - alias('androidx-compose-bom').to('androidx.compose:compose-bom:2022.12.00') + alias('androidx-compose-bom').to('androidx.compose:compose-bom:2023.01.00') alias('androidx-compose-material3').to('androidx.compose.material3', 'material3').withoutVersion() alias('androidx-compose-ui-tooling-preview').to('androidx.compose.ui', 'ui-tooling-preview').withoutVersion() alias('androidx-compose-ui-tooling-core').to('androidx.compose.ui', 'ui-tooling').withoutVersion() @@ -24,20 +30,23 @@ dependencyResolutionManagement { // Desugaring alias('android-tools-desugar').to('com.android.tools:desugar_jdk_libs:1.1.5') + // Kotlin + alias('kotlin-gradle-plugin').to('org.jetbrains.kotlin', 'kotlin-gradle-plugin').versionRef('kotlin') + // Android X - alias('androidx-activity-ktx').to('androidx.activity:activity-ktx:1.5.1') - alias('androidx-core-ktx').to('androidx.core:core-ktx:1.5.0') + alias('androidx-activity-ktx').to('androidx.activity', 'activity-ktx').versionRef('androidx-appcompat') + alias('androidx-appcompat').to('androidx.appcompat', 'appcompat').versionRef('androidx-appcompat') + alias('androidx-core-ktx').to('androidx.core:core-ktx:1.9.0') + alias('androidx-fragment').to('androidx.fragment', 'fragment').versionRef('androidx-fragment') alias('androidx-fragment-ktx').to('androidx.fragment', 'fragment-ktx').versionRef('androidx-fragment') alias('androidx-fragment-testing').to('androidx.fragment', 'fragment-testing').versionRef('androidx-fragment') alias('androidx-annotation').to('androidx.annotation:annotation:1.4.0') - alias('androidx-appcompat').to('androidx.appcompat:appcompat:1.5.1') alias('androidx-constraintlayout').to('androidx.constraintlayout:constraintlayout:2.0.4') alias('androidx-window-window').to('androidx.window', 'window').versionRef('androidx-window') alias('androidx-window-java').to('androidx.window', 'window-java').versionRef('androidx-window') alias('androidx-recyclerview').to('androidx.recyclerview:recyclerview:1.2.1') alias('androidx-legacy-support').to('androidx.legacy:legacy-support-v13:1.0.0') alias('androidx-legacy-preference').to('androidx.legacy:legacy-preference-v14:1.0.0') - alias('androidx-cardview').to('androidx.cardview:cardview:1.0.0') alias('androidx-preference').to('androidx.preference:preference:1.1.1') alias('androidx-gridlayout').to('androidx.gridlayout:gridlayout:1.0.0') alias('androidx-exifinterface').to('androidx.exifinterface:exifinterface:1.3.3') @@ -64,7 +73,7 @@ dependencyResolutionManagement { alias('androidx-webkit').to('androidx.webkit:webkit:1.4.0') // Material - alias('material-material').to('com.google.android.material:material:1.6.1') + alias('material-material').to('com.google.android.material:material:1.8.0') // Google alias('google-protobuf-javalite').to('com.google.protobuf:protobuf-javalite:3.11.4') @@ -92,8 +101,8 @@ dependencyResolutionManagement { alias('libsignal-android').to('org.signal', 'libsignal-android').versionRef('libsignal-client') alias('signal-aesgcmprovider').to('org.signal:aesgcmprovider:0.0.3') alias('molly-argon2').to('im.molly:argon2:13.1-1') - alias('molly-ringrtc').to('im.molly:ringrtc-android:2.22.0-1') - alias('signal-android-database-sqlcipher').to('org.signal:android-database-sqlcipher:4.5.1-S1') + alias('molly-ringrtc').to('im.molly:ringrtc-android:2.25.1-1') + alias('signal-android-database-sqlcipher').to('org.signal:sqlcipher-android:4.5.3-FTS-S2') // MOLLY alias('gosimple-nbvcxz').to('me.gosimple:nbvcxz:1.5.0') @@ -111,7 +120,7 @@ dependencyResolutionManagement { alias('rxjava3-rxkotlin').to('io.reactivex.rxjava3:rxkotlin:3.0.1') alias('rxdogtag').to('com.uber.rxdogtag2:rxdogtag:2.0.1') alias('conscrypt-android').to('org.conscrypt:conscrypt-android:2.0.0') - alias('mobilecoin').to('com.mobilecoin:android-sdk:4.0.0') + alias('mobilecoin').to('com.mobilecoin:android-sdk:4.0.0.1') alias('leolin-shortcutbadger').to('me.leolin:ShortcutBadger:1.1.22') alias('emilsjolander-stickylistheaders').to('se.emilsjolander:stickylistheaders:2.7.0') alias('jpardogo-materialtabstrip').to('com.jpardogo.materialtabstrip:library:1.0.9') @@ -121,7 +130,6 @@ dependencyResolutionManagement { alias('roundedimageview').to('com.makeramen:roundedimageview:2.1.0') alias('materialish-progress').to('com.pnikosis:materialish-progress:1.5') alias('waitingdots').to('pl.tajchert:waitingdots:0.1.0') - alias('time-duration-picker').to('mobi.upod:time-duration-picker:1.1.3') alias('subsampling-scale-image-view').to('com.davemorrissey.labs:subsampling-scale-image-view:3.10.0') alias('android-tooltips').to('com.tomergoldst.android:tooltips:1.0.6') alias('stream').to('com.annimon:stream:1.1.8') @@ -161,6 +169,7 @@ dependencyResolutionManagement { alias('assertj-core').to('org.assertj:assertj-core:3.11.1') alias('square-okhttp-mockserver').to('com.squareup.okhttp3:mockwebserver:3.12.13') alias('mockk').to('io.mockk:mockk:1.13.2') + alias('mockk-android').to('io.mockk:mockk-android:1.13.2') alias('conscrypt-openjdk-uber').to('org.conscrypt:conscrypt-openjdk-uber:2.0.0') } diff --git a/device-transfer/app/build.gradle b/device-transfer/app/build.gradle index 278f702886..426e7a5678 100644 --- a/device-transfer/app/build.gradle +++ b/device-transfer/app/build.gradle @@ -1,16 +1,12 @@ -apply plugin: 'com.android.application' +plugins { + id 'signal-sample-app' +} android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'org.signal.devicetransfer.app' defaultConfig { applicationId "org.signal.devicetransfer.app" - versionCode 1 - versionName "1.0" - - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' @@ -18,33 +14,8 @@ android { buildConfigField "String", "LIBSIGNAL_VERSION", "\"libsignal ${libs.versions.libsignal.client.get()}\"" } - - compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard/proguard.cfg' - } - - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard/proguard.cfg' - } - } } dependencies { - implementation libs.androidx.appcompat - implementation libs.material.material - implementation libs.androidx.constraintlayout - - testImplementation testLibs.junit.junit - implementation project(':device-transfer') } diff --git a/device-transfer/app/src/main/AndroidManifest.xml b/device-transfer/app/src/main/AndroidManifest.xml index 7f077f857a..c5e51b3272 100644 --- a/device-transfer/app/src/main/AndroidManifest.xml +++ b/device-transfer/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/device-transfer/lib/build.gradle b/device-transfer/lib/build.gradle index ffc6d71752..a797034260 100644 --- a/device-transfer/lib/build.gradle +++ b/device-transfer/lib/build.gradle @@ -1,32 +1,16 @@ -apply plugin: 'com.android.library' - -repositories { - mavenCentral() +plugins { + id 'signal-library' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - } - - compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } + namespace 'org.signal.devicetransfer' } dependencies { - implementation libs.androidx.appcompat implementation project(':core-util') implementation libs.libsignal.android api libs.greenrobot.eventbus - testImplementation testLibs.junit.junit - testImplementation testLibs.androidx.test.core testImplementation (testLibs.robolectric.robolectric) { exclude group: 'com.google.protobuf', module: 'protobuf-java' } diff --git a/device-transfer/lib/src/main/AndroidManifest.xml b/device-transfer/lib/src/main/AndroidManifest.xml index ad08af0860..091975b220 100644 --- a/device-transfer/lib/src/main/AndroidManifest.xml +++ b/device-transfer/lib/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/glide-config/build.gradle b/glide-config/build.gradle index ce092456fe..f5fe3d458f 100644 --- a/glide-config/build.gradle +++ b/glide-config/build.gradle @@ -1,38 +1,13 @@ plugins { - id 'com.android.library' - id 'kotlin-android' + id 'signal-library' id 'kotlin-kapt' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - kotlinOptions { - jvmTarget = '1.8' - } + namespace 'org.signal.glide' } dependencies { - lintChecks project(':lintchecks') - - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.core.ktx - implementation libs.androidx.annotation - implementation libs.androidx.appcompat - implementation libs.glide.glide kapt libs.glide.compiler } diff --git a/glide-config/src/main/AndroidManifest.xml b/glide-config/src/main/AndroidManifest.xml index 1d19f23524..8072ee00db 100644 --- a/glide-config/src/main/AndroidManifest.xml +++ b/glide-config/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/gradle.properties b/gradle.properties index 739463df82..e786fac0ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ -org.gradle.jvmargs=-Xmx6g +org.gradle.jvmargs=-Xmx6g -XX:+UseParallelGC +kotlin.daemon.jvmargs=-Xmx5g -XX:+UseG1GC +org.gradle.parallel=true android.useAndroidX=true android.enableJetifier=true kapt.incremental.apt=false -android.experimental.androidTest.numManagedDeviceShards=4 \ No newline at end of file diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 91084a8515..55f0049133 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -20,22 +20,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - @@ -44,6 +28,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -60,6 +52,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -94,11 +94,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -115,36 +110,28 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - - - - - + + @@ -193,36 +180,36 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -245,20 +232,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - + + @@ -317,92 +304,92 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -410,21 +397,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -446,21 +423,10 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - @@ -469,25 +435,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + - - - + + + - - - - - - - - - - + + @@ -518,22 +484,50 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + @@ -556,14 +550,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -572,14 +558,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -614,44 +592,28 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - - - - - + + @@ -694,14 +656,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -710,6 +664,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -736,14 +695,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -768,14 +719,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -814,21 +757,10 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - @@ -837,14 +769,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -853,24 +777,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - @@ -879,14 +790,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -895,14 +798,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -929,67 +824,57 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -1000,28 +885,36 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -1060,14 +953,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -1076,14 +961,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -1100,14 +977,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -1135,9 +1004,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - @@ -1322,14 +1188,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - - + + + @@ -1345,19 +1211,49 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + + + + + + @@ -1365,11 +1261,41 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1385,6 +1311,31 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1395,19 +1346,74 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1415,30 +1421,75 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + - - + + - - + + - - - + + + @@ -1449,38 +1500,106 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - + + + + + + + + + + + + + + + + + + @@ -1491,33 +1610,129 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1528,19 +1743,49 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + - - - + + + + + + @@ -1548,9 +1793,24 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + + + + + + + + @@ -1558,39 +1818,119 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + @@ -1598,134 +1938,198 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + - - - + + + - - - + + + - - + + + + - - - + + + + + + - - + + + + - - - + + + - - + + + + - - - + + + - - + + - - - + + + - - + + + + + + + + + - - - + + + - - + + - - - + + + - - + + + + + + + + + - - - + + + - - + + - - - + + + - - + + + + - - - + + + - - + + + + + + + - - - + + + - - + + + + + + + - - - + + + + + + - - + + + + - - - + + + - - + + + + - - - + + + - - + + - - - + + + - - + + + + + + + + + @@ -1733,11 +2137,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -1746,21 +2145,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -1774,11 +2163,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -1792,11 +2176,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -1810,21 +2189,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -1833,11 +2197,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -1863,21 +2222,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -1893,31 +2242,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -1995,61 +2329,26 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2060,36 +2359,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - @@ -2100,52 +2379,32 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + - - - + + + @@ -2178,6 +2437,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -2203,6 +2467,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -2213,46 +2482,31 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + - - - - - - - - - - - - - - - - - - - - @@ -2278,31 +2532,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -2328,6 +2567,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -2383,14 +2627,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - - + + + @@ -2412,44 +2656,34 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -2467,16 +2701,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -2487,19 +2711,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - @@ -2525,27 +2736,27 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + - - - - - - - - + + + - - - - - - + + + @@ -2556,12 +2767,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2572,12 +2783,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2588,12 +2799,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2604,12 +2815,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + + + + + + + + + @@ -2620,12 +2839,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2636,12 +2855,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2652,12 +2871,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2668,12 +2887,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2684,12 +2903,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2700,12 +2919,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + @@ -2716,11 +2935,24 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + @@ -2745,9 +2977,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - @@ -2830,11 +3059,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -2855,19 +3079,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - @@ -2970,39 +3181,51 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - + + + - - - + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + @@ -3055,9 +3278,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -3065,9 +3288,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -3078,69 +3301,77 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -3159,6 +3390,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3183,6 +3422,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3223,64 +3470,62 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + @@ -3309,11 +3554,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3324,11 +3564,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3339,26 +3574,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -3379,11 +3599,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3394,9 +3609,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -3409,9 +3624,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -3439,41 +3654,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3484,31 +3669,31 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + - - - - - - - - - - + + + + + @@ -3529,11 +3714,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -3549,6 +3744,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3567,9 +3767,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -3577,131 +3777,21 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - @@ -3712,36 +3802,29 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - + + + + + + + + @@ -3767,44 +3850,24 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - + + + - - - + + + @@ -3817,27 +3880,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - @@ -3858,11 +3900,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3888,11 +3925,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3903,6 +3935,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -3928,11 +3965,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3943,6 +3975,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4033,21 +4070,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -4063,26 +4090,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -4093,11 +4105,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4108,9 +4115,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + @@ -4123,6 +4135,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4168,41 +4185,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4213,11 +4205,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4228,14 +4215,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - + + + @@ -4243,26 +4225,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -4278,11 +4245,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4293,9 +4255,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -4303,16 +4265,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -4328,11 +4280,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4343,9 +4290,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + @@ -4353,16 +4300,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -4378,11 +4315,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4393,14 +4325,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - + + + @@ -4433,11 +4360,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4454,22 +4376,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - @@ -4491,11 +4397,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4509,6 +4410,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -4517,14 +4423,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4533,14 +4431,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4549,6 +4439,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4557,14 +4455,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4604,17 +4494,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - + + + - - + + @@ -4622,24 +4507,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - + @@ -4655,14 +4530,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4671,14 +4538,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4695,14 +4554,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4711,14 +4562,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -4727,19 +4570,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - @@ -4750,11 +4580,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4815,16 +4640,16 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + - - - - - @@ -4835,11 +4660,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -4850,26 +4670,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - @@ -4880,16 +4685,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - @@ -5009,38 +4804,38 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + @@ -5063,14 +4858,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - - + + + @@ -5083,11 +4878,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - diff --git a/image-editor/app/build.gradle b/image-editor/app/build.gradle index fc7701d22b..161a4ae98c 100644 --- a/image-editor/app/build.gradle +++ b/image-editor/app/build.gradle @@ -1,42 +1,17 @@ plugins { - id 'com.android.application' - id 'kotlin-android' + id 'signal-sample-app' id 'kotlin-kapt' } android { - compileSdk COMPILE_SDK + namespace 'org.signal.imageeditor.app' defaultConfig { applicationId "org.signal.imageeditor.app" - versionCode 1 - versionName "1.0" - multiDexEnabled true - - minSdk MINIMUM_SDK - targetSdk TARGET_SDK - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - kotlinOptions { - jvmTarget = '1.8' } } dependencies { - implementation libs.androidx.core.ktx - implementation libs.androidx.appcompat - implementation libs.material.material - implementation project(':core-util') implementation project(':image-editor') implementation libs.glide.glide diff --git a/image-editor/app/src/main/AndroidManifest.xml b/image-editor/app/src/main/AndroidManifest.xml index 2102741cdb..02affe3123 100644 --- a/image-editor/app/src/main/AndroidManifest.xml +++ b/image-editor/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/image-editor/app/src/test/java/com/example/imageeditor/app/ExampleUnitTest.kt b/image-editor/app/src/test/java/com/example/imageeditor/app/ExampleUnitTest.kt index 03c24560b6..915950b9d6 100644 --- a/image-editor/app/src/test/java/com/example/imageeditor/app/ExampleUnitTest.kt +++ b/image-editor/app/src/test/java/com/example/imageeditor/app/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.example.imageeditor.app +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/image-editor/lib/build.gradle b/image-editor/lib/build.gradle index 4e6b8996a1..c9d5223e44 100644 --- a/image-editor/lib/build.gradle +++ b/image-editor/lib/build.gradle @@ -1,37 +1,11 @@ plugins { - id 'com.android.library' - id 'kotlin-android' + id 'signal-library' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - - kotlinOptions { - jvmTarget = '1.8' - } + namespace 'org.signal.imageeditor' } dependencies { - lintChecks project(':lintchecks') - implementation project(':core-util') - - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.core.ktx - implementation libs.androidx.annotation - implementation libs.androidx.appcompat } \ No newline at end of file diff --git a/image-editor/lib/src/main/AndroidManifest.xml b/image-editor/lib/src/main/AndroidManifest.xml index cd18e60eee..a5918e68ab 100644 --- a/image-editor/lib/src/main/AndroidManifest.xml +++ b/image-editor/lib/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/MultiLineTextRenderer.java b/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/MultiLineTextRenderer.java index 14999ed30b..24bc9dbb53 100644 --- a/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/MultiLineTextRenderer.java +++ b/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/MultiLineTextRenderer.java @@ -320,9 +320,9 @@ public void render(@NonNull RendererContext rendererContext) { rendererContext.canvasMatrix.concat(projectionMatrix); if (mode == Mode.HIGHLIGHT) { - if(text.isEmpty()){ + if (text.isEmpty()) { modeBounds.setEmpty(); - }else{ + } else { modeBounds.set(textBounds.left - HIGHLIGHT_HORIZONTAL_PADDING, selectionBounds.top - HIGHLIGHT_TOP_PADDING, textBounds.right + HIGHLIGHT_HORIZONTAL_PADDING, @@ -333,13 +333,17 @@ public void render(@NonNull RendererContext rendererContext) { rendererContext.canvas.drawRoundRect(modeBounds, HIGHLIGHT_CORNER_RADIUS, HIGHLIGHT_CORNER_RADIUS, modePaint); modePaint.setAlpha(alpha); } else if (mode == Mode.UNDERLINE) { - modeBounds.set(textBounds.left, selectionBounds.top, textBounds.right, selectionBounds.bottom); - modeBounds.inset(-DimensionUnit.DP.toPixels(2), -DimensionUnit.DP.toPixels(2)); + if (text.isEmpty()) { + modeBounds.setEmpty(); + } else { + modeBounds.set(textBounds.left, selectionBounds.top, textBounds.right, selectionBounds.bottom); + modeBounds.inset(-DimensionUnit.DP.toPixels(2), -DimensionUnit.DP.toPixels(2)); - modeBounds.set(modeBounds.left, - Math.max(modeBounds.top, modeBounds.bottom - DimensionUnit.DP.toPixels(6)), - modeBounds.right, - modeBounds.bottom - DimensionUnit.DP.toPixels(2)); + modeBounds.set(modeBounds.left, + Math.max(modeBounds.top, modeBounds.bottom - DimensionUnit.DP.toPixels(6)), + modeBounds.right, + modeBounds.bottom - DimensionUnit.DP.toPixels(2)); + } int alpha = modePaint.getAlpha(); modePaint.setAlpha(rendererContext.getAlpha(alpha)); diff --git a/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/SelectedElementGuideRenderer.kt b/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/SelectedElementGuideRenderer.kt index 190cee2dcf..35706b5c3a 100644 --- a/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/SelectedElementGuideRenderer.kt +++ b/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/SelectedElementGuideRenderer.kt @@ -4,9 +4,9 @@ import android.graphics.Color import android.graphics.DashPathEffect import android.graphics.Paint import android.graphics.Path -import org.signal.core.util.DimensionUnit import android.os.Parcel import android.os.Parcelable +import org.signal.core.util.DimensionUnit import org.signal.imageeditor.core.Bounds import org.signal.imageeditor.core.Renderer import org.signal.imageeditor.core.RendererContext @@ -15,10 +15,14 @@ class SelectedElementGuideRenderer : Renderer { private val allPointsOnScreen = FloatArray(8) private val allPointsInLocalCords = floatArrayOf( - Bounds.LEFT, Bounds.TOP, - Bounds.RIGHT, Bounds.TOP, - Bounds.RIGHT, Bounds.BOTTOM, - Bounds.LEFT, Bounds.BOTTOM + Bounds.LEFT, + Bounds.TOP, + Bounds.RIGHT, + Bounds.TOP, + Bounds.RIGHT, + Bounds.BOTTOM, + Bounds.LEFT, + Bounds.BOTTOM ) private val circleRadius = DimensionUnit.DP.toPixels(5f) diff --git a/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/TrashRenderer.kt b/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/TrashRenderer.kt index 4373d61868..c5f2dbde78 100644 --- a/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/TrashRenderer.kt +++ b/image-editor/lib/src/main/java/org/signal/imageeditor/core/renderers/TrashRenderer.kt @@ -67,7 +67,7 @@ internal class TrashRenderer : InvalidateableRenderer, Renderer, Parcelable { rendererContext.canvas.drawCircle(buttonCenter[0], buttonCenter[1], diameter / 2f, shadePaint) rendererContext.canvas.drawCircle(buttonCenter[0], buttonCenter[1], diameter / 2f, outlinePaint) rendererContext.canvas.translate(bounds.centerX(), bounds.bottom - diameterLarge / 2f - padBottom) - rendererContext.canvas.translate(- (trashSize / 2f), - (trashSize / 2f)) + rendererContext.canvas.translate(-(trashSize / 2f), -(trashSize / 2f)) trash.draw(rendererContext.canvas) rendererContext.canvas.restore() diff --git a/libfakegms/build.gradle b/libfakegms/build.gradle index 86952a4b47..da0255360e 100644 --- a/libfakegms/build.gradle +++ b/libfakegms/build.gradle @@ -1,21 +1,26 @@ -apply plugin: 'com.android.library' +plugins { + id 'com.android.library' + id 'android-constants' +} android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'com.google.android.gms' + + buildToolsVersion = signalBuildToolsVersion + compileSdkVersion = signalCompileSdkVersion defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK + minSdkVersion signalMinSdkVersion + targetSdkVersion signalTargetSdkVersion } compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION + sourceCompatibility signalJavaVersion + targetCompatibility signalJavaVersion } } dependencies { - implementation 'androidx.fragment:fragment:1.3.5' - implementation 'com.google.auto.value:auto-value-annotations:1.6.3' + implementation libs.androidx.fragment.ktx + lintChecks project(':lintchecks') } \ No newline at end of file diff --git a/libfakegms/src/main/AndroidManifest.xml b/libfakegms/src/main/AndroidManifest.xml deleted file mode 100644 index 0944979ef7..0000000000 --- a/libfakegms/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/libnetcipher/build.gradle b/libnetcipher/build.gradle index 0335ae1f12..d5a5741db3 100644 --- a/libnetcipher/build.gradle +++ b/libnetcipher/build.gradle @@ -1,22 +1,26 @@ -apply plugin: 'com.android.library' +plugins { + id 'com.android.library' + id 'android-constants' +} android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'info.guardianproject.netcipher' + + buildToolsVersion = signalBuildToolsVersion + compileSdkVersion = signalCompileSdkVersion defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK + minSdkVersion signalMinSdkVersion + targetSdkVersion signalTargetSdkVersion } compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION + sourceCompatibility signalJavaVersion + targetCompatibility signalJavaVersion } } dependencies { - lintChecks project(':lintchecks') - implementation project(':core-util') + lintChecks project(':lintchecks') } diff --git a/libnetcipher/src/main/AndroidManifest.xml b/libnetcipher/src/main/AndroidManifest.xml index 1a5e060d8b..6662137b0e 100644 --- a/libnetcipher/src/main/AndroidManifest.xml +++ b/libnetcipher/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/OrbotHelper.java b/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/OrbotHelper.java index b90a70c83d..a54598a7de 100644 --- a/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/OrbotHelper.java +++ b/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/OrbotHelper.java @@ -30,8 +30,7 @@ import android.os.Looper; import android.text.TextUtils; -import androidx.annotation.Nullable; - +import org.jetbrains.annotations.Nullable; import org.signal.core.util.logging.Log; import java.net.MalformedURLException; diff --git a/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/SignatureUtils.java b/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/SignatureUtils.java index 1975697d4a..98a0081cd3 100644 --- a/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/SignatureUtils.java +++ b/libnetcipher/src/main/java/info/guardianproject/netcipher/proxy/SignatureUtils.java @@ -24,8 +24,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.Signature; -import androidx.annotation.Nullable; - +import org.jetbrains.annotations.Nullable; import org.signal.core.util.logging.Log; import java.security.MessageDigest; diff --git a/libsignal/service/build.gradle b/libsignal/service/build.gradle index 13550c0d61..7ba0ddc4d1 100644 --- a/libsignal/service/build.gradle +++ b/libsignal/service/build.gradle @@ -3,26 +3,24 @@ apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'java-test-fixtures' apply plugin: 'com.google.protobuf' apply plugin: 'idea' -apply plugin: 'org.jlleitschuh.gradle.ktlint' -sourceCompatibility = 1.8 -archivesBaseName = "signal-service-java" -version = lib_signal_service_version_number -group = lib_signal_service_group_info +archivesBaseName = "signal-service-java" +version = lib_signal_service_version_number +group = lib_signal_service_group_info java { - targetCompatibility = 1.8 withJavadocJar() withSourcesJar() } -compileJava { - options.release = 8 +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } } -repositories { - mavenCentral() - mavenLocal() +compileJava { + options.release = 11 } configurations { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index ab87d047ea..2f2569b567 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -20,7 +20,6 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest; -import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; import org.whispersystems.signalservice.api.crypto.ProfileCipher; import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; @@ -29,7 +28,6 @@ import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo; import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; -import org.whispersystems.signalservice.api.messages.multidevice.VerifyDeviceResponse; import org.whispersystems.signalservice.api.payments.CurrencyConversions; import org.whispersystems.signalservice.api.profiles.AvatarUploadParams; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; @@ -55,21 +53,15 @@ import org.whispersystems.signalservice.api.util.Preconditions; import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; -import org.whispersystems.signalservice.internal.contacts.crypto.ContactDiscoveryCipher; -import org.whispersystems.signalservice.internal.contacts.crypto.Quote; -import org.whispersystems.signalservice.internal.contacts.crypto.RemoteAttestation; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; -import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest; -import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse; import org.whispersystems.signalservice.internal.crypto.PrimaryProvisioningCipher; import org.whispersystems.signalservice.internal.push.AuthCredentials; +import org.whispersystems.signalservice.internal.push.BackupAuthCheckRequest; +import org.whispersystems.signalservice.internal.push.BackupAuthCheckResponse; import org.whispersystems.signalservice.internal.push.CdsiAuthResponse; import org.whispersystems.signalservice.internal.push.ProfileAvatarData; import org.whispersystems.signalservice.internal.push.PushServiceSocket; -import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil; +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse; import org.whispersystems.signalservice.internal.push.RemoteConfigResponse; -import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse; import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; @@ -83,15 +75,14 @@ import org.whispersystems.signalservice.internal.storage.protos.WriteOperation; import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider; import org.whispersystems.signalservice.internal.util.Util; +import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper; import org.whispersystems.util.Base64; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -102,13 +93,14 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import io.reactivex.rxjava3.core.Single; @@ -219,16 +211,57 @@ public void setGcmId(Optional gcmRegistrationId) throws IOException { } } + public Single> checkBackupAuthCredentials(@Nonnull String e164, @Nonnull List usernamePasswords) { + + return pushServiceSocket.checkBackupAuthCredentials(new BackupAuthCheckRequest(e164, usernamePasswords), DefaultResponseMapper.getDefault(BackupAuthCheckResponse.class)); + } + /** * Request a push challenge. A number will be pushed to the GCM (FCM) id. This can then be used * during SMS/call requests to bypass the CAPTCHA. * * @param gcmRegistrationId The GCM (FCM) id to use. - * @param e164number The number to associate it with. + * @param sessionId The session to request a push for. * @throws IOException */ - public void requestRegistrationPushChallenge(String gcmRegistrationId, String e164number) throws IOException { - this.pushServiceSocket.requestPushChallenge(gcmRegistrationId, e164number); + public void requestRegistrationPushChallenge(String sessionId, String gcmRegistrationId) throws IOException { + pushServiceSocket.requestPushChallenge(sessionId, gcmRegistrationId); + } + + public ServiceResponse createRegistrationSession(@Nullable String fcmToken, @Nullable String mcc, @Nullable String mnc) { + try { + final RegistrationSessionMetadataResponse response = pushServiceSocket.createVerificationSession(fcmToken, mcc, mnc); + return ServiceResponse.forResult(response, 200, null); + } catch (IOException e) { + return ServiceResponse.forUnknownError(e); + } + } + + public ServiceResponse getRegistrationSession(String sessionId) { + try { + final RegistrationSessionMetadataResponse response = pushServiceSocket.getSessionStatus(sessionId); + return ServiceResponse.forResult(response, 200, null); + } catch (IOException e) { + return ServiceResponse.forUnknownError(e); + } + } + + public ServiceResponse submitPushChallengeToken(String sessionId, String pushChallengeToken) { + try { + final RegistrationSessionMetadataResponse response = pushServiceSocket.patchVerificationSession(sessionId, null, null, null, null, pushChallengeToken); + return ServiceResponse.forResult(response, 200, null); + } catch (IOException e) { + return ServiceResponse.forUnknownError(e); + } + } + + public ServiceResponse submitCaptchaToken(String sessionId, @Nullable String captchaToken) { + try { + final RegistrationSessionMetadataResponse response = pushServiceSocket.patchVerificationSession(sessionId, null, null, null, captchaToken, null); + return ServiceResponse.forResult(response, 200, null); + } catch (IOException e) { + return ServiceResponse.forUnknownError(e); + } } /** @@ -236,13 +269,11 @@ public void requestRegistrationPushChallenge(String gcmRegistrationId, String e1 * an SMS verification code to this Signal user. * * @param androidSmsRetrieverSupported - * @param captchaToken If the user has done a CAPTCHA, include this. - * @param challenge If present, it can bypass the CAPTCHA. */ - public ServiceResponse requestSmsVerificationCode(Locale locale, boolean androidSmsRetrieverSupported, Optional captchaToken, Optional challenge, Optional fcmToken) { + public ServiceResponse requestSmsVerificationCode(String sessionId, Locale locale, boolean androidSmsRetrieverSupported) { try { - this.pushServiceSocket.requestSmsVerificationCode(locale, androidSmsRetrieverSupported, captchaToken, challenge); - return ServiceResponse.forResult(new RequestVerificationCodeResponse(fcmToken), 200, null); + final RegistrationSessionMetadataResponse response = pushServiceSocket.requestVerificationCode(sessionId, locale, androidSmsRetrieverSupported, PushServiceSocket.VerificationCodeTransport.SMS); + return ServiceResponse.forResult(response, 200, null); } catch (IOException e) { return ServiceResponse.forUnknownError(e); } @@ -253,13 +284,11 @@ public ServiceResponse requestSmsVerificationCo * make a voice call to this Signal user. * * @param locale - * @param captchaToken If the user has done a CAPTCHA, include this. - * @param challenge If present, it can bypass the CAPTCHA. */ - public ServiceResponse requestVoiceVerificationCode(Locale locale, Optional captchaToken, Optional challenge, Optional fcmToken) { + public ServiceResponse requestVoiceVerificationCode(String sessionId, Locale locale, boolean androidSmsRetrieverSupported) { try { - this.pushServiceSocket.requestVoiceVerificationCode(locale, captchaToken, challenge); - return ServiceResponse.forResult(new RequestVerificationCodeResponse(fcmToken), 200, null); + final RegistrationSessionMetadataResponse response = pushServiceSocket.requestVerificationCode(sessionId, locale, androidSmsRetrieverSupported, PushServiceSocket.VerificationCodeTransport.VOICE); + return ServiceResponse.forResult(response, 200, null); } catch (IOException e) { return ServiceResponse.forUnknownError(e); } @@ -271,110 +300,28 @@ public ServiceResponse requestVoiceVerification * @param verificationCode The verification code received via SMS or Voice * (see {@link #requestSmsVerificationCode} and * {@link #requestVoiceVerificationCode}). - * @param signalProtocolRegistrationId A random 14-bit number that identifies this Signal install. - * This value should remain consistent across registrations for the - * same install, but probabilistically differ across registrations - * for separate installs. + * @param sessionId The ID of the current registration session. * @return The UUID of the user that was registered. * @throws IOException for various HTTP and networking errors */ - public ServiceResponse verifyAccount(String verificationCode, - int signalProtocolRegistrationId, - boolean fetchesMessages, - byte[] unidentifiedAccessKey, - boolean unrestrictedUnidentifiedAccess, - AccountAttributes.Capabilities capabilities, - boolean discoverableByPhoneNumber, - int pniRegistrationId) - { + public ServiceResponse verifyAccount(@Nonnull String verificationCode, @Nonnull String sessionId) { try { - VerifyAccountResponse response = this.pushServiceSocket.verifyAccountCode(verificationCode, - null, - signalProtocolRegistrationId, - fetchesMessages, - null, - null, - unidentifiedAccessKey, - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber, - pniRegistrationId); + RegistrationSessionMetadataResponse response = pushServiceSocket.submitVerificationCode(sessionId, verificationCode); return ServiceResponse.forResult(response, 200, null); } catch (IOException e) { return ServiceResponse.forUnknownError(e); } } - /** - * Verify a Signal Service account with a received SMS or voice verification code with - * registration lock. - * - * @param verificationCode The verification code received via SMS or Voice - * (see {@link #requestSmsVerificationCode} and - * {@link #requestVoiceVerificationCode}). - * @param signalProtocolRegistrationId A random 14-bit number that identifies this Signal install. - * This value should remain consistent across registrations for the - * same install, but probabilistically differ across registrations - * for separate installs. - * @param registrationLock Only supply if found on KBS. - * @return The UUID of the user that was registered. - */ - public ServiceResponse verifyAccountWithRegistrationLockPin(String verificationCode, - int signalProtocolRegistrationId, - boolean fetchesMessages, - String registrationLock, - byte[] unidentifiedAccessKey, - boolean unrestrictedUnidentifiedAccess, - AccountAttributes.Capabilities capabilities, - boolean discoverableByPhoneNumber, - int pniRegistrationId) - { + public @Nonnull ServiceResponse registerAccount(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, boolean skipDeviceTransfer) { try { - VerifyAccountResponse response = this.pushServiceSocket.verifyAccountCode(verificationCode, - null, - signalProtocolRegistrationId, - fetchesMessages, - null, - registrationLock, - unidentifiedAccessKey, - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber, - pniRegistrationId); + VerifyAccountResponse response = pushServiceSocket.submitRegistrationRequest(sessionId, recoveryPassword, attributes, skipDeviceTransfer); return ServiceResponse.forResult(response, 200, null); } catch (IOException e) { return ServiceResponse.forUnknownError(e); } } - public VerifyDeviceResponse verifySecondaryDevice(String verificationCode, - int signalProtocolRegistrationId, - boolean fetchesMessages, - byte[] unidentifiedAccessKey, - boolean unrestrictedUnidentifiedAccess, - AccountAttributes.Capabilities capabilities, - boolean discoverableByPhoneNumber, - byte[] encryptedDeviceName, - int pniRegistrationId) - throws IOException - { - AccountAttributes accountAttributes = new AccountAttributes( - null, - signalProtocolRegistrationId, - fetchesMessages, - null, - null, - unidentifiedAccessKey, - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber, - Base64.encodeBytes(encryptedDeviceName), - pniRegistrationId - ); - - return this.pushServiceSocket.verifySecondaryDevice(verificationCode, accountAttributes); - } - public @Nonnull ServiceResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest) { try { VerifyAccountResponse response = this.pushServiceSocket.changeNumber(changePhoneNumberRequest); @@ -387,42 +334,12 @@ public VerifyDeviceResponse verifySecondaryDevice(String verificationCode, /** * Refresh account attributes with server. * - * @param signalingKey 52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, concatenated. - * @param signalProtocolRegistrationId A random 14-bit number that identifies this Signal install. - * This value should remain consistent across registrations for the same - * install, but probabilistically differ across registrations for - * separate installs. - * @param pin Only supply if pin has not yet been migrated to KBS. - * @param registrationLock Only supply if found on KBS. - * * @throws IOException */ - public void setAccountAttributes(String signalingKey, - int signalProtocolRegistrationId, - boolean fetchesMessages, - String pin, - String registrationLock, - byte[] unidentifiedAccessKey, - boolean unrestrictedUnidentifiedAccess, - AccountAttributes.Capabilities capabilities, - boolean discoverableByPhoneNumber, - byte[] encryptedDeviceName, - int pniRegistrationId) + public void setAccountAttributes(@Nonnull AccountAttributes accountAttributes) throws IOException { - this.pushServiceSocket.setAccountAttributes( - signalingKey, - signalProtocolRegistrationId, - fetchesMessages, - pin, - registrationLock, - unidentifiedAccessKey, - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber, - encryptedDeviceName, - pniRegistrationId - ); + this.pushServiceSocket.setAccountAttributes(accountAttributes); } /** @@ -823,20 +740,16 @@ public Optional resolveProfileKeyCredential(Servic } } - public ACI getAciByUsername(String username) throws IOException { - return this.pushServiceSocket.getAciByUsername(username); - } - - public void setUsername(String nickname, String existingUsername) throws IOException { - this.pushServiceSocket.setUsername(nickname, existingUsername); + public ACI getAciByUsernameHash(String usernameHash) throws IOException { + return this.pushServiceSocket.getAciByUsernameHash(usernameHash); } - public ReserveUsernameResponse reserveUsername(String nickname) throws IOException { - return this.pushServiceSocket.reserveUsername(nickname); + public ReserveUsernameResponse reserveUsername(List usernameHashes) throws IOException { + return this.pushServiceSocket.reserveUsername(usernameHashes); } - public void confirmUsername(ReserveUsernameResponse reserveUsernameResponse) throws IOException { - this.pushServiceSocket.confirmUsername(reserveUsernameResponse); + public void confirmUsername(String username, ReserveUsernameResponse reserveUsernameResponse) throws IOException { + this.pushServiceSocket.confirmUsername(username, reserveUsernameResponse); } public void deleteUsername() throws IOException { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index 19be68a99d..02779e6cdd 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -217,7 +217,7 @@ public List retrieveMessages(boolean allowStories, Messag entity.getDestinationUuid(), entity.isUrgent(), entity.isStory(), - entity.getReportingToken()); + entity.getReportSpamToken()); } else { envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(), @@ -228,7 +228,7 @@ public List retrieveMessages(boolean allowStories, Messag entity.getDestinationUuid(), entity.isUrgent(), entity.isStory(), - entity.getReportingToken()); + entity.getReportSpamToken()); } callback.onMessage(envelope); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index f148c48c74..d97059b67a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -2213,7 +2213,8 @@ public List getSubDeviceSessions(SignalServiceAddress recipient) { return aciStore.getSubDeviceSessions(recipient.getIdentifier()); } - private OutgoingPushMessage getEncryptedMessage(SignalServiceAddress recipient, + // Visible for testing only + public OutgoingPushMessage getEncryptedMessage(SignalServiceAddress recipient, Optional unidentifiedAccess, int deviceId, EnvelopeContent plaintext, @@ -2252,7 +2253,6 @@ private OutgoingPushMessage getEncryptedMessage(SignalServiceAddress rec } } - private List getPreKeys(SignalServiceAddress recipient, Optional unidentifiedAccess, int deviceId, boolean story) throws IOException { try { return socket.getPreKeys(recipient, unidentifiedAccess, deviceId); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java index a113ac36b5..1e778c66c0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalWebSocket.java @@ -2,10 +2,10 @@ import org.signal.libsignal.protocol.logging.Log; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; import org.whispersystems.signalservice.api.websocket.WebSocketFactory; import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage; import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketResponseMessage; @@ -15,6 +15,7 @@ import java.io.IOException; import java.util.Optional; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; @@ -218,30 +219,33 @@ public Single request(WebSocketRequestMessage requestMessage, /** *

- * A blocking call that reads a message off the pipe. When this call returns, the message has been - * acknowledged and will not be retransmitted. This will return {@link Optional#empty()} when an - * empty response is hit, which indicates the WebSocket is empty. + * A blocking call that reads a message off the pipe. When this call returns, if the callback indicates the + * message was successfully processed, then the message will be ack'ed on the serve and will not be retransmitted. + *

+ * This will return true if there are more messages to be read from the websocket, or false if the websocket is empty. *

* You can specify a {@link MessageReceivedCallback} that will be called before the received message is acknowledged. * This allows you to write the received message to durable storage before acknowledging receipt of it to the * server. *

- * Important: The empty response will only be hit once for each connection. That means if you get - * an empty response and call readOrEmpty() again on the same instance, you will not get an empty - * response, and instead will block until you get an actual message. This will, however, reset if - * connection breaks (if, for instance, you lose and regain network). + * Important: This will only return `false` once for each connection. That means if you get false call readMessage() + * again on the same instance, you will not get an immediate `false` return value, and instead will block until + * you get an actual message. This will, however, reset if connection breaks (if, for instance, you lose and regain network). * * @param timeout The timeout to wait for. * @param callback A callback that will be called before the message receipt is acknowledged to the server. * @return The message read (same as the message sent through the callback). */ @SuppressWarnings("DuplicateThrows") - public Optional readOrEmpty(long timeout, MessageReceivedCallback callback) + public boolean readMessage(long timeout, MessageReceivedCallback callback) throws TimeoutException, WebSocketUnavailableException, IOException { while (true) { WebSocketRequestMessage request = getWebSocket().readRequest(timeout); WebSocketResponseMessage response = createWebSocketResponse(request); + + AtomicBoolean successfullyProcessed = new AtomicBoolean(false); + try { if (isSignalServiceEnvelope(request)) { Optional timestampHeader = findHeader(request); @@ -255,15 +259,18 @@ public Optional readOrEmpty(long timeout, MessageReceived } } - SignalServiceEnvelope envelope = new SignalServiceEnvelope(request.getBody().toByteArray(), timestamp); + SignalServiceProtos.Envelope envelope = SignalServiceProtos.Envelope.parseFrom(request.getBody().toByteArray()); - callback.onMessage(envelope); - return Optional.of(envelope); + successfullyProcessed.set(callback.onMessage(envelope, timestamp)); + + return true; } else if (isSocketEmptyRequest(request)) { - return Optional.empty(); + return false; } } finally { - getWebSocket().sendResponse(response); + if (successfullyProcessed.get()) { + getWebSocket().sendResponse(response); + } } } } @@ -314,6 +321,8 @@ private static Optional findHeader(WebSocketRequestMessage message) { * received. */ public interface MessageReceivedCallback { - void onMessage(SignalServiceEnvelope envelope); + + /** True if you successfully processed the message, otherwise false. **/ + boolean onMessage(SignalServiceProtos.Envelope envelope, long serverDeliveredTimestamp); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt index 550dee8bba..740a785e00 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/AccountAttributes.kt @@ -24,6 +24,7 @@ class AccountAttributes @JsonCreator constructor( @JsonProperty val capabilities: Capabilities?, @JsonProperty val name: String?, @JsonProperty val pniRegistrationId: Int, + @JsonProperty val recoveryPassword: String?, ) { constructor( signalingKey: String?, @@ -36,7 +37,8 @@ class AccountAttributes @JsonCreator constructor( capabilities: Capabilities?, isDiscoverableByPhoneNumber: Boolean, name: String?, - pniRegistrationId: Int + pniRegistrationId: Int, + recoveryPassword: String? ) : this( signalingKey = signalingKey, registrationId = registrationId, @@ -50,7 +52,8 @@ class AccountAttributes @JsonCreator constructor( isDiscoverableByPhoneNumber = isDiscoverableByPhoneNumber, capabilities = capabilities, name = name, - pniRegistrationId = pniRegistrationId + pniRegistrationId = pniRegistrationId, + recoveryPassword = recoveryPassword ) data class Capabilities @JsonCreator constructor( diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java index c6d6c72489..8910e177e2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java @@ -14,10 +14,13 @@ public final class ChangePhoneNumberRequest { @JsonProperty - private String number; + private String sessionId; + + @JsonProperty + private String recoveryPassword; @JsonProperty - private String code; + private String number; @JsonProperty("reglock") private String registrationLock; @@ -36,18 +39,21 @@ public final class ChangePhoneNumberRequest { @JsonProperty private Map pniRegistrationIds; + @SuppressWarnings("unused") public ChangePhoneNumberRequest() {} - public ChangePhoneNumberRequest(String number, - String code, + public ChangePhoneNumberRequest(String sessionId, + String recoveryPassword, + String number, String registrationLock, IdentityKey pniIdentityKey, List deviceMessages, Map devicePniSignedPrekeys, Map pniRegistrationIds) { + this.sessionId = sessionId; + this.recoveryPassword = recoveryPassword; this.number = number; - this.code = code; this.registrationLock = registrationLock; this.pniIdentityKey = pniIdentityKey; this.deviceMessages = deviceMessages; @@ -59,10 +65,6 @@ public String getNumber() { return number; } - public String getCode() { - return code; - } - public String getRegistrationLock() { return registrationLock; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt new file mode 100644 index 0000000000..91924af8c7 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt @@ -0,0 +1,12 @@ +package org.whispersystems.signalservice.api.crypto + +import org.whispersystems.signalservice.api.push.ServiceId + +class EnvelopeMetadata( + val sourceServiceId: ServiceId, + val sourceE164: String?, + val sourceDeviceId: Int, + val sealedSender: Boolean, + val groupId: ByteArray?, + val destinationServiceId: ServiceId +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index ddda1d1294..6bf64dd5aa 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -49,10 +49,12 @@ import org.whispersystems.signalservice.api.messages.SignalServiceMetadata; import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.DistributionId; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.push.OutgoingPushMessage; import org.whispersystems.signalservice.internal.push.PushTransportDetails; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer; import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer; @@ -144,7 +146,7 @@ public SignalServiceContent decrypt(SignalServiceEnvelope envelope) { try { if (envelope.hasContent()) { - Plaintext plaintext = decrypt(envelope, envelope.getContent()); + Plaintext plaintext = decryptInternal(envelope.getProto(), envelope.getServerDeliveredTimestamp()); SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData()); SignalServiceContentProto contentProto = SignalServiceContentProto.newBuilder() @@ -162,7 +164,39 @@ public SignalServiceContent decrypt(SignalServiceEnvelope envelope) } } - private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) + public SignalServiceCipherResult decrypt(Envelope envelope, long serverDeliveredTimestamp) + throws InvalidMetadataMessageException, InvalidMetadataVersionException, + ProtocolInvalidKeyIdException, ProtocolLegacyMessageException, + ProtocolUntrustedIdentityException, ProtocolNoSessionException, + ProtocolInvalidVersionException, ProtocolInvalidMessageException, + ProtocolInvalidKeyException, ProtocolDuplicateMessageException, + SelfSendException, InvalidMessageStructureException + { + try { + if (envelope.hasContent()) { + Plaintext plaintext = decryptInternal(envelope, serverDeliveredTimestamp); + SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData()); + + return new SignalServiceCipherResult( + content, + new EnvelopeMetadata( + plaintext.metadata.getSender().getServiceId(), + plaintext.metadata.getSender().getNumber().orElse(null), + plaintext.metadata.getSenderDevice(), + plaintext.metadata.isNeedsReceipt(), + plaintext.metadata.getGroupId().orElse(null), + localAddress.getServiceId() + ) + ); + } else { + return null; + } + } catch (InvalidProtocolBufferException e) { + throw new InvalidMetadataMessageException(e); + } + } + + private Plaintext decryptInternal(Envelope envelope, long serverDeliveredTimestamp) throws InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, @@ -175,30 +209,30 @@ private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) byte[] paddedMessage; SignalServiceMetadata metadata; - if (!envelope.hasSourceUuid() && !envelope.isUnidentifiedSender()) { + if (!envelope.hasSourceUuid() && envelope.getType().getNumber() != Envelope.Type.UNIDENTIFIED_SENDER_VALUE) { throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!"); } - if (envelope.isPreKeySignalMessage()) { - SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid().get(), envelope.getSourceDevice()); + if (envelope.getType().getNumber() == Envelope.Type.PREKEY_BUNDLE_VALUE) { + SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid(), envelope.getSourceDevice()); SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); - paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext)); - metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); + paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.getContent().toByteArray())); + metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress)); - } else if (envelope.isSignalMessage()) { - SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid().get(), envelope.getSourceDevice()); + } else if (envelope.getType().getNumber() == Envelope.Type.CIPHERTEXT_VALUE) { + SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid(), envelope.getSourceDevice()); SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); - paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext)); - metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); - } else if (envelope.isPlaintextContent()) { - paddedMessage = new PlaintextContent(ciphertext).getBody(); - metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); - } else if (envelope.isUnidentifiedSender()) { + paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.getContent().toByteArray())); + metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); + } else if (envelope.getType().getNumber() == Envelope.Type.PLAINTEXT_CONTENT_VALUE) { + paddedMessage = new PlaintextContent(envelope.getContent().toByteArray()).getBody(); + metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); + } else if (envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE) { SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId)); - DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp()); + DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.getContent().toByteArray(), envelope.getServerTimestamp()); SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164()); Optional groupId = result.getGroupId(); boolean needsReceipt = true; @@ -213,7 +247,7 @@ private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) } paddedMessage = result.getPaddedMessage(); - metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid()); + metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid()); } else { throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType()); } @@ -223,24 +257,28 @@ private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) return new Plaintext(metadata, data); } catch (DuplicateMessageException e) { - throw new ProtocolDuplicateMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolDuplicateMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (LegacyMessageException e) { - throw new ProtocolLegacyMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolLegacyMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (InvalidMessageException e) { - throw new ProtocolInvalidMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolInvalidMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (InvalidKeyIdException e) { - throw new ProtocolInvalidKeyIdException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolInvalidKeyIdException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (InvalidKeyException e) { - throw new ProtocolInvalidKeyException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolInvalidKeyException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (UntrustedIdentityException e) { - throw new ProtocolUntrustedIdentityException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolUntrustedIdentityException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (InvalidVersionException e) { - throw new ProtocolInvalidVersionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolInvalidVersionException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } catch (NoSessionException e) { - throw new ProtocolNoSessionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice()); + throw new ProtocolNoSessionException(e, envelope.getSourceUuid(), envelope.getSourceDevice()); } } + private static SignalServiceAddress getSourceAddress(Envelope envelope) { + return new SignalServiceAddress(ServiceId.parseOrNull(envelope.getSourceUuid())); + } + private static class Plaintext { private final SignalServiceMetadata metadata; private final byte[] data; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipherResult.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipherResult.kt new file mode 100644 index 0000000000..c496f4e8c5 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipherResult.kt @@ -0,0 +1,15 @@ +package org.whispersystems.signalservice.api.crypto + +import org.whispersystems.signalservice.internal.push.SignalServiceProtos + +/** + * Represents the output of decrypting a [SignalServiceProtos.Envelope] via [SignalServiceCipher.decrypt] + * + * @param content The [SignalServiceProtos.Content] that was decrypted from the envelope. + * @param metadata The decrypted metadata of the envelope. Represents sender information that may have + * been encrypted with sealed sender. + */ +data class SignalServiceCipherResult( + val content: SignalServiceProtos.Content, + val metadata: EnvelopeMetadata +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java index 1b36ffe9a8..e6acbad95a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/kbs/MasterKey.java @@ -31,7 +31,7 @@ public String deriveRegistrationLock() { return Hex.toStringCondensed(derive("Registration Lock")); } - public String deriveRegistrationRecoveryToken() { + public String deriveRegistrationRecoveryPassword() { return Hex.toStringCondensed(derive("Registration Recovery")); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt new file mode 100644 index 0000000000..cd361c7bf1 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt @@ -0,0 +1,270 @@ +package org.whispersystems.signalservice.api.messages + +import org.signal.libsignal.protocol.message.DecryptionErrorMessage +import org.signal.libsignal.zkgroup.InvalidInputException +import org.signal.libsignal.zkgroup.groups.GroupMasterKey +import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation +import org.whispersystems.signalservice.api.InvalidMessageStructureException +import org.whispersystems.signalservice.api.util.UuidUtil +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2 +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.ReceiptMessage +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.StoryMessage +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage +import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage + +/** + * Validates an [Envelope] and its decrypted [Content] so that we know the message can be processed safely + * down the line. + * + * Mostly makes sure that UUIDs are valid, required fields are presents, etc. + */ +object EnvelopeContentValidator { + + fun validate(envelope: Envelope, content: Content): Result { + return when { + envelope.story && !content.meetsStoryFlagCriteria() -> Result.Invalid("Envelope was flagged as a story, but it did not have any story-related content!") + content.hasDataMessage() -> validateDataMessage(envelope, content.dataMessage) + content.hasSyncMessage() -> validateSyncMessage(envelope, content.syncMessage) + content.hasCallMessage() -> Result.Valid + content.hasReceiptMessage() -> validateReceiptMessage(content.receiptMessage) + content.hasTypingMessage() -> validateTypingMessage(envelope, content.typingMessage) + content.hasDecryptionErrorMessage() -> validateDecryptionErrorMessage(content.decryptionErrorMessage.toByteArray()) + content.hasStoryMessage() -> validateStoryMessage(content.storyMessage) + content.hasPniSignatureMessage() -> Result.Valid + content.hasSenderKeyDistributionMessage() -> Result.Valid + else -> Result.Invalid("Content is empty!") + } + } + + private fun validateDataMessage(envelope: Envelope, dataMessage: DataMessage): Result { + if (dataMessage.requiredProtocolVersion > DataMessage.ProtocolVersion.CURRENT_VALUE) { + return Result.UnsupportedDataMessage( + ourVersion = DataMessage.ProtocolVersion.CURRENT_VALUE, + theirVersion = dataMessage.requiredProtocolVersion + ) + } + + if (!dataMessage.hasTimestamp()) { + return Result.Invalid("[DataMessage] Missing timestamp!") + } + + if (dataMessage.timestamp != envelope.timestamp) { + Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}") + } + + if (dataMessage.hasQuote() && dataMessage.quote.authorUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[DataMessage] Invalid UUID on quote!") + } + + if (dataMessage.contactList.any { it.hasAvatar() && it.avatar.avatar.isPresentAndInvalid() }) { + return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.contactList.avatar!") + } + + if (dataMessage.previewList.any { it.hasImage() && it.image.isPresentAndInvalid() }) { + return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.previewList.image!") + } + + if (dataMessage.bodyRangesList.any { it.hasMentionUuid() && it.mentionUuid.isNullOrInvalidUuid() }) { + return Result.Invalid("[DataMessage] Invalid UUID on body range!") + } + + if (dataMessage.hasSticker() && dataMessage.sticker.data.isNullOrInvalid()) { + return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.sticker!") + } + + if (dataMessage.hasReaction()) { + if (!dataMessage.reaction.hasTargetSentTimestamp()) { + return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.reaction!") + } + if (dataMessage.reaction.targetAuthorUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.reaction!") + } + } + + if (dataMessage.hasDelete() && !dataMessage.delete.hasTargetSentTimestamp()) { + return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.delete!") + } + + if (dataMessage.hasStoryContext() && dataMessage.storyContext.authorUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.storyContext!") + } + + if (dataMessage.hasGiftBadge()) { + if (!dataMessage.giftBadge.hasReceiptCredentialPresentation()) { + return Result.Invalid("[DataMessage] Missing DataMessage.giftBadge.receiptCredentialPresentation!") + } + if (!dataMessage.giftBadge.hasReceiptCredentialPresentation()) { + try { + ReceiptCredentialPresentation(dataMessage.giftBadge.receiptCredentialPresentation.toByteArray()) + } catch (e: InvalidInputException) { + return Result.Invalid("[DataMessage] Invalid DataMessage.giftBadge.receiptCredentialPresentation!") + } + } + } + + if (dataMessage.attachmentsList.any { it.isNullOrInvalid() }) { + return Result.Invalid("[DataMessage] Invalid attachments!") + } + + return Result.Valid + } + + private fun validateSyncMessage(envelope: Envelope, syncMessage: SyncMessage): Result { + if (syncMessage.hasSent()) { + val validAddress = syncMessage.sent.destinationUuid.isValidUuid() + val hasDataGroup = syncMessage.sent.message?.hasGroupV2() ?: false + val hasStoryGroup = syncMessage.sent.storyMessage?.hasGroup() ?: false + val hasStoryManifest = syncMessage.sent.storyMessageRecipientsList.isNotEmpty() + + if (hasDataGroup) { + validateGroupContextV2(syncMessage.sent.message.groupV2, "[SyncMessage.Sent.Message]")?.let { return it } + } + + if (hasStoryGroup) { + validateGroupContextV2(syncMessage.sent.storyMessage.group, "[SyncMessage.Sent.StoryMessage]")?.let { return it } + } + + if (!validAddress && !hasDataGroup && !hasStoryGroup && !hasStoryManifest) { + return Result.Invalid("[SyncMessage] No valid destination! Checked the destination, DataMessage.group, StoryMessage.group, and storyMessageRecipientList") + } + + for (status in syncMessage.sent.unidentifiedStatusList) { + if (status.destinationUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.sent.unidentifiedStatusList!") + } + } + + return if (syncMessage.sent.hasMessage()) { + validateDataMessage(envelope, syncMessage.sent.message) + } else if (syncMessage.sent.hasStoryMessage()) { + validateStoryMessage(syncMessage.sent.storyMessage) + } else { + Result.Invalid("[SyncMessage] Empty SyncMessage.sent!") + } + } + + if (syncMessage.readList.any { it.senderUuid.isNullOrInvalidUuid() }) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.readList!") + } + + if (syncMessage.viewedList.any { it.senderUuid.isNullOrInvalidUuid() }) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewList!") + } + + if (syncMessage.hasViewOnceOpen() && syncMessage.viewOnceOpen.senderUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewOnceOpen!") + } + + if (syncMessage.hasVerified() && syncMessage.verified.destinationUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.verified!") + } + + if (syncMessage.stickerPackOperationList.any { !it.hasPackId() }) { + return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!") + } + + if (syncMessage.hasBlocked() && syncMessage.blocked.uuidsList.any { it.isNullOrInvalidUuid() }) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.blocked!") + } + + if (syncMessage.hasMessageRequestResponse() && !syncMessage.messageRequestResponse.hasGroupId() && syncMessage.messageRequestResponse.threadUuid.isNullOrInvalidUuid()) { + return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.messageRequestResponse!") + } + + return Result.Valid + } + + private fun validateReceiptMessage(receiptMessage: ReceiptMessage): Result { + return if (!receiptMessage.hasType()) { + Result.Invalid("[ReceiptMessage] Missing type!") + } else { + Result.Valid + } + } + + private fun validateTypingMessage(envelope: Envelope, typingMessage: TypingMessage): Result { + return if (!typingMessage.hasTimestamp()) { + return Result.Invalid("[TypingMessage] Missing timestamp!") + } else if (typingMessage.hasTimestamp() && typingMessage.timestamp != envelope.timestamp) { + Result.Invalid("[TypingMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${typingMessage.timestamp}") + } else if (!typingMessage.hasAction()) { + Result.Invalid("[TypingMessage] Missing action!") + } else { + Result.Valid + } + } + + private fun validateDecryptionErrorMessage(serializedDecryptionErrorMessage: ByteArray): Result { + return try { + DecryptionErrorMessage(serializedDecryptionErrorMessage) + Result.Valid + } catch (e: InvalidMessageStructureException) { + Result.Invalid("[DecryptionErrorMessage] Bad decryption error message!", e) + } + } + + private fun validateStoryMessage(storyMessage: StoryMessage): Result { + if (storyMessage.hasGroup()) { + validateGroupContextV2(storyMessage.group, "[StoryMessage]")?.let { return it } + } + + return Result.Valid + } + + private fun AttachmentPointer?.isNullOrInvalid(): Boolean { + return this == null || this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET + } + + private fun AttachmentPointer?.isPresentAndInvalid(): Boolean { + return this != null && this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET + } + + private fun String?.isValidUuid(): Boolean { + return UuidUtil.isUuid(this) + } + + private fun String?.isNullOrInvalidUuid(): Boolean { + return !UuidUtil.isUuid(this) + } + + private fun Content?.meetsStoryFlagCriteria(): Boolean { + return when { + this == null -> false + this.hasSenderKeyDistributionMessage() -> true + this.hasStoryMessage() -> true + this.hasDataMessage() && this.dataMessage.hasStoryContext() && this.dataMessage.hasGroupV2() -> true + this.hasDataMessage() && this.dataMessage.hasDelete() -> true + else -> false + } + } + + private fun validateGroupContextV2(groupContext: GroupContextV2, prefix: String): Result.Invalid? { + return if (!groupContext.hasMasterKey()) { + Result.Invalid("$prefix Missing GV2 master key!") + } else if (!groupContext.hasRevision()) { + Result.Invalid("$prefix Missing GV2 revision!") + } else { + try { + GroupMasterKey(groupContext.masterKey.toByteArray()) + null + } catch (e: InvalidInputException) { + Result.Invalid("$prefix Bad GV2 master key!", e) + } + } + } + + sealed class Result { + /** Content is valid. */ + object Valid : Result() + + /** The [DataMessage.requiredProtocolVersion_] is newer than the one we support. */ + class UnsupportedDataMessage(val ourVersion: Int, val theirVersion: Int) : Result() + + /** The contents of the proto do not match our expectations, e.g. invalid UUIDs, missing required fields, etc. */ + class Invalid(val reason: String, val throwable: Throwable = Throwable()) : Result() + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java index 4a8106f221..cf2cd193a7 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java @@ -271,6 +271,10 @@ public byte[] getReportingToken() { return envelope.getReportingToken().toByteArray(); } + public Envelope getProto() { + return envelope; + } + private SignalServiceEnvelopeProto.Builder serializeToProto() { SignalServiceEnvelopeProto.Builder builder = SignalServiceEnvelopeProto.newBuilder() .setType(getType()) @@ -331,6 +335,6 @@ public static SignalServiceEnvelope deserialize(byte[] serialized) { proto.getDestinationUuid(), proto.getUrgent(), proto.getStory(), - proto.getReportingToken().toByteArray()); + proto.hasReportingToken() ? proto.getReportingToken().toByteArray() : null); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java index a524005909..407791b033 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/PNI.java @@ -23,6 +23,10 @@ public static PNI parseOrThrow(String raw) { return from(UUID.fromString(raw)); } + public static PNI parseOrThrow(byte[] raw) { + return from(UuidUtil.parseOrThrow(raw)); + } + public static PNI parseOrNull(byte[] raw) { UUID uuid = UuidUtil.parseOrNull(raw); return uuid != null ? from(uuid) : null; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/AlreadyVerifiedException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/AlreadyVerifiedException.kt new file mode 100644 index 0000000000..cbe35eedf3 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/AlreadyVerifiedException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class AlreadyVerifiedException : NonSuccessfulResponseCodeException(409) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/ExternalServiceFailureException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/ExternalServiceFailureException.kt new file mode 100644 index 0000000000..98dceb0200 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/ExternalServiceFailureException.kt @@ -0,0 +1,9 @@ +package org.whispersystems.signalservice.api.push.exceptions + +/** + * known possible values for @property[reason]: + * providerRejected - indicates that the provider understood the request, but declined to deliver a verification SMS/call (potentially due to fraud prevention rules) + * providerUnavailable - indicates that the provider could not be reached or did not respond to the request to send a verification code in a timely manner + * illegalArgument - some part of the request was not understood or accepted by the provider (e.g. the provider did not recognize the phone number as a valid number for the selected transport) + */ +class ExternalServiceFailureException(val isPermanent: Boolean, val reason: String) : NonSuccessfulResponseCodeException(502) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/HttpConflictException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/HttpConflictException.kt new file mode 100644 index 0000000000..e163a86e96 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/HttpConflictException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class HttpConflictException : NonSuccessfulResponseCodeException(409) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/IncorrectCodeException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/IncorrectCodeException.kt new file mode 100644 index 0000000000..1772184dd4 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/IncorrectCodeException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class IncorrectCodeException : NonSuccessfulResponseCodeException(403) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/IncorrectRegistrationRecoveryPasswordException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/IncorrectRegistrationRecoveryPasswordException.kt new file mode 100644 index 0000000000..1d34d6465c --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/IncorrectRegistrationRecoveryPasswordException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class IncorrectRegistrationRecoveryPasswordException : NonSuccessfulResponseCodeException(403) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/InvalidTransportModeException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/InvalidTransportModeException.kt new file mode 100644 index 0000000000..fbb9207603 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/InvalidTransportModeException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class InvalidTransportModeException : NonSuccessfulResponseCodeException(400) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MustRequestNewCodeException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MustRequestNewCodeException.kt new file mode 100644 index 0000000000..7a2141acef --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/MustRequestNewCodeException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class MustRequestNewCodeException : NonSuccessfulResponseCodeException(409) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/NoSuchSessionException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/NoSuchSessionException.kt new file mode 100644 index 0000000000..205bb6da3f --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/NoSuchSessionException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class NoSuchSessionException : NonSuccessfulResponseCodeException(404) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/PushChallengeRequiredException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/PushChallengeRequiredException.kt new file mode 100644 index 0000000000..343116afd8 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/PushChallengeRequiredException.kt @@ -0,0 +1,3 @@ +package org.whispersystems.signalservice.api.push.exceptions + +class PushChallengeRequiredException : NonSuccessfulResponseCodeException(409) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/TokenNotAcceptedException.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/TokenNotAcceptedException.kt new file mode 100644 index 0000000000..c8973e1ee3 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/push/exceptions/TokenNotAcceptedException.kt @@ -0,0 +1,6 @@ +package org.whispersystems.signalservice.api.push.exceptions + +/** + * Exception representing that the submitted information was not accepted (e.g. the push challenge token or captcha did not match) + */ +class TokenNotAcceptedException : NonSuccessfulResponseCodeException(403) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java index dffa9f0f19..c4cb2f7c03 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalAccountRecord.java @@ -19,6 +19,8 @@ import java.util.Objects; import java.util.Optional; +import javax.annotation.Nullable; + public final class SignalAccountRecord implements SignalRecord { private static final String TAG = SignalAccountRecord.class.getSimpleName(); @@ -195,6 +197,10 @@ public String describeDiff(SignalRecord other) { diff.add("HasSeenGroupStoryEducationSheet"); } + if (!Objects.equals(getUsername(), that.getUsername())) { + diff.add("Username"); + } + return diff.toString(); } else { return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName(); @@ -325,6 +331,10 @@ public boolean hasSeenGroupStoryEducationSheet() { return proto.getHasSeenGroupStoryEducationSheet(); } + public @Nullable String getUsername() { + return proto.getUsername(); + } + public AccountRecord toProto() { return proto; } @@ -697,6 +707,16 @@ public Builder setHasSeenGroupStoryEducationSheet(boolean hasSeenGroupStoryEduca return this; } + public Builder setUsername(@Nullable String username) { + if (username == null || username.isEmpty()) { + builder.clearUsername(); + } else { + builder.setUsername(username); + } + + return this; + } + private static AccountRecord.Builder parseUnknowns(byte[] serializedUnknowns) { try { return AccountRecord.parseFrom(serializedUnknowns).toBuilder(); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java index c78f7d720d..1e9e0c0da3 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalContactRecord.java @@ -32,6 +32,7 @@ public final class SignalContactRecord implements SignalRecord { private final Optional profileFamilyName; private final Optional systemGivenName; private final Optional systemFamilyName; + private final Optional systemNickname; private final Optional profileKey; private final Optional username; private final Optional identityKey; @@ -48,6 +49,7 @@ public SignalContactRecord(StorageId id, ContactRecord proto) { this.profileFamilyName = OptionalUtil.absentIfEmpty(proto.getFamilyName()); this.systemGivenName = OptionalUtil.absentIfEmpty(proto.getSystemGivenName()); this.systemFamilyName = OptionalUtil.absentIfEmpty(proto.getSystemFamilyName()); + this.systemNickname = OptionalUtil.absentIfEmpty(proto.getSystemNickname()); this.profileKey = OptionalUtil.absentIfEmpty(proto.getProfileKey()); this.username = OptionalUtil.absentIfEmpty(proto.getUsername()); this.identityKey = OptionalUtil.absentIfEmpty(proto.getIdentityKey()); @@ -101,6 +103,10 @@ public String describeDiff(SignalRecord other) { diff.add("SystemFamilyName"); } + if (!Objects.equals(this.systemNickname, that.systemNickname)) { + diff.add("SystemNickname"); + } + if (!OptionalUtil.byteArrayEquals(this.profileKey, that.profileKey)) { diff.add("ProfileKey"); } @@ -195,6 +201,10 @@ public Optional getSystemFamilyName() { return systemFamilyName; } + public Optional getSystemNickname() { + return systemNickname; + } + public Optional getProfileKey() { return profileKey; } @@ -314,6 +324,11 @@ public Builder setSystemFamilyName(String familyName) { return this; } + public Builder setSystemNickname(String nickname) { + builder.setSystemNickname(nickname == null ? "" : nickname); + return this; + } + public Builder setProfileKey(byte[] profileKey) { builder.setProfileKey(profileKey == null ? ByteString.EMPTY : ByteString.copyFrom(profileKey)); return this; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java index 7e645d52c9..905f70370a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/storage/SignalStorageManifest.java @@ -69,6 +69,10 @@ public Optional getAccountStorageId() { } } + public Map> getStorageIdsByType() { + return storageIdsByType; + } + public byte[] serialize() { List ids = new ArrayList<>(storageIds.size()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponseProcessor.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponseProcessor.java index 08c0cfd526..0b3e00b527 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponseProcessor.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponseProcessor.java @@ -67,10 +67,6 @@ protected boolean authorizationFailed() { return response.getStatus() == 401 || response.getStatus() == 403; } - protected boolean captchaRequired() { - return response.getStatus() == 402; - } - protected boolean notFound() { return response.getStatus() == 404; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/BackupAuthCheckRequest.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/BackupAuthCheckRequest.kt new file mode 100644 index 0000000000..1128b674fd --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/BackupAuthCheckRequest.kt @@ -0,0 +1,46 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonCreator +import okio.ByteString.Companion.encode +import org.whispersystems.signalservice.internal.ServiceResponse +import org.whispersystems.signalservice.internal.ServiceResponseProcessor +import java.nio.charset.StandardCharsets + +/** + * Request body JSON for verifying stored KBS auth credentials. + */ +@Suppress("unused") +class BackupAuthCheckRequest @JsonCreator constructor( + val number: String?, + val passwords: List +) + +/** + * Verify KBS auth credentials JSON response. + */ +data class BackupAuthCheckResponse @JsonCreator constructor( + private val matches: Map> +) { + private val actualMatches = matches["matches"] ?: emptyMap() + + val match: String? = actualMatches.entries.firstOrNull { it.value.toString() == "match" }?.key?.toBasic() + val invalid: List = actualMatches.filterValues { it.toString() == "invalid" }.keys.map { it.toBasic() } + + /** Server expects and returns values as : but we prefer the full encoded Basic auth header format */ + private fun String.toBasic(): String { + return "Basic ${encode(StandardCharsets.ISO_8859_1).base64()}" + } +} + +/** + * Processes a response from the verify stored KBS auth credentials request. + */ +class BackupAuthCheckProcessor(response: ServiceResponse) : ServiceResponseProcessor(response) { + fun getInvalid(): List { + return response.result.map { it.invalid }.orElse(emptyList()) + } + + fun getValid(): String? { + return response.result.map { it.match }.orElse(null) + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java index 9e2c2612b2..cd617ace4b 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ConfirmUsernameRequest.java @@ -4,13 +4,13 @@ class ConfirmUsernameRequest { @JsonProperty - private String usernameToConfirm; + private String usernameHash; @JsonProperty - private String reservationToken; + private String zkProof; - ConfirmUsernameRequest(String usernameToConfirm, String reservationToken) { - this.usernameToConfirm = usernameToConfirm; - this.reservationToken = reservationToken; + ConfirmUsernameRequest(String usernameHash, String zkProof) { + this.usernameHash = usernameHash; + this.zkProof = zkProof; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/OutgoingPushMessage.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/OutgoingPushMessage.java index ed34b04544..618d2ca135 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/OutgoingPushMessage.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/OutgoingPushMessage.java @@ -12,13 +12,13 @@ public class OutgoingPushMessage { @JsonProperty - private int type; + public int type; @JsonProperty - private int destinationDeviceId; + public int destinationDeviceId; @JsonProperty - private int destinationRegistrationId; + public int destinationRegistrationId; @JsonProperty - private String content; + public String content; public OutgoingPushMessage() {} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 512bd3f882..c02bd33a57 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -19,6 +19,8 @@ import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.util.Pair; +import org.signal.libsignal.usernames.BaseUsernameException; +import org.signal.libsignal.usernames.Username; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; @@ -57,27 +59,34 @@ import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; +import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException; import org.whispersystems.signalservice.api.push.exceptions.ConflictException; import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException; -import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException; +import org.whispersystems.signalservice.api.push.exceptions.ExternalServiceFailureException; +import org.whispersystems.signalservice.api.push.exceptions.HttpConflictException; +import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException; +import org.whispersystems.signalservice.api.push.exceptions.InvalidTransportModeException; import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; +import org.whispersystems.signalservice.api.push.exceptions.MustRequestNewCodeException; import org.whispersystems.signalservice.api.push.exceptions.NoContentException; -import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException; +import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; +import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.RangeException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.push.exceptions.RemoteAttestationResponseExpiredException; import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; +import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException; import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReservedException; @@ -95,8 +104,6 @@ import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.configuration.SignalUrl; -import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest; -import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse; import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest; import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; @@ -192,22 +199,19 @@ public class PushServiceSocket { private static final String TAG = PushServiceSocket.class.getSimpleName(); - private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s?client=%s"; - private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s"; private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s"; private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/"; private static final String TURN_SERVER_INFO = "/v1/accounts/turn"; private static final String SET_ACCOUNT_ATTRIBUTES = "/v1/accounts/attributes/"; private static final String PIN_PATH = "/v1/accounts/pin/"; private static final String REGISTRATION_LOCK_PATH = "/v1/accounts/registration_lock"; - private static final String REQUEST_PUSH_CHALLENGE = "/v1/accounts/fcm/preauth/%s/%s"; private static final String WHO_AM_I = "/v1/accounts/whoami"; - private static final String GET_USERNAME_PATH = "/v1/accounts/username/%s"; - private static final String MODIFY_USERNAME_PATH = "/v1/accounts/username"; - private static final String RESERVE_USERNAME_PATH = "/v1/accounts/username/reserved"; - private static final String CONFIRM_USERNAME_PATH = "/v1/accounts/username/confirm"; + private static final String GET_USERNAME_PATH = "/v1/accounts/username_hash/%s"; + private static final String MODIFY_USERNAME_PATH = "/v1/accounts/username_hash"; + private static final String RESERVE_USERNAME_PATH = "/v1/accounts/username_hash/reserve"; + private static final String CONFIRM_USERNAME_PATH = "/v1/accounts/username_hash/confirm"; private static final String DELETE_ACCOUNT_PATH = "/v1/accounts/me"; - private static final String CHANGE_NUMBER_PATH = "/v1/accounts/number"; + private static final String CHANGE_NUMBER_PATH = "/v2/accounts/number"; private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s"; private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%s"; @@ -236,6 +240,7 @@ public class PushServiceSocket { private static final String SENDER_CERTIFICATE_NO_E164_PATH = "/v1/certificate/delivery?includeE164=false"; private static final String KBS_AUTH_PATH = "/v1/backup/auth"; + private static final String KBS_AUTH_CHECK_PATH = "/v1/backup/auth/check"; private static final String ATTACHMENT_KEY_DOWNLOAD_PATH = "attachments/%s"; private static final String ATTACHMENT_ID_DOWNLOAD_PATH = "attachments/%d"; @@ -274,10 +279,17 @@ public class PushServiceSocket { private static final String BOOST_RECEIPT_CREDENTIALS = "/v1/subscription/boost/receipt_credentials"; private static final String DONATIONS_CONFIGURATION = "/v1/subscription/configuration"; + private static final String VERIFICATION_SESSION_PATH = "/v1/verification/session"; + private static final String VERIFICATION_CODE_PATH = "/v1/verification/session/%s/code"; + + private static final String REGISTRATION_PATH = "/v1/registration"; + private static final String CDSI_AUTH = "/v2/directory/auth"; private static final String REPORT_SPAM = "/v1/messages/report/%s/%s"; + private static final String BACKUP_AUTH_CHECK = "/v1/backup/auth/check"; + private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp"; private static final Map NO_HEADERS = Collections.emptyMap(); @@ -318,30 +330,79 @@ public PushServiceSocket(SignalServiceConfiguration configuration, this.clientZkProfileOperations = clientZkProfileOperations; } - public void requestSmsVerificationCode(Locale locale, boolean androidSmsRetriever, Optional captchaToken, Optional challenge) throws IOException { - Map headers = locale != null ? Collections.singletonMap("Accept-Language", locale.getLanguage() + "-" + locale.getCountry()) : NO_HEADERS; - String path = String.format(CREATE_ACCOUNT_SMS_PATH, credentialsProvider.getE164(), androidSmsRetriever ? "android-2021-03" : "android"); + public RegistrationSessionMetadataResponse createVerificationSession(@Nullable String pushToken, @Nullable String mcc, @Nullable String mnc) throws IOException { + final String jsonBody = JsonUtil.toJson(new VerificationSessionMetadataRequestBody(credentialsProvider.getE164(), pushToken, mcc, mnc)); + try (Response response = makeServiceRequest(VERIFICATION_SESSION_PATH, "POST", jsonRequestBody(jsonBody), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty(), false)) { + return parseSessionMetadataResponse(response); + } + } + + public RegistrationSessionMetadataResponse getSessionStatus(String sessionId) throws IOException { + String path = VERIFICATION_SESSION_PATH + "/" + sessionId; - if (captchaToken.isPresent()) { - path += "&captcha=" + captchaToken.get(); - } else if (challenge.isPresent()) { - path += "&challenge=" + challenge.get(); + try (Response response = makeServiceRequest(path, "GET", jsonRequestBody(null), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty(), false)) { + return parseSessionMetadataResponse(response); } + } + + public RegistrationSessionMetadataResponse patchVerificationSession(String sessionId, @Nullable String pushToken, @Nullable String mcc, @Nullable String mnc, @Nullable String captchaToken, @Nullable String pushChallengeToken) throws IOException { + String path = VERIFICATION_SESSION_PATH + "/" + sessionId; - makeServiceRequest(path, "GET", null, headers, new VerificationCodeResponseHandler(), Optional.empty()); + final UpdateVerificationSessionRequestBody requestBody = new UpdateVerificationSessionRequestBody(captchaToken, pushToken, pushChallengeToken, mcc, mnc); + try (Response response = makeServiceRequest(path, "PATCH", jsonRequestBody(JsonUtil.toJson(requestBody)), NO_HEADERS, new PatchRegistrationSessionResponseHandler(), Optional.empty(), false)) { + return parseSessionMetadataResponse(response); + } } - public void requestVoiceVerificationCode(Locale locale, Optional captchaToken, Optional challenge) throws IOException { + public RegistrationSessionMetadataResponse requestVerificationCode(String sessionId, Locale locale, boolean androidSmsRetriever, VerificationCodeTransport transport) throws IOException { + String path = String.format(VERIFICATION_CODE_PATH, sessionId); Map headers = locale != null ? Collections.singletonMap("Accept-Language", locale.getLanguage() + "-" + locale.getCountry()) : NO_HEADERS; - String path = String.format(CREATE_ACCOUNT_VOICE_PATH, credentialsProvider.getE164()); + Map body = new HashMap<>(); + + switch (transport) { + case SMS: + body.put("transport", "sms"); + break; + case VOICE: + body.put("transport", "voice"); + break; + } + + body.put("client", androidSmsRetriever ? "android-2021-03" : "android"); + + try (Response response = makeServiceRequest(path, "POST", jsonRequestBody(JsonUtil.toJson(body)), headers, new RegistrationSessionResponseHandler(), Optional.empty(), false)) { + return parseSessionMetadataResponse(response); + } + } + + public RegistrationSessionMetadataResponse submitVerificationCode(String sessionId, String verificationCode) throws IOException { + String path = String.format(VERIFICATION_CODE_PATH, sessionId); + Map body = new HashMap<>(); + body.put("code", verificationCode); + try (Response response = makeServiceRequest(path, "PUT", jsonRequestBody(JsonUtil.toJson(body)), NO_HEADERS, new RegistrationCodeRequestResponseHandler(), Optional.empty(), false)) { + return parseSessionMetadataResponse(response); + } + } + + public VerifyAccountResponse submitRegistrationRequest(@Nullable String sessionId, @Nullable String recoveryPassword, AccountAttributes attributes, boolean skipDeviceTransfer) throws IOException { + String path = REGISTRATION_PATH; + if (sessionId == null && recoveryPassword == null) { + throw new IllegalArgumentException("Neither Session ID nor Recovery Password provided."); + + } - if (captchaToken.isPresent()) { - path += "?captcha=" + captchaToken.get(); - } else if (challenge.isPresent()) { - path += "?challenge=" + challenge.get(); + if (sessionId != null && recoveryPassword != null) { + throw new IllegalArgumentException("You must supply one and only one of either: Session ID, or Recovery Password."); + } + RegistrationSessionRequestBody body; + if (sessionId != null) { + body = new RegistrationSessionRequestBody(sessionId, null, attributes, skipDeviceTransfer); + } else { + body = new RegistrationSessionRequestBody(null, recoveryPassword, attributes, skipDeviceTransfer); } - makeServiceRequest(path, "GET", null, headers, new VerificationCodeResponseHandler(), Optional.empty()); + String response = makeServiceRequest(path, "POST", JsonUtil.toJson(body), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty()); + return JsonUtil.fromJson(response, VerifyAccountResponse.class); } public WhoAmIResponse getWhoAmI() throws IOException { @@ -362,26 +423,6 @@ public CdsiAuthResponse getCdsiAuth() throws IOException { return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class); } - public VerifyAccountResponse verifyAccountCode(String verificationCode, - String signalingKey, - int registrationId, - boolean fetchesMessages, - String pin, - String registrationLock, - byte[] unidentifiedAccessKey, - boolean unrestrictedUnidentifiedAccess, - AccountAttributes.Capabilities capabilities, - boolean discoverableByPhoneNumber, - int pniRegistrationId) - throws IOException - { - AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin, registrationLock, unidentifiedAccessKey, unrestrictedUnidentifiedAccess, capabilities, discoverableByPhoneNumber, null, pniRegistrationId); - String requestBody = JsonUtil.toJson(signalingKeyEntity); - String responseBody = makeServiceRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode), "PUT", requestBody); - - return JsonUtil.fromJson(responseBody, VerifyAccountResponse.class); - } - public VerifyAccountResponse changeNumber(@Nonnull ChangePhoneNumberRequest changePhoneNumberRequest) throws IOException { @@ -391,37 +432,13 @@ public VerifyAccountResponse changeNumber(@Nonnull ChangePhoneNumberRequest chan return JsonUtil.fromJson(responseBody, VerifyAccountResponse.class); } - public void setAccountAttributes(String signalingKey, - int registrationId, - boolean fetchesMessages, - String pin, - String registrationLock, - byte[] unidentifiedAccessKey, - boolean unrestrictedUnidentifiedAccess, - AccountAttributes.Capabilities capabilities, - boolean discoverableByPhoneNumber, - byte[] encryptedDeviceName, - int pniRegistrationId) + public void setAccountAttributes(@Nonnull AccountAttributes accountAttributes) throws IOException { - if (registrationLock != null && pin != null) { + if (accountAttributes.getRegistrationLock() != null && accountAttributes.getPin() != null) { throw new AssertionError("Pin should be null if registrationLock is set."); } - String name = (encryptedDeviceName == null) ? null : Base64.encodeBytes(encryptedDeviceName); - - AccountAttributes accountAttributes = new AccountAttributes(signalingKey, - registrationId, - fetchesMessages, - pin, - registrationLock, - unidentifiedAccessKey, - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber, - name, - pniRegistrationId); - makeServiceRequest(SET_ACCOUNT_ATTRIBUTES, "PUT", JsonUtil.toJson(accountAttributes)); } @@ -430,11 +447,6 @@ public String getNewDeviceVerificationCode() throws IOException { return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode(); } - public VerifyDeviceResponse verifySecondaryDevice(String verificationCode, AccountAttributes accountAttributes) throws IOException { - String responseText = makeServiceRequest(String.format(DEVICE_PATH, verificationCode), "PUT", JsonUtil.toJson(accountAttributes)); - return JsonUtil.fromJson(responseText, VerifyDeviceResponse.class); - } - public List getDevices() throws IOException { String responseText = makeServiceRequest(String.format(DEVICE_PATH, ""), "GET", null); return JsonUtil.fromJson(responseText, DeviceInfoList.class).getDevices(); @@ -458,8 +470,8 @@ public void unregisterGcmId() throws IOException { makeServiceRequest(REGISTER_GCM_PATH, "DELETE", null); } - public void requestPushChallenge(String gcmRegistrationId, String e164number) throws IOException { - makeServiceRequest(String.format(Locale.US, REQUEST_PUSH_CHALLENGE, gcmRegistrationId, e164number), "GET", null); + public void requestPushChallenge(String sessionId, String gcmRegistrationId) throws IOException { + patchVerificationSession(sessionId, gcmRegistrationId, null, null, null, null); } /** Note: Setting a KBS Pin will clear this */ @@ -559,7 +571,7 @@ public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional< public SignalServiceMessagesResult getMessages(boolean allowStories) throws IOException { Map headers = Collections.singletonMap("X-Signal-Receive-Stories", allowStories ? "true" : "false"); - + try (Response response = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", (RequestBody) null, headers, NO_HANDLER, Optional.empty(), false)) { validateServiceResponse(response); @@ -895,8 +907,26 @@ public Single> performIdentityCheck(@Nonn .onErrorReturn(ServiceResponse::forUnknownError); } + public Single> checkBackupAuthCredentials(@Nonnull BackupAuthCheckRequest request, + @Nonnull ResponseMapper responseMapper) + { + Single> requestSingle = Single.fromCallable(() -> { + try (Response response = getServiceConnection(BACKUP_AUTH_CHECK, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), Optional.empty(), false)) { + String body = response.body() != null ? readBodyString(response.body()): ""; + return responseMapper.map(response.code(), body, response::header, false); + } + }); + + return requestSingle + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .onErrorReturn(ServiceResponse::forUnknownError); + } + /** - * Gets the ACI for the given username, if it exists. This is an unauthenticated request. + * GET /v1/accounts/username_hash/{usernameHash} + * + * Gets the ACI for the given username hash, if it exists. This is an unauthenticated request. * * This network request can have the following error responses: *

    @@ -905,13 +935,13 @@ public Single> performIdentityCheck(@Nonn *
  • 400 - Bad Request. The request included authentication.
  • *
* - * @param username The username to look up. + * @param usernameHash The usernameHash to look up. * @return The ACI for the given username if it exists. * @throws IOException if a network exception occurs. */ - public @NonNull ACI getAciByUsername(String username) throws IOException { + public @NonNull ACI getAciByUsernameHash(String usernameHash) throws IOException { String response = makeServiceRequestWithoutAuthentication( - String.format(GET_USERNAME_PATH, URLEncoder.encode(username, StandardCharsets.UTF_8.toString())), + String.format(GET_USERNAME_PATH, URLEncoder.encode(usernameHash, StandardCharsets.UTF_8.toString())), "GET", null, NO_HEADERS, @@ -927,38 +957,16 @@ public Single> performIdentityCheck(@Nonn } /** - * Set the username for the account without seeing the discriminator first. - * - * @param nickname The user-supplied nickname, which must meet the requirements for usernames. - * @param existingUsername (Optional) If the account has a current username, indicates what the client thinks the current username is. Allows the server to - * deduplicate repeated requests. - * @return The username as set by the server, which includes both the nickname and discriminator. - * @throws IOException Thrown when the username is invalid or taken, or when another network error occurs. - */ - public @NonNull String setUsername(@NonNull String nickname, @Nullable String existingUsername) throws IOException { - SetUsernameRequest setUsernameRequest = new SetUsernameRequest(nickname, existingUsername); - - String responseString = makeServiceRequest(MODIFY_USERNAME_PATH, "PUT", JsonUtil.toJson(setUsernameRequest), NO_HEADERS, (responseCode, body) -> { - switch (responseCode) { - case 422: throw new UsernameMalformedException(); - case 409: throw new UsernameTakenException(); - } - }, Optional.empty()); - - SetUsernameResponse response = JsonUtil.fromJsonResponse(responseString, SetUsernameResponse.class); - return response.getUsername(); - } - - /** + * PUT /v1/accounts/username_hash/reserve * Reserve a username for the account. This replaces an existing reservation if one exists. The username is guaranteed to be available for 5 minutes and can * be confirmed with confirmUsername. * - * @param nickname The user-supplied nickname, which must meet the requirements for usernames. + * @param usernameHashes A list of hashed usernames encoded as web-safe base64 strings without padding. The list will have a max length of 20, and each hash will be 32 bytes. * @return The reserved username. It is available for confirmation for 5 minutes. * @throws IOException Thrown when the username is invalid or taken, or when another network error occurs. */ - public @NonNull ReserveUsernameResponse reserveUsername(@NonNull String nickname) throws IOException { - ReserveUsernameRequest reserveUsernameRequest = new ReserveUsernameRequest(nickname); + public @NonNull ReserveUsernameResponse reserveUsername(@NonNull List usernameHashes) throws IOException { + ReserveUsernameRequest reserveUsernameRequest = new ReserveUsernameRequest(usernameHashes); String responseString = makeServiceRequest(RESERVE_USERNAME_PATH, "PUT", JsonUtil.toJson(reserveUsernameRequest), NO_HEADERS, (responseCode, body) -> { switch (responseCode) { @@ -971,20 +979,33 @@ public Single> performIdentityCheck(@Nonn } /** + * PUT /v1/accounts/username_hash/confirm * Set a previously reserved username for the account. * + * @param username The username the user wishes to confirm. For example, myusername.27 * @param reserveUsernameResponse The response object from the reservation - * @throws IOException Thrown when the username is invalid or taken, or when another network error occurs. + * @throws IOException Thrown when the username is invalid or taken, or when another network error occurs. */ - public void confirmUsername(ReserveUsernameResponse reserveUsernameResponse) throws IOException { - ConfirmUsernameRequest confirmUsernameRequest = new ConfirmUsernameRequest(reserveUsernameResponse.getUsername(), reserveUsernameResponse.getReservationToken()); - - makeServiceRequest(CONFIRM_USERNAME_PATH, "PUT", JsonUtil.toJson(confirmUsernameRequest), NO_HEADERS, (responseCode, body) -> { - switch (responseCode) { - case 409: throw new UsernameIsNotReservedException(); - case 410: throw new UsernameTakenException(); - } - }, Optional.empty()); + public void confirmUsername(String username, ReserveUsernameResponse reserveUsernameResponse) throws IOException { + try { + byte[] randomness = new byte[32]; + random.nextBytes(randomness); + + byte[] proof = Username.generateProof(username, randomness); + ConfirmUsernameRequest confirmUsernameRequest = new ConfirmUsernameRequest(reserveUsernameResponse.getUsernameHash(), + Base64UrlSafe.encodeBytesWithoutPadding(proof)); + + makeServiceRequest(CONFIRM_USERNAME_PATH, "PUT", JsonUtil.toJson(confirmUsernameRequest), NO_HEADERS, (responseCode, body) -> { + switch (responseCode) { + case 409: + throw new UsernameIsNotReservedException(); + case 410: + throw new UsernameTakenException(); + } + }, Optional.empty()); + } catch (BaseUsernameException e) { + throw new IOException(e); + } } /** @@ -2222,6 +2243,8 @@ public GcmRegistrationId(String gcmRegistrationId, boolean webSocketChannel) { } } + public enum VerificationCodeTransport { SMS, VOICE } + private static class RegistrationLock { @JsonProperty private String pin; @@ -2493,28 +2516,118 @@ public void reportSpam(ServiceId serviceId, String serverGuid, String reportingT makeServiceRequest(String.format(REPORT_SPAM, serviceId.toString(), serverGuid), "POST", JsonUtil.toJson(new SpamTokenMessage(reportingToken))); } - private static class VerificationCodeResponseHandler implements ResponseCodeHandler { + private static class RegistrationSessionResponseHandler implements ResponseCodeHandler { + + @Override + public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException { + switch (responseCode) { + case 403: + throw new IncorrectRegistrationRecoveryPasswordException(); + case 404: + throw new NoSuchSessionException(); + case 409: + RegistrationSessionMetadataJson response; + try { + response = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class); + } catch (IOException e) { + Log.e(TAG, "Unable to read response body.", e); + throw new NonSuccessfulResponseCodeException(409); + } + if (response.pushChallengedRequired()) { + throw new PushChallengeRequiredException(); + } else if (response.captchaRequired()) { + throw new CaptchaRequiredException(); + } else { + throw new HttpConflictException(); + } + } + } + } + + + private static class PatchRegistrationSessionResponseHandler implements ResponseCodeHandler { + @Override - public void handle(int responseCode, ResponseBody responseBody) throws NonSuccessfulResponseCodeException, PushNetworkException { + public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException { + switch (responseCode) { + case 403: + throw new TokenNotAcceptedException(); + case 404: + throw new NoSuchSessionException(); + case 409: + RegistrationSessionMetadataJson response; + try { + response = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class); + } catch (IOException e) { + Log.e(TAG, "Unable to read response body.", e); + throw new NonSuccessfulResponseCodeException(409); + } + if (response.pushChallengedRequired()) { + throw new PushChallengeRequiredException(); + } else if (response.captchaRequired()) { + throw new CaptchaRequiredException(); + } else { + throw new HttpConflictException(); + } + } + } + } + + private static class RegistrationCodeRequestResponseHandler implements ResponseCodeHandler { + @Override public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException { switch (responseCode) { case 400: + throw new InvalidTransportModeException(); + case 404: + throw new NoSuchSessionException(); + case 409: + RegistrationSessionMetadataJson sessionMetadata; try { - String body = responseBody != null ? readBodyString(responseBody) : ""; - if (body.isEmpty()) { - throw new ImpossiblePhoneNumberException(); - } else { - throw NonNormalizedPhoneNumberException.forResponse(body); - } - } catch (MalformedResponseException e) { - Log.w(TAG, "Unable to parse 400 response! Assuming a generic 400."); - throw new ImpossiblePhoneNumberException(); + sessionMetadata = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class); + } catch (IOException e) { + Log.e(TAG, "Unable to read response body.", e); + throw new NonSuccessfulResponseCodeException(409); } - case 402: - throw new CaptchaRequiredException(); + if (sessionMetadata.getVerified()) { + throw new AlreadyVerifiedException(); + } else if (sessionMetadata.getNextVerificationAttempt() == null) { + // Note: this explicitly requires Verified to be false + throw new MustRequestNewCodeException(); + } else { + throw new HttpConflictException(); + } + case 502: + VerificationCodeFailureResponseBody codeFailureResponse; + try { + codeFailureResponse = JsonUtil.fromJson(body.string(), VerificationCodeFailureResponseBody.class); + } catch (IOException e) { + Log.e(TAG, "Unable to read response body.", e); + throw new NonSuccessfulResponseCodeException(502); + } + + throw new ExternalServiceFailureException(codeFailureResponse.getPermanentFailure(), codeFailureResponse.getReason()); } } } + + private static RegistrationSessionMetadataResponse parseSessionMetadataResponse(Response response) throws IOException { + long serverDeliveredTimestamp = 0; + try { + String stringValue = response.header(SERVER_DELIVERED_TIMESTAMP_HEADER); + stringValue = stringValue != null ? stringValue : "0"; + + serverDeliveredTimestamp = Long.parseLong(stringValue); + } catch (NumberFormatException e) { + Log.w(TAG, e); + } + + RegistrationSessionMetadataHeaders responseHeaders = new RegistrationSessionMetadataHeaders(serverDeliveredTimestamp); + RegistrationSessionMetadataJson responseBody = JsonUtil.fromJson(readBodyString(response), RegistrationSessionMetadataJson.class); + + return new RegistrationSessionMetadataResponse(responseHeaders, responseBody, null); + } + public static final class GroupHistory { private final GroupChanges groupChanges; private final Optional contentRange; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionMetadataResponse.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionMetadataResponse.kt new file mode 100644 index 0000000000..a85043465a --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionMetadataResponse.kt @@ -0,0 +1,39 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * This is a parsed, POJO representation of the server response describing the state of the registration session. + * The useful headers and the request body are wrapped in a single holder class. + */ +data class RegistrationSessionMetadataResponse( + val headers: RegistrationSessionMetadataHeaders, + val body: RegistrationSessionMetadataJson, + val state: RegistrationSessionState?, +) + +data class RegistrationSessionMetadataHeaders( + val timestamp: Long +) + +data class RegistrationSessionMetadataJson( + @JsonProperty("id") val id: String, + @JsonProperty("nextSms") val nextSms: Int?, + @JsonProperty("nextCall") val nextCall: Int?, + @JsonProperty("nextVerificationAttempt") val nextVerificationAttempt: Int?, + @JsonProperty("allowedToRequestCode") val allowedToRequestCode: Boolean, + @JsonProperty("requestedInformation") val requestedInformation: List, + @JsonProperty("verified") val verified: Boolean, +) { + fun pushChallengedRequired(): Boolean { + return requestedInformation.contains("pushChallenge") + } + + fun captchaRequired(): Boolean { + return requestedInformation.contains("captcha") + } +} + +data class RegistrationSessionState( + var pushChallengeTimedOut: Boolean, +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt new file mode 100644 index 0000000000..714da0ab47 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/RegistrationSessionRequestBody.kt @@ -0,0 +1,13 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +import org.whispersystems.signalservice.api.account.AccountAttributes + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class RegistrationSessionRequestBody( + @JsonProperty val sessionId: String? = null, + @JsonProperty val recoveryPassword: String? = null, + @JsonProperty val accountAttributes: AccountAttributes, + @JsonProperty val skipDeviceTransfer: Boolean +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameRequest.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameRequest.java index 8435c8cedd..eec41934ab 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameRequest.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameRequest.java @@ -2,15 +2,18 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; + class ReserveUsernameRequest { @JsonProperty - private String nickname; + private List usernameHashes; - ReserveUsernameRequest(String nickname) { - this.nickname = nickname; + ReserveUsernameRequest(List usernameHashes) { + this.usernameHashes = Collections.unmodifiableList(usernameHashes); } - String getNickname() { - return nickname; + List getUsernameHashes() { + return usernameHashes; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameResponse.java index d3d20b12a9..011c0b7268 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/ReserveUsernameResponse.java @@ -4,26 +4,18 @@ public class ReserveUsernameResponse { @JsonProperty - private String username; - - @JsonProperty - private String reservationToken; + private String usernameHash; ReserveUsernameResponse() {} /** * Visible for testing. */ - public ReserveUsernameResponse(String username, String reservationToken) { - this.username = username; - this.reservationToken = reservationToken; - } - - public String getUsername() { - return username; + public ReserveUsernameResponse(String usernameHash) { + this.usernameHash = usernameHash; } - String getReservationToken() { - return reservationToken; + public String getUsernameHash() { + return usernameHash; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java index 27d9041652..07a91cddd0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java @@ -44,7 +44,7 @@ public class SignalServiceEnvelopeEntity { private Boolean story; @JsonProperty - private byte[] reportingToken; + private byte[] reportSpamToken; public SignalServiceEnvelopeEntity() {} @@ -104,7 +104,7 @@ public boolean isStory() { return story != null && story; } - public byte[] getReportingToken() { - return reportingToken; + public byte[] getReportSpamToken() { + return reportSpamToken; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SpamTokenMessage.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SpamTokenMessage.kt index efc96a5ea1..fbf8eed5b2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SpamTokenMessage.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SpamTokenMessage.kt @@ -2,4 +2,4 @@ package org.whispersystems.signalservice.internal.push import com.fasterxml.jackson.annotation.JsonProperty -data class SpamTokenMessage(@JsonProperty val token: String) +data class SpamTokenMessage(@JsonProperty val token: String?) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UpdateVerificationSessionRequestBody.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UpdateVerificationSessionRequestBody.kt new file mode 100644 index 0000000000..18b9fb3fa5 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/UpdateVerificationSessionRequestBody.kt @@ -0,0 +1,16 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class UpdateVerificationSessionRequestBody( + @JsonProperty val captcha: String?, + @JsonProperty val pushToken: String?, + @JsonProperty val pushChallenge: String?, + @JsonProperty val mcc: String?, + @JsonProperty val mnc: String?, +) { + @JsonProperty + val pushTokenType: String? = if (pushToken != null) "fcm" else null +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerificationCodeFailureResponseBody.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerificationCodeFailureResponseBody.kt new file mode 100644 index 0000000000..d5ddb5b8b4 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerificationCodeFailureResponseBody.kt @@ -0,0 +1,12 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * Jackson parser for the response body from the server explaining a failure. + * See also [org.whispersystems.signalservice.api.push.exceptions.ExternalServiceFailureException] + */ +data class VerificationCodeFailureResponseBody( + @JsonProperty val permanentFailure: Boolean, + @JsonProperty val reason: String +) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerificationSessionMetadataRequestBody.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerificationSessionMetadataRequestBody.kt new file mode 100644 index 0000000000..f09aa70f9b --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerificationSessionMetadataRequestBody.kt @@ -0,0 +1,15 @@ +package org.whispersystems.signalservice.internal.push + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class VerificationSessionMetadataRequestBody( + @JsonProperty val number: String, + @JsonProperty val pushToken: String?, + @JsonProperty val mcc: String?, + @JsonProperty val mnc: String?, +) { + @JsonProperty + val pushTokenType: String? = if (pushToken != null) "fcm" else null +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java index bb74ec964e..4fde11c6c0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/VerifyAccountResponse.java @@ -13,6 +13,9 @@ public class VerifyAccountResponse { @JsonProperty public boolean storageCapable; + @JsonProperty + public String number; + @JsonCreator public VerifyAccountResponse() {} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java index af9180b885..2920ccd728 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java @@ -13,7 +13,7 @@ public class WhoAmIResponse { public String number; @JsonProperty - public String username; + public String usernameHash; public String getAci() { return uuid; @@ -27,7 +27,7 @@ public String getNumber() { return number; } - public String getUsername() { - return username; + public String getUsernameHash() { + return usernameHash; } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java index c61c659997..d592dddce4 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java @@ -147,7 +147,7 @@ public synchronized Observable connect() { .dns(dns) .sslSocketFactory(new Tls12SocketFactory(sslSocketFactory.first()), sslSocketFactory.second()) - .connectionSpecs(Util.immutableList(ConnectionSpec.RESTRICTED_TLS)) + .connectionSpecs(serviceUrl.getConnectionSpecs().orElse(Util.immutableList(ConnectionSpec.RESTRICTED_TLS))) .readTimeout(KEEPALIVE_TIMEOUT_SECONDS + 10, TimeUnit.SECONDS) .connectTimeout(KEEPALIVE_TIMEOUT_SECONDS + 10, TimeUnit.SECONDS); diff --git a/libsignal/service/src/main/proto/StorageService.proto b/libsignal/service/src/main/proto/StorageService.proto index 80f02efba1..c6870de26d 100644 --- a/libsignal/service/src/main/proto/StorageService.proto +++ b/libsignal/service/src/main/proto/StorageService.proto @@ -95,8 +95,9 @@ message ContactRecord { uint64 unregisteredAtTimestamp = 16; string systemGivenName = 17; string systemFamilyName = 18; - bool hidden = 19; - // NEXT ID: 20 + string systemNickname = 19; + bool hidden = 20; + // NEXT ID: 21 } message GroupV1Record { @@ -185,6 +186,7 @@ message AccountRecord { OptionalBool storyViewReceiptsEnabled = 30; bool hasReadOnboardingStory = 31; bool hasSeenGroupStoryEducationSheet = 32; + string username = 33; } message StoryDistributionListRecord { diff --git a/lintchecks/build.gradle b/lintchecks/build.gradle index 952c57ccb9..3274d1dafd 100644 --- a/lintchecks/build.gradle +++ b/lintchecks/build.gradle @@ -1,10 +1,5 @@ apply plugin: 'java-library' -repositories { - google() - mavenCentral() -} - dependencies { compileOnly lintLibs.lint.api compileOnly lintLibs.lint.checks diff --git a/lintchecks/src/main/java/org/signal/lint/CardViewDetector.java b/lintchecks/src/main/java/org/signal/lint/CardViewDetector.java new file mode 100644 index 0000000000..84ad79754f --- /dev/null +++ b/lintchecks/src/main/java/org/signal/lint/CardViewDetector.java @@ -0,0 +1,82 @@ +package org.signal.lint; + +import com.android.tools.lint.client.api.JavaEvaluator; +import com.android.tools.lint.detector.api.Category; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Implementation; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.JavaContext; +import com.android.tools.lint.detector.api.LintFix; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.Severity; +import com.intellij.psi.PsiMethod; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.UCallExpression; +import org.jetbrains.uast.UExpression; + +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("UnstableApiUsage") +public final class CardViewDetector extends Detector implements Detector.UastScanner { + + static final Issue CARD_VIEW_USAGE = Issue.create("CardViewUsage", + "Utilizing CardView instead of MaterialCardView subclass", + "Signal utilizes MaterialCardView for more consistent and pleasant CardViews.", + Category.MESSAGES, + 5, + Severity.WARNING, + new Implementation(CardViewDetector.class, Scope.JAVA_FILE_SCOPE)); + + @Override + public @Nullable List getApplicableConstructorTypes() { + return Collections.singletonList("androidx.cardview.widget.CardView"); + } + + @Override + public void visitConstructor(JavaContext context, @NotNull UCallExpression call, @NotNull PsiMethod method) { + JavaEvaluator evaluator = context.getEvaluator(); + + if (evaluator.isMemberInClass(method, "androidx.cardview.widget.CardView")) { + LintFix fix = quickFixIssueAlertDialogBuilder(call); + context.report(CARD_VIEW_USAGE, + call, + context.getLocation(call), + "Using 'androidx.cardview.widget.CardView' instead of com.google.android.material.card.MaterialCardView", + fix); + } + } + + private LintFix quickFixIssueAlertDialogBuilder(@NotNull UCallExpression alertBuilderCall) { + List arguments = alertBuilderCall.getValueArguments(); + UExpression context = arguments.get(0); + + String fixSource = "new com.google.android.material.card.MaterialCardView"; + + //Context context, AttributeSet attrs, int defStyleAttr + switch (arguments.size()) { + case 1: + fixSource += String.format("(%s)", context); + break; + case 2: + UExpression attrs = arguments.get(1); + fixSource += String.format("(%s, %s)", context, attrs); + break; + case 3: + UExpression attributes = arguments.get(1); + UExpression defStyleAttr = arguments.get(2); + fixSource += String.format("(%s, %s, %s)", context, attributes, defStyleAttr); + break; + + default: + throw new IllegalStateException("MaterialAlertDialogBuilder overloads should have 1 or 2 arguments"); + } + + String builderCallSource = alertBuilderCall.asSourceString(); + LintFix.GroupBuilder fixGrouper = fix().group(); + fixGrouper.add(fix().replace().text(builderCallSource).shortenNames().reformat(true).with(fixSource).build()); + return fixGrouper.build(); + } +} \ No newline at end of file diff --git a/lintchecks/src/main/java/org/signal/lint/Registry.java b/lintchecks/src/main/java/org/signal/lint/Registry.java index 3fdc026a35..9fe5314732 100644 --- a/lintchecks/src/main/java/org/signal/lint/Registry.java +++ b/lintchecks/src/main/java/org/signal/lint/Registry.java @@ -1,15 +1,23 @@ package org.signal.lint; import com.android.tools.lint.client.api.IssueRegistry; +import com.android.tools.lint.client.api.Vendor; import com.android.tools.lint.detector.api.ApiKt; import com.android.tools.lint.detector.api.Issue; +import org.jetbrains.annotations.Nullable; + import java.util.Arrays; import java.util.List; @SuppressWarnings("UnstableApiUsage") public final class Registry extends IssueRegistry { + @Override + public Vendor getVendor() { + return new Vendor("Signal", "Signal", "Signal", "Signal"); + } + @Override public List getIssues() { return Arrays.asList(SignalLogDetector.LOG_NOT_SIGNAL, @@ -20,7 +28,8 @@ public List getIssues() { BlockingGetDetector.UNSAFE_BLOCKING_GET, RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE, ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE, - StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE); + StartForegroundServiceDetector.START_FOREGROUND_SERVICE_ISSUE, + CardViewDetector.CARD_VIEW_USAGE); } @Override diff --git a/lintchecks/src/test/resources/CardViewStub.java b/lintchecks/src/test/resources/CardViewStub.java new file mode 100644 index 0000000000..97860064a8 --- /dev/null +++ b/lintchecks/src/test/resources/CardViewStub.java @@ -0,0 +1,16 @@ +package androidx.appcompat.app; + +public class CardView { + + public CardView(Context context) { + + } + + public CardView(Context context, AttributeSet attrs) { + + } + + public CardView(Context context, AttributeSet attrs, int defStyleAttr) { + + } +} diff --git a/paging/app/build.gradle b/paging/app/build.gradle index 99122ec9a4..5a003895b6 100644 --- a/paging/app/build.gradle +++ b/paging/app/build.gradle @@ -1,34 +1,15 @@ -apply plugin: 'com.android.application' +plugins { + id 'signal-sample-app' +} android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'org.signal.pagingtest' defaultConfig { applicationId "org.signal.pagingtest" - versionCode 1 - versionName "1.0" - - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION } } dependencies { - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.appcompat - implementation libs.material.material - implementation libs.androidx.constraintlayout - - testImplementation testLibs.junit.junit - implementation project(':paging') } \ No newline at end of file diff --git a/paging/app/src/main/AndroidManifest.xml b/paging/app/src/main/AndroidManifest.xml index 687310857d..6320a7ca67 100644 --- a/paging/app/src/main/AndroidManifest.xml +++ b/paging/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> - - + \ No newline at end of file diff --git a/paging/lib/build.gradle b/paging/lib/build.gradle index c5e85d9553..aa3b1d694a 100644 --- a/paging/lib/build.gradle +++ b/paging/lib/build.gradle @@ -1,27 +1,11 @@ -apply plugin: 'com.android.library' +plugins { + id 'signal-library' +} android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } + namespace 'org.signal.paging' } dependencies { - implementation libs.androidx.appcompat - implementation libs.androidx.multidex - implementation libs.material.material - implementation libs.rxjava3.rxandroid - implementation libs.rxjava3.rxjava implementation project(':core-util') - testImplementation testLibs.junit.junit -} \ No newline at end of file +} diff --git a/paging/lib/src/main/AndroidManifest.xml b/paging/lib/src/main/AndroidManifest.xml index e72d78efcd..7277dc362a 100644 --- a/paging/lib/src/main/AndroidManifest.xml +++ b/paging/lib/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/photoview/build.gradle b/photoview/build.gradle index 010e1ba692..5fde00f778 100644 --- a/photoview/build.gradle +++ b/photoview/build.gradle @@ -1,62 +1,11 @@ plugins { - id 'com.android.library' - id 'com.google.protobuf' - id 'kotlin-android' - id 'kotlin-kapt' + id 'signal-library' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -protobuf { - protoc { - artifact = 'com.google.protobuf:protoc:3.18.0' - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - option "lite" - } - } - } - } + namespace 'com.github.chrisbanes.photoview' } dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' - lintChecks project(':lintchecks') - - coreLibraryDesugaring libs.android.tools.desugar - - api libs.androidx.annotation - - implementation libs.androidx.core.ktx - implementation libs.androidx.lifecycle.common.java8 - implementation libs.google.protobuf.javalite - implementation libs.androidx.sqlite - implementation libs.rxjava3.rxjava - - testImplementation testLibs.junit.junit - testImplementation testLibs.mockito.core - testImplementation (testLibs.robolectric.robolectric) { - exclude group: 'com.google.protobuf', module: 'protobuf-java' - } } diff --git a/photoview/src/main/AndroidManifest.xml b/photoview/src/main/AndroidManifest.xml index e598439623..7277dc362a 100644 --- a/photoview/src/main/AndroidManifest.xml +++ b/photoview/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/qr/app/build.gradle b/qr/app/build.gradle index 09c5998730..0933d86b61 100644 --- a/qr/app/build.gradle +++ b/qr/app/build.gradle @@ -1,6 +1,10 @@ -apply from: "$rootProject.projectDir/signalModuleApp.gradle" +plugins { + id 'signal-sample-app' +} android { + namespace 'org.signal.qrtest' + defaultConfig { applicationId "org.signal.qrtest" } @@ -8,9 +12,6 @@ android { dependencies { implementation project(':qr') - implementation libs.rxjava3.rxjava - implementation libs.rxjava3.rxandroid - implementation libs.rxjava3.rxkotlin implementation libs.google.zxing.android.integration implementation libs.google.zxing.core diff --git a/qr/app/src/main/AndroidManifest.xml b/qr/app/src/main/AndroidManifest.xml index 3a8aa2c4e8..75433c31d3 100644 --- a/qr/app/src/main/AndroidManifest.xml +++ b/qr/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + + xmlns:tools="http://schemas.android.com/tools"> diff --git a/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt b/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt index 6981c3efdd..eb58325237 100644 --- a/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt +++ b/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt @@ -65,6 +65,7 @@ class QrProcessor { companion object { private val TAG = Log.tag(QrProcessor::class.java) + /** For debugging only */ var listener: ((LuminanceSource) -> Unit)? = null } diff --git a/reproducible-builds/docker-compose.yml b/reproducible-builds/docker-compose.yml index 96450bcdd4..e71c68b9f8 100644 --- a/reproducible-builds/docker-compose.yml +++ b/reproducible-builds/docker-compose.yml @@ -4,11 +4,12 @@ services: image: reproducible-molly build: context: .. - command: :app:assembleRelease :app:bundleRelease + command: :app:assembleRelease :app:bundleRelease --no-daemon volumes: - ./certs:/molly/app/certs:ro - ./outputs:/molly/app/build/outputs environment: + - GRADLE_OPTS - CI_APP_TITLE - CI_APP_FILENAME - CI_PACKAGE_ID @@ -18,14 +19,3 @@ services: - CI_KEYSTORE_PATH - CI_KEYSTORE_PASSWORD - CI_KEYSTORE_ALIAS - test: - image: reproducible-molly - build: - context: .. - command: build - volumes: - - ./test-reports:/molly/app/build/reports - environment: - - BUILD_SCAN=1 - profiles: - - test diff --git a/reproducible-builds/test-reports/.keep b/reproducible-builds/test-reports/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/settings.gradle b/settings.gradle index 83e3b6843d..d50017d539 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,12 +1,45 @@ -plugins { - id "com.gradle.enterprise" version "3.11.1" +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + includeBuild("build-logic") } -gradleEnterprise { - buildScan { - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" - publishAlwaysIf(System.getenv("BUILD_SCAN") != null) +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + mavenLocal() + maven { + url "https://raw.githubusercontent.com/signalapp/maven/master/sqlcipher/release/" + content { + includeModule 'org.signal', 'sqlcipher-android' + } + } + maven { + url "https://raw.githubusercontent.com/mollyim/maven/master/argon2/releases/" + content { + includeModule 'im.molly', 'argon2' + } + } + maven { + url "https://raw.githubusercontent.com/mollyim/maven/master/ringrtc/releases/" + content { + includeModule 'im.molly', 'ringrtc-android' + } + } + maven { + url "https://raw.githubusercontent.com/mollyim/maven/master/native-utils/releases/" + content { + includeModule 'im.molly', 'native-utils' + } + } + maven { + url "https://dl.cloudsmith.io/qxAgwaeEE1vN8aLU/mobilecoin/mobilecoin/maven/" + } } } diff --git a/signalModule.gradle b/signalModule.gradle deleted file mode 100644 index de78c96478..0000000000 --- a/signalModule.gradle +++ /dev/null @@ -1,46 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'org.jlleitschuh.gradle.ktlint' - -android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - - kotlinOptions { - jvmTarget = '1.8' - } - - lintOptions { - disable 'InvalidVectorPath' - } -} - -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" -} - -dependencies { - lintChecks project(':lintchecks') - - implementation project(':core-util') - - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.core.ktx - implementation libs.androidx.fragment.ktx - implementation libs.androidx.annotation - implementation libs.androidx.appcompat -} diff --git a/signalModuleApp.gradle b/signalModuleApp.gradle deleted file mode 100644 index 0f34c61f92..0000000000 --- a/signalModuleApp.gradle +++ /dev/null @@ -1,47 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'org.jlleitschuh.gradle.ktlint' - -android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - versionCode 1 - versionName "1.0" - - minSdkVersion 21 - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - kotlinOptions { - jvmTarget = '1.8' - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } -} - -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" -} - -dependencies { - coreLibraryDesugaring libs.android.tools.desugar - - implementation libs.androidx.activity.ktx - implementation libs.androidx.appcompat - implementation libs.material.material - implementation libs.androidx.constraintlayout - - implementation libs.kotlin.stdlib.jdk8 - - testImplementation testLibs.junit.junit - - implementation project(':core-util') -} \ No newline at end of file diff --git a/spinner/app/build.gradle b/spinner/app/build.gradle index b03b0d0720..df44d15e9f 100644 --- a/spinner/app/build.gradle +++ b/spinner/app/build.gradle @@ -1,61 +1,18 @@ plugins { - id 'com.android.application' - id 'kotlin-android' - id 'org.jlleitschuh.gradle.ktlint' -} - -repositories { - maven { - url "https://raw.githubusercontent.com/signalapp/maven/master/sqlcipher/release/" - content { - includeGroupByRegex "org\\.signal.*" - } - } -} - -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" + id 'signal-sample-app' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK + namespace 'org.signal.spinnertest' defaultConfig { applicationId "org.signal.spinnertest" - versionCode 1 - versionName "1.0" - - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - kotlinOptions { - jvmTarget = '1.8' - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION } } dependencies { - coreLibraryDesugaring libs.android.tools.desugar + implementation project(':spinner') - implementation libs.androidx.core.ktx - implementation libs.androidx.activity.ktx - implementation libs.androidx.appcompat - implementation libs.material.material - implementation libs.androidx.multidex - implementation libs.androidx.constraintlayout - implementation libs.signal.android.database.sqlcipher implementation libs.androidx.sqlite - - testImplementation testLibs.junit.junit - - implementation project(':spinner') + implementation libs.signal.android.database.sqlcipher } \ No newline at end of file diff --git a/spinner/app/src/main/AndroidManifest.xml b/spinner/app/src/main/AndroidManifest.xml index a1d79ebcfa..74b3ce4bc8 100644 --- a/spinner/app/src/main/AndroidManifest.xml +++ b/spinner/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/spinner/app/src/main/res/layout/item.xml b/spinner/app/src/main/res/layout/item.xml index a0cd66dbdd..3c2e797f41 100644 --- a/spinner/app/src/main/res/layout/item.xml +++ b/spinner/app/src/main/res/layout/item.xml @@ -9,7 +9,7 @@ android:clipToPadding="false" android:clipChildren="false"> - - + \ No newline at end of file diff --git a/spinner/lib/build.gradle b/spinner/lib/build.gradle index 1c901986d3..74c86c9eb3 100644 --- a/spinner/lib/build.gradle +++ b/spinner/lib/build.gradle @@ -1,43 +1,17 @@ plugins { - id 'com.android.library' - id 'kotlin-android' - id 'org.jlleitschuh.gradle.ktlint' + id 'signal-library' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - - kotlinOptions { - jvmTarget = '1.8' - } -} - -ktlint { - // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 - version = "0.43.2" + namespace 'org.signal.spinner' } dependencies { - implementation libs.jknack.handlebars - - implementation libs.androidx.appcompat - implementation libs.material.material - implementation libs.androidx.multidex - implementation libs.androidx.sqlite implementation project(':core-util') - testImplementation testLibs.junit.junit + implementation libs.jknack.handlebars implementation libs.nanohttpd.webserver + implementation libs.androidx.sqlite + + testImplementation testLibs.junit.junit } \ No newline at end of file diff --git a/spinner/lib/src/main/AndroidManifest.xml b/spinner/lib/src/main/AndroidManifest.xml index cd8bdc69a9..7277dc362a 100644 --- a/spinner/lib/src/main/AndroidManifest.xml +++ b/spinner/lib/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt b/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt index 2999821bf6..a60a4d98db 100644 --- a/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt +++ b/spinner/lib/src/main/java/org/signal/spinner/PluginResult.kt @@ -4,6 +4,6 @@ sealed class PluginResult(val type: String) { data class TableResult( val columns: List, val rows: List>, - val rowCount: Int = rows.size, + val rowCount: Int = rows.size ) : PluginResult("table") } diff --git a/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt b/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt index 3a9a483091..087ee28539 100644 --- a/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt +++ b/spinner/lib/src/main/java/org/signal/spinner/SpinnerServer.kt @@ -424,7 +424,7 @@ internal class SpinnerServer( val tableNames: List, val table: String? = null, val queryResult: QueryResult? = null, - val pagingData: PagingData? = null, + val pagingData: PagingData? = null ) : PrefixPageData data class QueryPageModel( @@ -461,7 +461,7 @@ internal class SpinnerServer( val rows: List>, val rowCount: Int = rows.size, val timeToFirstRow: String, - val timeToReadRows: String, + val timeToReadRows: String ) data class TableInfo( diff --git a/sticky-header-grid/build.gradle b/sticky-header-grid/build.gradle index 8c5183c684..ef55b26f39 100644 --- a/sticky-header-grid/build.gradle +++ b/sticky-header-grid/build.gradle @@ -1,62 +1,11 @@ plugins { - id 'com.android.library' - id 'com.google.protobuf' - id 'kotlin-android' - id 'kotlin-kapt' + id 'signal-library' } android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - multiDexEnabled true - } - - compileOptions { - coreLibraryDesugaringEnabled true - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -protobuf { - protoc { - artifact = 'com.google.protobuf:protoc:3.18.0' - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - option "lite" - } - } - } - } + namespace 'com.codewaves.stickyheadergrid' } dependencies { implementation 'androidx.recyclerview:recyclerview:1.2.1' - lintChecks project(':lintchecks') - - coreLibraryDesugaring libs.android.tools.desugar - - api libs.androidx.annotation - - implementation libs.androidx.core.ktx - implementation libs.androidx.lifecycle.common.java8 - implementation libs.google.protobuf.javalite - implementation libs.androidx.sqlite - implementation libs.rxjava3.rxjava - - testImplementation testLibs.junit.junit - testImplementation testLibs.mockito.core - testImplementation (testLibs.robolectric.robolectric) { - exclude group: 'com.google.protobuf', module: 'protobuf-java' - } } diff --git a/sticky-header-grid/src/main/AndroidManifest.xml b/sticky-header-grid/src/main/AndroidManifest.xml index e2c2c148e3..7277dc362a 100644 --- a/sticky-header-grid/src/main/AndroidManifest.xml +++ b/sticky-header-grid/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/video/build.gradle b/video/build.gradle index b8870f8ea1..47e27bb2d9 100644 --- a/video/build.gradle +++ b/video/build.gradle @@ -1,25 +1,12 @@ -apply plugin: 'com.android.library' +plugins { + id 'signal-library' +} android { - buildToolsVersion BUILD_TOOL_VERSION - compileSdkVersion COMPILE_SDK - - defaultConfig { - minSdkVersion MINIMUM_SDK - targetSdkVersion TARGET_SDK - } - - compileOptions { - sourceCompatibility JAVA_VERSION - targetCompatibility JAVA_VERSION - } + namespace 'org.signal.video' } dependencies { - lintChecks project(':lintchecks') - - api libs.androidx.annotation - implementation project(':core-util') implementation(libs.bundles.mp4parser) { diff --git a/video/src/main/AndroidManifest.xml b/video/src/main/AndroidManifest.xml index 99dd357bf8..8072ee00db 100644 --- a/video/src/main/AndroidManifest.xml +++ b/video/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - +